github pages blog addr
【CTF题解NO.00002】minilCTF 2020 - write up by arttnba3
0x00.绪论
mini-LCTF,前身是makerCTF,是西电校内享誉盛名(?)的CTF,作为菜鸡CTFer也尝试着参加了一手
因为咱主攻PWN的原因,所以这一篇应该只有PWN(
(不过主要还是因为我太菜了XD
注:所有题目在archive.lctf.online上都有部署
0x01.Sign in
Starting Point
点进页面就可以直接获得flag了
0x02.PWN
hello - ret2text + ret2shellcode
证明了我真的是菜鸡的一道pwn题,搞了半天才明白XD
做题环境Manjaro-KDE
首先使用checksec
指令查看保护,可以发现保护基本都是关的,只有Partial RELRO
,那么基本上是可以为所欲为了wwww
win
环境下拖进IDA
进行分析
可以发现在vul函数
存在明显的栈溢出
main
函数中调用了vul
那么程序漏洞很明显了:
- 使用
fgets
读入最大为72个字节的字符串,但是只分配给了48字节的空间,存在栈溢出
又有一个可疑的bd
函数,那么第一时间想到**ret2text
**——构造payload跳转到bd
但是很明显,bd
函数基本是是空的(悲)
然后我就在这里卡了半天,证明我真的菜XD,感谢cor1e大佬的耐心解答
那么我们该如何利用这个bd
呢?
可以看到在bd
中存在操作jmp rsp
,那么其实我们可以利用这个指令跳转回栈上,执行我们放在栈上的shellcode
也就是说这其实是一道ret2shellcode的题
ret2text执行过程:
-
rsp永远指向栈顶
-
当我们用常规的ret2text构造48字节字符串+8字节rbp+8字节bd函数地址(覆盖掉原返回地址)的payload时,rsp指向的其实是bd函数地址的位置
-
ret指令进入bd后弹出bd地址,rsp指向栈内储存的rbp的值(预期内)
-
bd函数将rbp再push入栈中,此时rsp再加8,指向被新压入的rbp(预期内)
-
bd函数执行rsp上的指令
那么我们就可以在rsp预期内指向的地址上覆盖上我们的shellcode使其被执行
接下来就是ret2shellcode的:
ret2shellcode修正过程:
-
在rsp最终指向的地址放上我们待执行的shellcode
-
由于长度不够,我们可以把getshell的shellcode放在读入的字符串的最开始的地方,再通过汇编指令进行跳转
-
在rsp所指向的位置覆盖上shellcode,改变rsp的值使其指向getshell的shellcode并再次进行跳转,完成getshell
那么payload就很容易构造出来了:
注:第一次盲打payload居然错了,我还是太菜了Or2
注2:其实可以不需要跳转到bd,直接跳转到
context.arch = 'amd64'
sc1 = asm(shellcraft.sh())
sc2 = asm('sub rsp,56')//48字节的字符串+8字节的rbp,跳回开头
sc3 = asm('jmp rsp')
elf = ELF('hello')
payload = sc1 + b'a'*(56-len(sc1)) + p64(elf.symbols['bd']) + sc2 + sc3
高阶解法:栈迁移
假如说这道题并不存在供我们进行跳转的bd函数
,给我们的溢出长度就只有72-48=24个字节,但是仅仅是asm(sellcraft.sh())
就已经需要48个字节,那么我们该如何通过仅仅24字节的溢出完成pwn呢
答案是使用栈迁移
技术
注:赛期因为各种ddl导致只解出了这一道题XD 后面的题解是在赛后写的 部分参照了Lunatic和eqqie师傅的题解
ezsc - Alphanumeric Shellcode
首先是惯例的checksec
四舍五入保护全关,我们有极大的操作空间
程序分析
拖入IDA进行分析
v7是一个函数指针,并被分配到了0x1000
大小的内存空间
程序会从输入流逐字节读入最大4096个字节写入到v7所指向的空间上,且限制输入仅能为字母或数字否则终止输入
输入结束后尝试执行函数指针v7
那么我们直接输入一段shellcode即可getshell
但是限制了输入只能是数字和字母,常见的shellcode都包含一些其他字符
于是只好百度XD
得知一种叫做Alphanumeric Shellcode的东西www
Alphanumeric Shelllcode
具体参见:https://www.freebuf.com/articles/system/232280.html
首先从GitHub上随便找一个轮子
$ git clone https://github.com/TaQini/alpha3.git
然后编写生成我们的shellcode的脚本
# sc.py
from pwn import *
context.arch='amd64'
sc = shellcraft.sh()
print asm(sc)
输出重定向至文本
$ python sc.py > sc
使用轮子生成alphanumeric shellcode
$ python ./ALPHA3.py x64 ascii mixedcase rax --input="sc"
Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a071N00
连接服务端,发送我们的alphanumeric shellcode,成功getshell
得到flag
moectf{Y0u_kn3w_tH3_a1ph4num3r1C_she1lc0de!}
easycpp - Use After Free
首先是惯例的checksec
我们可以看到,和前面几题不同的是这个程序是一个32位的程序
说实话我也不知道为什么画风突变变成32位XD
程序分析
拖入IDA进行分析
用到了一个名为B
的类,我们先看看这个类都有些啥
IDA中类B有一个构造函数B()
和一个print()
函数
类B的构造函数如下:
类B的构造函数首先调用了类A的构造函数,然后将变量_vptr_A设置为一个函数指针
我们不难看到在0x80489E4位置上我们还需要再跳转一次到0x80488F2的位置,这个位置上有一个B::print()
函数,故可知这是一个二级函数指针
我们再来看看类A的构造函数,如下:
类A的构造函数也是将变量_vptr_A
设置为一个函数指针
位于off_80489F0上的函数为A::print()
接下来是main函数的简单分析:
v3、v4都是一个类型为类 B
的指针
首先setvbuf()
函数将程序设置为无缓冲输入
之后创建了一个类B的实例并将地址给到指针v3和v4
之后将该实例内的变量_vptr_A
的值设为0
之后使用delete释放掉之前分配给v3、v4的内存
之后从标准输入流读入1024个字节到buf(或许有操作空间?)
之后调用strbuf()
函数重新分配一段内存空间