# pwndbg调试工具使用教程

### **C++ 进程内存空间分布** <a href="#c-jin-cheng-nei-cun-kong-jian-fen-bu" id="c-jin-cheng-nei-cun-kong-jian-fen-bu"></a>

内存可以分为以下几段：

> * **文本段**：包含实际要执行的代码（机器指令）和常量。它通常是共享的，多个实例之间共享文本段。文本段是不可修改的。
> * **初始化数据段**：包含程序已经初始化的全局变量，.data。
> * **未初始化数据段**：包含程序未初始化的全局变量，.bbs。该段中的变量在执行之前初始化为0或NULL。
> * **栈**：由系统管理，由高地址向低地址扩展。
> * **堆**：动态内存，由用户管理。通过malloc/alloc/realloc、new/new\[]申请空间，通过free、delete/delete\[]释放所申请的空间。由低地址向高地址扩展。

![process\_mem\_lay](https://dongwenhu.github.io/img/process_mem_lay.png)

### 内存布局

![](https://1702163534-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M7oudw7uWMhEPV8wJgq%2F-ME0wgGcgrOmy7MKQ4Ji%2F-ME1H_YmK3zPI1lwCLrr%2Fimage.png?alt=media\&token=540f7c35-7d46-4345-b890-32eddada6afb)

进程地址空间从低地址开始依次是代码段(Text)、数据段(Data)、BSS段、堆、内存映射段(mmap)、栈。

![](https://1702163534-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M7oudw7uWMhEPV8wJgq%2F-ME0wgGcgrOmy7MKQ4Ji%2F-ME1HMAuymhJCOZLAUk3%2Fimage.png?alt=media\&token=c63e4b0b-229e-42ff-a33a-7fcdb910bdfe)

&#x20;**\[ 注意：BSS段 和 data段的区别是 ，如果一个全局变量没有被初始化（或被初始化为0），那么他就存放在bss段；如果一个全局变量被初始化为非0，那么他就被存放在data段。]**

在进程被载入内存中时，基本上被分裂成许多小的节（section）。我们比较关注的是6个主要的节：&#x20;

（1） .text 节&#x20;

（2）.data 节&#x20;

（3）.bss 节&#x20;

（4） 堆节&#x20;

（5） 栈节&#x20;

（6）环境/参数节 环境/参数节（environment/arguments section）用来存储系统环境变量的一份复制文件， 进程在运行时可能需要。

例如，运行中的进程，可以通过环境变量来访问路径、shell 名称、主机名等信息。 该节是可写的，因此在格式串（format string）和缓冲区溢出（buffer overflow）攻击中都可以使用该节。 另外，命令行参数也保持在该区域中。

### pwndbg调试命令

程序的调试过程主要有：单步执行，跳入函数，跳出函数，设置断点，设置观察点，查看变量。

GDB主要可以做4大类事（加上一些其他的辅助工作），以帮助用户在程序运行过程中发现bug。

* 启动您的程序，并列出可能会影响它运行的一些信息
* 使您的程序在特定条件下停止下来
* 当程序停下来的时候，检查发生了什么
* 对程序做出相应的调整，这样您就能尝试纠正一个错误并继续发现其它错误

#### 基本命令包括以下几个：

* 1.输入gdb或者gdb-multiarch program进行本地调试
* 2.如果是本地程序的话，可以输入entry或者start进入第一条指令
  * entry
    * 如果是本地调试有main函数，则可以输入main直接进入main函数
      * main
* 3.如果调试的程序有源代码，则输入n执行的时候是按照源代码一行一行执行的；
  * 如果没有源代码，则输入ni来按照汇编语言执行一条条指令
  * 如果指令是调用了函数，则可以通过si进入调用内部
* 4.输入p 函数名可以打印出函数地址，然后使用nearpc 地址即可显示此函数的反汇编代码
  * p scanf
  * nearpc 0x7ffff7a777f0
  * p/x：以16进制显示变量值
  * p/d：10进制显示
  * p/o：8进制显示
* 5.下断点，如果有符号，可以直接使用b 函数名下断点，如果没有，则需要使用b \*address的方式下断点
  * b read/b recv
  * b \*0x40062b
  * 通过
    * bl列出所有断点&#x20;
    * bc清除断点&#x20;
    * bd临时禁用断点&#x20;
    * be启用断点
* 6.下完断点之后需要执行到断点，从头开始运行输入r命令，继续运行输入c
* 7.通过stack命令可以查看栈的数据
  * stack 0x20 \[0x20为查看的长度]
* 8.输入context可以重新刷新一下之前的调试界面
* 9.可以通过hexdump查看栈的地址的内容
  * hexdump 0x7fffffffe3b8
  * x/s 0x7fffffffe3b8
* 10.通过nextcall可以直接进行下一次调用
* 11.vmmap可以查看内存布局
* 12.可以使用p $eax查看寄存器的值，也可以使用i r eax查看
* 13.如果使用的调试器为peda：
  * pattern create 200
  * pattern offset 0xaddr
* 14.如果遇到栈溢出需要查看偏移量：
  * cyclic 200生成200字节的长度数据
  * cyclic -l 覆盖的前四个字母：cyclic -l jaab,输出即为偏移量

### IDA和OD快捷键

#### IDA

* y修改变量为类型加值
* n单纯修改变量
* G跳转到指定地址
* X查看交叉引用
* shift+F12查找字符串
* 数组和
* 空格切换图形和文本视图
* shift+e查看data段初始化的全局变量及静态变量

#### OD

* F2下断点
* F4运行到选中指令
* F8步过
* F7单步执行
* 数据窗口跟随
* 智能搜索可以查找的字符串更全
* od的基址与ida可能不一样
* &#x20;在看[Shark恒 破解教程](http://www.52pojie.cn/thread-200439-1-1.html) 时，有很多吾友对教程中按键的含义不懂，我发一个常用的快捷键列表，希望对新手有点帮助。\
  打开一个新的可执行程序 (F3)\
  重新运行当前调试的程序 (Ctrl+F2)\
  当前调试的程序 (Alt+F2)\
  运行选定的程序进行调试 (F9)\
  暂时停止被调试程序的执行 (F12)\
  单步进入被调试程序的 Call 中 (F7)\
  步过被调试程序的 Call (F8)\
  跟入被调试程序的 Call 中 (Ctrl+F11)\
  跟踪时跳过被调试程序的 Call (Ctrl+F12)\
  执行直到返回 (Ctrl+F9)\
  显示记录窗口 (Alt+L)\
  显示模块窗口 (Alt+E)\
  显示内存窗口 (Alt+M)\
  显示 CPU 窗口 (Alt+C)\
  显示补丁窗口 (Ctrl+P)\
  显示呼叫堆栈 (Alt+K)\
  显示断点窗口 (Alt+B)\
  打开调试选项窗口 (Alt+O)

### 二进制一些基础知识

代码测试：

```c
#include <stdio.h>


int add(int x, int y)
{
        int sum;
        sum = x+y;
        return sum;
}

int main()
{
        int p = add(3, 4);
        printf("%d\n",p);
        return 0;
}

```

```c
gcc -g -m32 learn_stack.c -o learn_stack_32
```

* 栈从内存高地址向低地址生长，调用执行函数的时候：首先将被调用函数参数按照从右向左的顺序依次入栈，之后的流程为：
  * 1.将call指令的下一条地址入栈，即被调用函数的返回地址入栈；
  * 2.main函数的栈底地址入栈，即ebp入栈；mov ebp,esp;使得ebp指向新的栈顶；
  * 3.之后被调用函数的局部变量入栈，执行到leave ret指令的时候：mov esp,ebp;pop ebp;，相当于恢复ebp原来的值，esp指向原来的位置，ret直接将pop esp;mov eip,esp;
* plt与got
  * PLT:CODE
  * GOT:ADDRESS
  * PROGRAM CALL --> PLT --> GOT -->LIBC
* 64位传参：rdi,rsi,rdx...       rdi, rsi, rdx, rcx, r8, r9,栈
  * 当参数少于7个时， 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9
  * 当参数为7个以上时， 前 6 个与前面一样， 但后面的依次从 “右向左” 放入栈中，即和32位汇编一样。
* POP rdi; ret// 通过*pop*为*rdi*赋值,再通过*ret*指令跳转到我们希望的地方
* ret：sp增加一个内存单元栈顶数据出栈赋值给ip寄存器
