程序和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架构:

  1. 通用寄存器(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-->$

posted on 2026-05-12 13:59  shuyaoyyy  阅读(4)  评论(0)    收藏  举报