2025-11-21-AT&T

2025-11-21-AT&T

十一月 21, 2025

学习 AT&T 为了 pwn

由于 objdump 反汇编的二进制程序使用 AT&T语法,我必须好好学习

此汇编语法有如下特点

  • 操作数顺序:源 在前 目的 在后
    • mov %rsp, %sbp 表示把rsp的值移动到rbp
  • 寄存器以 % 为前缀, 立即数以 $ 前缀
  • 函数调用时,参数通过寄存器传递:
    • rdi 第一个
    • rsi 第二个
    • rdx 第三个
  • 常见指令:
    • 栈操作:pushpop
    • 数据移动:mov
    • 函数调用:call
    • 返回:ret

概念:

  • 函数序言:在函数开始时执行,用于设置堆栈帧(stack frame)。通常包括保存旧的基指针(%rbp)、设置新的基指针(指向当前堆栈顶部),以及可选的堆栈空间分配。这确保了函数在执行期间有独立的局部变量空间和调用上下文。
  • 函数尾声:在函数结束时执行,用于清理堆栈帧并返回调用者。通常包括恢复旧的基指针、释放堆栈空间(如果有分配),并执行返回指令。

例题:warmup_csaw_2016

1
2
3
4
5
6
40060d:       55                      push   %rbp
40060e: 48 89 e5 mov %rsp,%rbp
400611: bf 34 07 40 00 mov $0x400734,%edi
400616: e8 b5 fe ff ff call 4004d0 <system@plt>
40061b: 5d pop %rbp
40061c: c3 ret

这个代码是一个独立的函数

有标准的 prologue 和 epilogue

  • 函数序言:
    • push %rbp 将旧的基指针压入堆栈,保存调用函数的堆栈帧基地址。
    • mov %rsp,%rbp 将堆栈指针的值移动到基指针 堆栈用于保存上下文
  • 尾声:
    • pop %rbp 从堆栈中弹出之前保存的旧基指针值,恢复%rbp 到调用函数的状态
    • ret 从函数返回,将控制权交还给调用者, 并可能清理堆栈

代码:

  • pusb %rbp 旧基地址入栈
  • mov %rsp,%rbp 堆指针数值>基指针
  • mov $0x400734,%edi 将立即数 0x400734 移动到edi寄存器,x86-64中,edi用于传递函数的第一个参数,这里准备调用system函数。0x400734 很可能是一个字符串的地址。 二进制中,字符串常存放在制度数据段(.rodata) 可推特 0x400734只指向一个系统命令
  • call 4004d0 <system@plt> 通过 过程链接表 (PLT) 调用system 函数,system 函数会执行edi 中的字符串作为系统命令
  • pop %rbp 从栈中弹出旧基地址,恢复rbp的值
  • ret 从函数返回,控制流回到调用者

1
2
3
4
5
6
40061d:       55                      push   %rbp
40061e: 48 89 e5 mov %rsp,%rbp
400621: 48 83 c4 80 add $0xffffffffffffff80,%rsp
400625: ba 0a 00 00 00 mov $0xa,%edx
40062a: be 41 07 40 00 mov $0x400741,%esi
...
  • push %rbp , move %rsp, %rbp 建立新栈帧
  • add $0xffffffffffffff80, %rsp
    • 0x0xffffffffffffff80 在补码表示中是 -128,等价于 sub $0x80, %rsp 可能用于局部变量或缓冲区
    • mov $0xa, $edx 将edx设置为10 edx 通常用于第三个参数或长度值
    • move $0x400741, $esi:将esi 设置为0x400741 可能是另一个字符串地址

1
2
3
4
5
6
7
8
9
10
11
12
400651:       ba 0d 06 40 00          mov    $0x40060d,%edx
400656: be 51 07 40 00 mov $0x400751,%esi
40065b: 48 89 c7 mov %rax,%rdi
40065e: b8 00 00 00 00 mov $0x0,%eax
400663: e8 a8 fe ff ff call 400510 <sprintf@plt>
400668: 48 8d 45 80 lea -0x80(%rbp),%rax
40066c: ba 09 00 00 00 mov $0x9,%edx
400671: 48 89 c6 mov %rax,%rsi
400674: bf 01 00 00 00 mov $0x1,%edi
400679: e8 42 fe ff ff call 4004c0 <write@plt>
40067e: ba 01 00 00 00 mov $0x1,%edx

  • mov $0x40060d,%edx 将 edx设置为 0x40060d

  • mov $0x400751,%esi 将esi设置为 0x400751 AI说可能是一个格式化字符串地址 例如 %s%p

  • mov %rax,%rdi 把 rdi的值设置成rax的值,这里可能指向一个缓冲区(例如 栈上的空间) rdi是 sprintf的第一个参数

  • mov %0x0,%eax 把eax清零了

    • 在x86-64中,这常用于可变参数函数(如sprintf),表示没有浮点参数
  • call 400510 <sprintf@plt> 通过过程链接表调用 sprintf函数,参数是

    • rdi :目标缓冲区 地址来自rax
    • rsi :格式化字符串 0x400751
    • rdx:可变参数 也就是 0x40060d

所以,这行代码可能将地址0x40060d格式化为字符串并存储到缓冲区

如果格式字符串是"%p",它会将地址转换为字符串表示。

  • lea -0x80(%rbp), %rax 加载有效地址,计算 rbp - 0x80 到 rax

    • 这确认了缓冲区在栈上的位置
  • mov $0x9, %edx 把edx设置为9

  • mov %rax, %rsi 将 rax移动到 rsi , rsi是write的第二个参数(数据缓冲区)

  • mov $0x1, %rsi 将 edi设置为1,这是文件描述符 (1表示标准输出stdout)

  • call 4004c0 <write@plt> 调用 write函数 向stdout输出9字节的数据从缓冲区

  • mov $0x1, %edx 把edx设置成1 可能用于后续操作

总结这部分:代码使用sprintf将函数地址0x40060d格式化为字符串,存储到栈缓冲区,然后通过write输出前9字节到屏幕。这可能是为了泄漏地址(在CTF中常见),帮助攻击者计算偏移或绕过ASLR。

不仅是pwn 想打好二进制安全 汇编肯定要学的多多的,而且要多学几种,例如 nasm,还有2024 中科大比赛的时候出现的uxntal ,这个指令集我就不会。

所以你们到底怎么学的啊怎么什么都会!