程序和gdb调试实操
一、认识程序实操过程
1.前提:在linux系统下要下载gcc,准备一个事先写好的C语言程序question_1.c

sudo apt install gcc
sudo apt-get install gcc-multilib g++-multilib module-assistant
这句话大致意思就是:系统管理员,请从软件仓库为我们安装:
1) 能让GCC编译器兼容32位和64位程序的跨平台编译工具;
2) 一个能自动帮我们编译和安装内核驱动程序的便捷助手。
2.进入对应路径:在正确文件路径之运行正确的文件是很重要的,否则一直找不到文件或者命令
这里有个小tips(直接在文件夹里面打开终端更快嗷)

3.打开之后让我们用gcc命令编译一下这个C语言程序

由上图可见的是我们编译产生的question_1_x86程序是显示绿色的,而question_1_x64我是直接从主机粘贴在虚拟机的所以它没有执行权限,需要(chmod +x 程序:chmod 777 question_1_x64 )一下
4.编译成功之后运行:
./question_1_x64
这一步是测试一下程序的运行原理,看程序正常运行是怎样的运行逻辑

在前面第一点的C语言源程序截图中其实我们已经可以知道这程序的基本逻辑
5.file 指令查看文件类型
拿到一个程序的时候首先开始步骤就是分析这个程序要做什么,什么类型

可看到x86和x64的类型是不一样的,
可见 这是file 命令对 64位Linux可执行程序 的输出结果
ELF-Linux系统标准的可执行文件格式
LSB-内存中按小端序存储
pie executable-这是现代Linux程序的安全特性
dynamically linked-动态链接
/lib/ld-linux.so.2--负责程序运行前的所有准备工作(找库、加载库、链接)
not stripped-未剥离调试信息
6.readelf命令
可以看到程序的ELF文件头,节头表(编译器和组织码、数据、字符串等),程序头表,动态段,符号表
less中可以用空格键翻页,/main可以查找main函数,q退出
readelf -a question_1_x64 | less
上图只粘贴了ELF头,也可以通过其他特定参数单独查看
# 只看文件头
readelf -h question_1_x64
# 只看节头表
readelf -S question_1_x64
# 只看符号表(这是分析程序逻辑的关键!)
readelf -s question_1_x64
# 只看程序头表(内存布局)
readelf -l question_1_x64
# 只看动态段(依赖关系)
readelf -d question_1_x64
file相当于只看一眼大概,而readelf -a可以查看全部,代码放在哪里、数据放在哪里、有哪些函数、依赖什么库等等
7.nm指令
命令用于列出目标文件(可执行文件、静态库、动态库)中定义的符号
**nm question_1_x64**
9.hexdump
hexdump 让你能直接查看文件的二进制内容
10.strings
strings 命令会扫描任何文件(包括二进制可执行文件),找出其中所有连续的可打印字符序列(字符串),并将其输出

上图仅展示部分字符串,其实在IDA里面直接shift+F12就可以查看
11.ldd
查看程序动态依赖关系
ldd question_1_x64

12.objdump
神秘古老用法,在没有ida之前的劳模,哈哈开玩笑,其实是可以直接在终端查查看函数结构的一个好用的工具,不过随着ida这个神秘女人出现,objdump有点跟不上时代了,可以看到反汇编、短信息、符号表
主要用法:objdump question_1_x64
加不同参数用法不同:
# 反汇编整个代码段
objdump -d question_1_x64 | less
objdump -d question_1_x64 _M intel | less
但是最常用还是-d
**-d**:反汇编代码段**-D**:反汇编所有段**-S**:交织显示源代码和汇编代码**-C**:demangle C++ 符号名**-l**:显示源代码文件名和行号
这是加了-M intel 因特尔下面呈现的汇编代码


大致意思就是

再补充一个:
汇编指令tips

movq %rsp ,%rbp :mov 用来赋值
mov rbp,rsp : rbp=rsp
~add sub xor call
lea Load effective address -->lea rax ,[rbp-x018] : rax =rbp-0(大部分是用来运算)
J指令:je jne 跳转与标志位有关,满足什么条件就跳转
13.gcc编译成汇编形式
gcc -S question_1.c

用vim 或nano查看一下

二、GDB
这玩意是一个动态的调试器,这周也是狠狠学习怎么用它调试
1.GDB怎么来?
sudo apt-get update
sudo apt-get install gdb
gdb --version
2.gdb怎末用之?
1.start:开始啦~
gdb ./question_1_x64
(gdb) start
键入tap可以查看所有gdb命令如下图

2.run:跑起来

3.i r :info registers
查询 寄存器状态

小芝士之寄存器:
一、x86-64架构:
- 通用寄存器(16个,64位)
| 64位**** | 32位 | 16位 | 高8位 | 低8位 | 主要用途 |
|---|---|---|---|---|---|
| rax | eax | ax | ah | al | 返回值, 算术运算 |
| rbx | ebx | bx | bh | bl | 基址寄存器, 数据存储 |
| rcx | ecx | cx | ch | cl | 计数寄存器, 循环计数 |
| rdx | edx | dx | dh | dl | 数据寄存器, I/O操作 |
| rsi | esi | si | - | sil | 源索引, 函数参数2 |
| rdi | edi | di | - | dil | 目的索引, 函数参数1 |
| rbp | ebp | bp | - | bpl | 基址指针, 栈帧底部 |
| rsp | esp | sp | - | spl | 栈指针, 栈顶 |
| r8 | r8d | r8w | - | r8b | 通用寄存器/参数5 |
| r9 | r9d | r9w | - | r9b | 通用寄存器/参数6 |
| r10 | r10d | r10w | - | r10b | 通用寄存器 |
| r11 | r11d | r11w | - | r11b | 通用寄存器 |
| r12 | r12d | r12w | - | r12b | 通用寄存器 |
| r13 | r13d | r13w | - | r13b | 通用寄存器 |
| r14 | r14d | r14w | - | r14b | 通用寄存器 |
| r15 | r15d | r15w | - | r15b | 通用寄存器 |
2.特殊寄存器
| 寄存器 | 位数 | 作用 |
|---|---|---|
| rip | 64位 | 指令指针 - 下一条要执行的指令地址 |
| rflags | 64位 | 标志寄存器 - 状态标志 |
| cs, ds, es, fs, gs, ss | 16位 | 段寄存器 - 内存分段 |
特殊寄存器中第三行已经成为时代眼泪,rip比较重要
:::info
有个小tips:rbp rsp跟栈相关
:::
二、x86架构(32位)
通用寄存器(8个,32位):eax, ebx, ecx, edx, esi, edi, ebp, esp
对应16位和8位:
eax → ax → ah + al
ebx → bx → bh + bl
ecx → cx → ch + cl
edx → dx → dh + dl
三、ARM64架构
1.通用寄存器(31个,64位):x0-x30
| 寄存器 | 用途 |
|---|---|
| x0-x7 | 函数参数/返回值 |
| x8 | 间接结果寄存器 |
| x9-x15 | 临时寄存器 |
| x16-x17 | 内部临时寄存器 |
| x18 | 平台保留寄存器 |
| x19-x28 | 被调用者保存寄存器 |
| x29 | 帧指针 |
| x30 | 链接寄存器 |
| sp | 栈指针 |
| pc | 程序计数器 |
2.浮点寄存器
v0-v31 (128位)
4.查看一下rip的位置 disas $rip: disassemble $rip

5.disas main

6.怎么变intel模式视图 : nano 或者vim ~/.gdbinit
在该文件里编辑这样一句set disassembly-flavor intel
把amd64格式改成intel,更方便查看

7.break *地址或者函数名等高级用法 : b *main ; b *0x000055555555527a

8.查看断点 :info break #偷懒写法i b

9.体会一下步入、步过、步出、步止
先让程序继续执行:countinue #偷懒ing: c

然后老套路disas $rip,可以明显看到执行到我们刚刚break的0x000055555555527a停下
ni-->步入:单步执行,可以看现在运行到0x0000555555555281

si-->步入:进入函数内部执行,可以看到直接进入到了puts函数内部执行

disas $rip查看一下寄存器,发现确实是在puts函数内部执行了

**一步执行完成函数内部:finish **,避免多次ni重复麻烦

10.让断点失效或直接删除:disable / delete


可见Enb那一栏的b 4断点已经失效
重新生效:enable b 4

查看断点,删除断点

11.字长--内存单位


byte 8字长,以此类推
- 内存单位总结
| 字符 | 单位大小 | 说明 |
|---|---|---|
b |
1字节 | 字节(byte) |
h |
2字节 | 半字(halfword) |
w |
4字节 | 字(word) |
g |
8字节 | 巨字(giant) |
12.重要指令-->X(examine)
- 查看字长 与disas不同的是查询从某个 寄存器/地址/函数 开始的,disas是查看全部
以下是对比图


- X指令不同格式用法和输出不同:实际值只是转换了格式,本质不变,方便阅读
**x/20i** - "把内存当成指令来看"(反汇编)
**x/20b** - "把内存当成原始字节来看"(机器码)
**x/20g** - "把内存当成8字节大整数来看"(64位数据)
- 想看懂程序逻辑 → 用
i(指令) - 想查看原始数据 → 用
b(字节) - 想分析大整数或指针 → 用
g(巨字) - 想查看字符串 → 用
s(字符串)

13.打印指令 print $rbp 偷懒写法:p $rbp

14.set指令用法

如果al=0x61就执行下一步lea
- 目前观察函数的运行逻辑让其走进func函数执行我们的目标位置
我们执行到了main函数中图中位置

让rbp-0x10=0x61或者al=0x61


成功执行到我们目标func拿到bin/sh-->$
浙公网安备 33010602011771号