RCTF Crypto 100 Decode The File

File: cip_d0283b2c5b4b87423e350f8640a0001e
MD5: d0283b2c5b4b87423e350f8640a0001e
SHA256: 1b13fdec1c3a0da404ad53d4f9130f84ba5f3d7708650f52fb328bb7abf65ba8

If you open the file with a text editor, you can see the following content:

content1

Obviously, the data here is encoded by Base64 algorithm, let’s decode it:

content2

The above picture shows the decoded content, by searching the keyword in Google we can find the source:

https://github.com/n0fate/chainbreaker/blob/master/pyDes.py

Compared the decoded file with the file downloaded from above link, we could not find any additional information being added to the decoded file.

So there must be some secrets being hidden into the Base64 strings, but what are they? Let’s re-encode the content with a standard Base64 algorithm to see if there are any differences:

cmp1

What can you find here? Yes, there are many strings that only different in the last byte before the “=”.

If you are familiar with Base64, I think you can easily figure out the reason. As we know, for Base64 algorithm, the original data will be split into groups of 3 bytes, and if the last group only contains 1 or 2 bytes, it will add some padding to the end and use 1 or 2 “=” to indicate how many original bytes are here in the last group. Here is an example of 1 byte in the last group:

base64

The 4 paddings here actually will be ignored by the decode routine, that is to say, we can put any bits here, what a good place to hide information!

Understand this, there will be no difficulties to solve this challenge, the following script is what I use to extract the hidden information:

import base64
import string

def tobin(data):
    b64table = string.ascii_uppercase + string.ascii_lowercase + string.digits + '+/'
    index = b64table.find(data)
    return format(index, '06b')

def toStr(bin):
    binlen = len(bin)
    out = ''
    for i in range(0, binlen, 8):
        out += chr(int(bin[i:i+8], 2))
    return out

out = ''
for line in open('cip_d0283b2c5b4b87423e350f8640a0001e', 'rb'):
    line = line.strip()
    if line.strip()[-2:] == '==':
        binstr = tobin(line[-3:-2])
        out += binstr[-4:]
        print binstr[-4:]
    elif line.strip()[-1:] == '=':
        binstr = tobin(line[-2:-1])
        out += binstr[-2:]
        print binstr[-2:]

print out
print toStr(out)

Flag: ROIS{base_GA_caN_b3_d1ffeR3nT}

Posted in CTF | Tagged , , | Comments Off on RCTF Crypto 100 Decode The File

RCTF Reverse 300 Creack Me

Name: crackMe_aafb0addeb58dece1fcf631a183c2b20
MD5: AAFB0ADDEB58DECE1FCF631A183C2B20
SHA256: 3091F5DF9D1D4E470B36DD8AFBBFEB7F03A5398B3F8846B892425F4BCD890E20

The file of this challenge is a Windows Portable Executable packed with UPX and it can be unpacked with the UPX utility:

upx_upack

The unpacked file introduced some simple anti mechanisms, the first one is located at address 0x0040146E:

disass_1

The code above will skip the next 2 opcode after this function call. If you did not notice this, you may get confused about the disassembly code.

And the second one is location at address 0x00401468:

disass_2

This function is used to call the API that passed as an argument. Using this approach to call an API will make some disassembler like IDA unable to recognize the parameters of that API automatically. However, as you can see in above screenshot, it is still very easy to figure out which API will be called through the disassembly code.

It is not difficult to under stand the functionality of this executable, in short, it will decyprt another Portable Executable file from its append data and then execute it in the memory. The decryption algorithm is to XOR each byte with 0x07.

The decrypted PE file also packed with UPX, and it contains the core verification logic of this challenge.

Before we step into the details of the verification logic, there is another thing need to be mentioned: the decrypted file introduced another anti mechanism which will replace the code block that already been executed and no longer needed with random generated data. This is usually seen in malware to hide itself from memory scan.

The decrypted file will read the input data, and then convert it to a hex-like string (sub_401020), the convert rule is:

For each byte of the input data:

(1) If the highest 4 bit is less than or equal to 0x09, add 0x30 to it, otherwise, add it with 0x57. Store the result as a new byte.

(2) If the lowest 4 bit is less than or equal to 0x09, add 0x30 to it, otherwise, add it with 0x57. Store the result as a new byte.

Next, it will encode the hex-like string (sub_401080) with following rule:

(1) Scan the hex-like string for duplicated sub-strings compared with the beginning of the hex-like string.

(2) If no duplicated sub-string is found, each byte will be XORed with 0x18.

(3) If a duplicated sub-string is found, the first byte of the duplicated sub-string will be XORed with 0x19 (0x18+1) and the second byte of the duplicated sub-string will be XORed with 0x1A (0x18+2), and so no. Other bytes in the string will be XORed with 0x18.

For example, for string “ABCDEFABCGHI”, there is a duplicated sub-string “ABC”, so the second “A” in this string will XOR with 0x19, the second “B” in this string will XOR with 0x1A and the second “C” will XOR with 0x1B. And other bytes in this string will XOR with 0x18.

After encode the hex-like string, the encode result will be converted once again to a new hex-like string, and then it will generate a table (see the code from address 0x00401391 to 0x00401435) and change the byte order of the string (sub_4011E0) based on the table generated before.

Finally, it will compare the result from above steps with following string:

22722272222227272222727a2222222222272222272222222222cfdceeeebb9fdbcdbbedfdede7ce9bebe0bb1e2ceab9e2bbbdecf9d8

Now the logic become clear:
FinalDate = Reorder(ToHex(Encode(ToHex(OriginalData))))

In order to get the original input, the first thing we should do is to reverse the order of the final data.

So how? Let’s take a look at the function which reorder the data:

disass_3

By entering some test data into this program we can easily found that if the input data has a same length, the Table1 in above screenshot will keep the same. And the Table2 here contains the second hex-like string described before. The gIndex here is an index value begin from 0.

Since the ToHex() function will double the length of the string and there are two ToHex() function calls, so the length of the original input should be len(FinalDate) / 4 = 27.

Now that we have the length of the input data, we can let the program itself to calculate the Table1 for us, the following immunity debugger script can help with this work:

import immlib
import getopt
import immutils
from immutils import *
imm = immlib.Debugger()

def main(args):
    imm.setBreakpoint(imm.getAddress("GetCommandLineA"))
    imm.run()
    imm.run()
    imm.run()
    imm.setBreakpoint(0x00401204)
    imm.setBreakpoint(0x00401448)
    while True:
        imm.run()
        regs = imm.getRegs()
        esi = regs['ESI']
        imm.log(" ESI : %08x" % (esi))
        open('d:\work\esi.txt', 'ab').write(hex(esi)+ '\r\n')
        if regs['EIP'] == 0x00401448:
            break

After we get the Table1 we can reverse the final data, and then UnHex and Decode it to get the original input. All the works are can be done by the following Python script:

def convert(data):

    out = ''
    datalen = len(data)
    
    for i in range(0, datalen, 2):
        tmp = 0
        if ord(data[i]) <= 0x39:
            tmp = ord(data[i]) - 0x30
        else:
            tmp = ord(data[i]) - 0x57
        tmp = tmp <<4
        if ord(data[i+1]) <= 0x39:
            tmp |= ord(data[i+1]) - 0x30
        else:
            tmp |= ord(data[i+1]) - 0x57
        out += chr(tmp & 0xff)

    return out

enc = '22722272222227272222727a2222222222272222272222222222cfdceeeebb9fdbcdbbedfdede7ce9bebe0bb1e2ceab9e2bbbdecf9d8'
table = [0x4f8, 0x258, 0x108, 0x318, 0x2b8, 0x3d8, 0x378, 0x498, 0x438, 0x60, 0x348, 0x168, 0x4c8, 0x408,
         0x138, 0x1c8, 0x198, 0x228, 0x1f8, 0x2e8, 0x288, 0x468, 0x3a8, 0xc, 0x420, 0x180, 0x4e0, 0x90,
         0x240, 0x1e0, 0x300, 0x2a0, 0x3c0, 0x360, 0x480, 0x78, 0x270, 0xc0, 0x3f0, 0x330, 0x4b0, 0xa8,
         0x450, 0xf0, 0xd8, 0x150, 0x120, 0x210, 0x1b0, 0x390, 0x2d0, 0x0, 0x48c, 0x1ec, 0x9c, 0x2ac,
         0x24c, 0x36c, 0x30c, 0x42c, 0x3cc, 0x4ec, 0x24, 0x2dc, 0xfc, 0x45c, 0x39c, 0xcc, 0x4bc, 0x15c,
         0x12c, 0x1bc, 0x18c, 0x27c, 0x21c, 0x3fc, 0x33c, 0x18, 0x3b4, 0x114, 0x474, 0x3c, 0x1d4, 0x174,
         0x294, 0x234, 0x354, 0x2f4, 0x4d4, 0x414, 0x30, 0x204, 0x54, 0x384, 0x2c4, 0x504, 0x444, 0x48,
         0x3e4, 0x84, 0x6c, 0xe4, 0xb4, 0x1a4, 0x144, 0x324, 0x264, 0x4a4]

list1 = list(table)
i = 0
for index in table:
    list1[index/12] = enc[i]
    i += 1
print ''.join(list1)

out1 = convert(''.join(list1))
print out1

out2 = ''
out3 = ''
out4 = ''
out5 = ''
i = 0
for ch in out1:
    out2 += chr(ord(ch) ^ 0x18)
    
    if i % 2 == 0:
        out3 += chr(ord(ch) ^ 0x19)
    else:
        out3 += chr(ord(ch) ^ 0x18)
        
    if i % 2 == 0:
        out4 += chr(ord(ch) ^ 0x18)
    else:
        out4 += chr(ord(ch) ^ 0x19)

    if i % 2 == 0:
        out5 += chr(ord(ch) ^ 0x19)
    else:
        out5 += chr(ord(ch) ^ 0x1A)
    i += 1
print out2
print out2.encode('hex')

print convert(out2)
print convert(out3)
print convert(out4)
print convert(out5)

Please note that since we could not know if there is any duplicated sub-string after the first ToHex() function, so we may need to do some “brute-force” here: we can assume there are one byte long duplicated sub-strings and two bytes long duplicated sub-strings, and we can calculate the Decode result for the two situations respectively.

From the output of above script we can find the correct combination of the flag:

output1

Flag: RCTF{*&*_U_g3t_the_CrackM3_f1@9!}

Posted in CTF | Tagged , , | Comments Off on RCTF Reverse 300 Creack Me

FLARE On Challenge (2015) #11

This is the final challenge and it is said to be more difficult than the previous challenges. The file of this challenge named CryptoGraph, which is a 32 bit Windows Portable Executable file.

Let’s analyze it in IDA at first. The flow of the main() function looks like below:
ch11_disass1

The main() function of this program does some simple works: it creates a file named secret.jpg, then checks if the number of the arguments is more than 1, if so it will convert the second argument to an DWORD and decrypt some data into secret.jpg using this DWORD as a “key”. Since the second argument is the only input used to decrypt the jpg file, I call it Masterkey. And if the number of the arguments equals to or less than 1, it will print out the following message:
The number of parameters passed in is incorrect.

The function decrypts the jpg file is located at address 0x00401910 (shown as DecryptMain() in above picture), this function takes two parameters passed through register edx and ecx, they are the Masterkey and the file handle of the secret.jpg respectively.

At the beginning of function DecryptMain(), it loads and verifies the size of two resources whose id is 120 and 121. The picture below shows a piece of code that loads the resource 120 and verifies if it size is larger than 0x30 bytes:
ch11_disass2

After loads the two resources, it will initialize a class which I named it as CryptoInfo. The structure of this class is shown as below:
ch11_struc1

The virtual table (vtblCryptoInfo) of this class contains two functions GenRandom() and IsInit():
ch11_vtbl

The hProv of this class is a handle of the Cryptographic Service Provider (CSP) obtained by calling API CryptAcquireContextW(), and the KeyFile and TotalRound will be described later in this article.

Next, a function located at address 0x00401A81 (the CalcDecryptKey() function in the picture below) will be used to calculate the KeyBlocks (will describe later), and function located at address 0x00401A99 (the DecryptImage() function in the picture below) will be used to decrypt the final data and save it to the jpg file:
ch11_disass3

The CalcDecryptKey() function takes three parameters: the MasterKey, the SecondaryKey and the resource 121. The SecondaryKey is calculated from resource 120 by XORing each 16 byte blocks and it will be used to decrypt the first KeyBlock (will describe later) in resource 121. The resource 121 contains a list of keys which will be used to decrypt the final data, so I call it KeyFile. The KeyFile has the following structure:
ch11_struc2

The Magic field in the KeyFile structure is an ASCII string “FLARE-ON”. The Round, RC5Round and IV are used by the decryption algorithms of this program, and each of them will be described in details later. The MD5 is the hash checksum of the KeyBlocks, and the KeyBlocks is an array of a structure named KeyBlock which contains the key to decrypt the final data.

In the CalcDecryptKey() function, the KeyFile is verified by following steps at first:
1. If there is a magic string “FLARE-ON” at the beginning of the KeyFile;
2. If the MD5 hash of the KeyBlocks (from offset 0x30 to offset 0x630) matches the MD5 hash (at offset 0x20) of the KeyFile.

After verifying the KeyFile, a RC5Key is calculated based on the SecondaryKey and the 16 bytes initiate vector (IV) located at offset 0x10 of the KeyFile. And the MasterKey will serve as the first byte of the IV, which indicates that the MasterKey must be a value between 0 and 255.

This program uses an algorithm which I am not familiar with to calculate the RC5key, I have translated the algorithm into following Python code:

def generate_key(key, iv):
    buffer1 = key + '\x00' * 48
    buffer2 = key + '\x00' * 48

    buffer1 = str(bytearray((ord(ch) ^ 0x36) for ch in buffer1))
    buffer2 = str(bytearray((ord(ch) ^ 0x5c) for ch in buffer2))
    
    iv_md5 = calc_md5_ex(buffer1, iv)

    buffer2 += iv_md5
    key = calc_md5(buffer2)

    return key


def generate_rc5key(secondarykey, iv, round):
    rc5key = ''

    secondarykey_md5 = calc_md5(secondarykey)
    rc5key = generate_key(secondarykey_md5, iv + '\x00\x00\x00\x01')

    for i in range(0, round - 1):
        tmpkey = generate_key(secondarykey_md5, rc5key)
        rc5key = str(bytearray((ord(tmpkey[i]) ^ ord(rc5key[i])) for i in range(0, 16)))
        
    return rc5key

The function generate_rc5key() in the script shows how this program generate the RC5Key, the parameter secondarykey has been described before — it is calculated from resource 120 by XORing each 16 byte blocks; the iv comes from offset 0x10 of the KeyFile structure and the round comes from offset 0x08 of the KeyFile structure.

Next, the RC5Key is used to decrypt the first KeyBlock in the KeyFile (at offset 0x30) with a slightly modified version of RC5 algorithm, this algorithm will XOR the first block (note: one block is 8 bytes) of the original RC5 decrypted data with an index value and XOR the followed blocks of the RC5 decrypted result with the encrypted data. The following Python code imitates how this algorithm works:

from Crypto.Cipher import RC5

def rc5_decrypt(key, data, round, index):
    rc5cipher = RC5.new(rc5key, mode = RC5.MODE_ECB, rounds = round)
    rc5_data = rc5cipher.decrypt(data)

    block1 = struct.pack('<I', index) * 2
    block2 = rc5_data[0x00:0x08]
    dec_data = str(bytearray((ord(block1[x]) ^ ord(block2[x])) for x in range(0, 0x08)))

    block1 = data[0x00:0x28]
    block2 = rc5_data[0x08:0x30]
    dec_data += str(bytearray((ord(block1[x]) ^ ord(block2[x])) for x in range(0, 0x28)))

    return dec_data

The decrypted KeyBlock has the following structure:
ch11_struc3

The Index is a numeric value count from 0. The Round and IV are used to decrypt the next KeyBlock. The Key will be used to decrypt the final data. And the MD5 is the hash checksum of the first 0x20 bytes of the KeyBlock structure.

After decrypted the first KeyBlock, we can continue to decrypt the second KeyBlock by using the MD5 hash of the Key in the first KeyBlock as NewSecondaryKey and the IV in the first KeyBlock as NewIV. The NewRound is calculated based on the old round value and the Round field in the first KeyBlock. Then sample algorithm (see the generate_rc5key() function described before) is used to calculate a NewRC5Key and the second KeyBlock is decrypted with this NewRC5Key.

Repeat the above steps we can theoretically decrypt all the 32 KeyBlocks and once we have all the KeyBlocks, we may decrypt the jpg file.

So our task become clearly: we need to find out the correct MasterKey so that we can decrypt all the KeyBlocks. Till now we have the following information:
1. The Masterkey is an integer between 0 and 255 and it serves as the first byte of the IV which is used to calculate the RC5Key.
2. The RC5Key is used to decrypt the first KeyBlock.
3. The first KeyBlock has an Index and a MD5 checksum which can be used to verify itself.

With above knowledge, we can do a brute force attack on the Masterkey by checking if the Index field of the first decrypted KeyBlock equals to 0.

This could be done with many approaches. One easiest way is to modify the first instruction of the code that only can be executed when the Index of the first KeyBlock is correct to a software breakpoint (int 3, or 0xCC), and call the modified program with argument from 0 to 255 to see which one will crash the program.

But here I use a WinDbg script to do the same thing, the script is as below:

import pykd
import time

g_index = 0

def bp_callback(event):
    global g_index
    addr = pykd.reg("esi")
    out = ''
    for b in pykd.loadBytes(addr, 16):
        out += chr(b)
    open('D:\\Work\\out.txt', 'ab').write(str(g_index) + ': ' + out.encode('hex')+'\r\n')
    print out.encode('hex')
    return True


for i in range(200, 256):
    g_index = i
    pykd.startProcess('D:\\Work\\CryptoGraph.exe ' + str(i))
    pykd.setBp(0x004016D4, bp_callback)
    pykd.go()
    time.sleep(1)
    pykd.dbgCommand('bc *')
    pykd.killProcess

The script does the following tasks:
1. Run process CryptoGraph.exe with different augments (from 0 to 255).
2. Set a breakpoint at address 0x004016D4, right after the program decrypted the first KeyBlock. If the program stopped at this address, the register esi will point to the decrypted data.
3. Dump the decrypted data into a file.

Executed this script in WinDbg and from the output file we can easily find the correct MasterKey:
ch11_out1

Now that we have the MasterKey, we can decrypt all the KeyBlocks and then decrypt the final data to get the email address.

Unfortunately, things not that easy. As we have described before, the NewRound value used the calculated the next RC5Key is based on the old round value and the Round filed in the previous KeyBlock, however, the increase of this value may lead to several hours or even several days to decrypt all the KeyBlocks, because it determines how many loops we should go through.

With the belief that the challenge should not aim at wasting our time, I moved on to the DecryptImage() function located at 0x00401A99.

The functionality of DecryptImage() is straightforward: it uses the Key in one of the KeyBlocks to decrypted the FinalRC5Key from resource 122, then it uses the FinalRC5Key to decrypt the resource 124 and save the decrypted data to the secret.jpg. All the decryption algorithms being used are the modified version of the RC5 algorithm we have described before.

Since this function will only use the Key in one of the KeyBlocks, so the question becomes to: which KeyBlock will be chosen? The answer is in function 0x00401B60 which I renamed it to SelectKey(). This function can be described by the following pseudo code:

Regard SecondaryKey as an Integer Array

a1 = SecondaryKey[1] | 0x10
a2 = (Mismatch > 0)
c1 = (CryptoInfo.TotalRound >> CountBitsSet(SecondaryKey[2] ^ 0x31000C01))
c2 = (CryptoInfo.TotalRound >> CountBitsSet(SecondaryKey[3]))

if (c1 == 0) {
    return (a2 + (a1 >> 8)) & 0x0F }
else if (c2 != 0) {
    return (a2 + ((a1 / c2) >> 16)) & 0x0F }
else {
    try {
        raise a1 
    }
    catch (e) {
        a3 = CountBitsSet(e)
        a3 = a3 >> 1 
    }
    return a3
}

The SecondaryKey we have described before, it is calculated from resource 120, so we can easily know that the SecondaryKey[1] = 0x766147E9, the SecondaryKey[2] = 0x86EBD2E6 and the SecondaryKey[3] = 0x7EDFEBFB. The Mismatch is a counter indicates how many KeyBlocks cannot bypass the MD5 verification, so it should be 0 if all the KeyBlocks are correctly decrypted. The function CountBitsSet() counts the number of bits set to 1 of a DWORD. As for the CryptoInfo.TotalRound, it is calculated based on the following logic:

CryptoInfo.TotalRound = 0x8000

For (KeyBlockIndex = 1; KeyBlockIndex < 0x20; KeyBlockIndex++) {
    CryptoInfo.TotalRound = CryptoInfo.TotalRound * (KeyBlockIndex >> 4) + (0x10000 << KeyBlockIndex-1)
}

Now let’s move back to the SelectKey() function. You may have noticed that there is a special if…else branch which uses a C++ Exception to calculate the return value, and to let the code run into this branch it need match the following conditions:

(CryptoInfo.TotalRound >> 0x18) != 0 and (CryptoInfo.TotalRound >> 0x1A) == 0

So that the CryptoInfo.TotalRound should have a value between 0x1000000 and 0x4000000, and according to the previous expression used to calculate the CryptoInfo.TotalRound we can know that to make above conditions return true the KeyBlockIndex should either be 0x09 or 0x0A, which means that we may only need to calculate the first 10 or 11 KeyBlocks to decrypt the jpg file! This can save a lot of time.

Firstly let’s try to only decrypt the first 10 KeyBlocks, this could be done by modifying the total number of the KeyBlocks need to be decrypted from 0x20 to 0x0A, this value is located at address 0x004018C0:
ch11_dbg1

Then we can pass the Masterkey 205 to this program and let it run. After a while, I am lucky to get the jpg file which contains the email address:
ch11_ans

Posted in CTF | Tagged , , , | Comments Off on FLARE On Challenge (2015) #11

FLARE On Challenge (2015) #10

This challenge contains a large Windows Portable Executable File which is nearly 3.4 MB. It is usually difficult to reverse engineering such a large file. However, if you have noticed the special resource embedded into this file, things will become easier:
ch10_res

This file is actually a compiled AutoIT program and we can extract the embedded AutoIT script with many tools like exe2aut. With the help of exe2aut I have extracted one AutoIT script and three executable files:

File Name: ioctl.exe
Size: 46,080 bytes
MD5: 205af3831459df9b7fb8d7f66e60884e
SHA1: dfb2dc09eb381f33c456bae0d26cf28d9fc332e0
SHA256: 44473274ab947e3921755ff90411c5c6c70c186f6b9d479f1587bea0fc122940


File Name: challenge-xp.sys
Size: 2,688,640 bytes
MD5: 399a3eeb0a8a2748ec760f8f666a87d0
SHA1: 393f2aefa862701642f566cdaee01a278e2378c0
SHA256: 57b1d7358d6af4e9d683cf004e1bd59ddac6047e3f5f787c57fea8f60eb0a92b


File Name: challenge-7.sys
Size: 2,689,536 bytes
MD5: dade1de693143d9ef28fe7ebe7a7fb22
SHA1: 745ba710cf5d4a8cbb906f84e6096ca1b9a1bae3
SHA256: 59dbf937021c7856bad4155440dbd2e0c5422f06589bb11e5ac0b3300aad629c

Apparently, there are two driver files, one is for Windows XP and the other one is for Windows 7. And the ioctl.exe, from the file name, we can guess that it is used to communicate with the driver.

Let’s see what will the AutoIT script do at first. The AutoIT script contains a series of functions used to manage the service like _startservice(), _stopservice(), _serviceexists(), _servicerunning(), _createservice(), _deleteservice(), and the core logic is shown as below:
ch10_autoit1

Firstly, it will check if the system architecture is x86, if so it will drop a driver file to the system directory based on the OS version, after this it will also drop the ioctl.exe to the system directory. Next, it executes the function dothis() with two arguments, one is a hex string and the other one is an ASCII string “flarebearstare”. If the return value of that function is true, it will execute the dothis() function again with a different hex string. If the second dothis() returns true, it will then execute the third dothis() function. So what does the dothis() function do?
ch10_autoit2

The dothis() function actually receives two arguments: data and key. It will decrypt the data with the key and execute the decrypted data. In the decrypt() function, it will execute an opcode by calling API CallWindowProc() and pass the data and key as arguments.

As we already know that the API CallWindowProc() will be called, we can just set a breakpoint on this API in a debugger and follow the API to execute the opcode, and then get the decryption result. However, if you run this program in a debugger, you may probably receiver this error:
ch10_error

What is going on here? Let’s search for the error string in IDA, and we can find the following piece of code:
ch10_disass1

If we follow the code reference here we can find the root cause:
ch10_disass2

There is an anti-debug check! Ok, to bypass this check we can just patch the “jnz” at 0x00403D5F to “jz”, and then continue the expectation. And finally, our breakpoint get hit in the debugger:
ch10_dbg1

The first arguments pass to the CallWindowProc() contains the address of the opcode, now we can put a breakpoint on the first instruction of the opcode and continue the execution. When the breakpoint on the opcode triggered, we can then execute to the “retn” instruction and here the decryption result can be found from the stack:
ch10_dbg2

Repeat the above process for the other two dothis() function, we can have all the decryption code:

_CreateService("", "challenge", "challenge", @SystemDir & "\challenge.sys", "","", $SERVICE_KERNEL_DRIVER, $SERVICE_DEMAND_START)
_StartService("", "challenge")
ShellExecute(@SystemDir & "\ioctl.exe", "22E0DC")

From those codes we know that the driver dropped by this program is registered as a service named “challenge” and then an IoControlCode 0x22E0DC is send to this driver through ioctl.exe.

Now it is the time to analyze the driver. As only one of the driver will be chosen (based on the OS version), so the two drivers must have same functionality and we only need look at one of them. I chose the one for Windows 7. The Entry Pointer of this driver seems normal, it will create a device named “challenge” and then register the unload routine and the dispatch routines. All the dispatch routines are pointing to a same function located at address 0x0029CD20, and if you look at the Graph Overview of this function, you may see something like this:
ch10_disass3

Terrible, right? In the beginning of this function, it will retrieve the IoControlCode sent to this driver and sub it with 0x22E004, then it will use the result of the subtraction as an index to retrieve another index from a table located at address 0x0029D614, then the second index will be used to retrieve the jump destination address from a jump table located at address 0x0029D480:
ch10_disass4

So what is the first IoControlCode passed to this driver? Yes, it’s 0x22E0DC. So we can calculate the first index: 0x22E0DC – 0x22E004 = 0xD8, and the we can have the second index which is 0x36, and with the second index, we can finally found the jump address which should be 0x0029D180:
ch10_disass5

Here it only calls another function located at 0x0029C1A0, and let’s move to this function. This function also looks terrible:
ch10_disass6

But do not be panic, let’s look at this function in details:
ch10_disass7

The functionality of this function is actually easy to understand, it will test each bit of a input sequence to see if that bit is 0 or 1, and then jump to different branch based on the test result. Basically, if all the bit test except the last one do not jump to the exit branch, we will get what we want. The following IDA Python script can be used to calculate the correct input sequence:

from idaapi import *

def bit_reverse(c):
    out = 0
    for i in range(0, 8):
        out <<= 1
        c1 = c & 0x01
        out |= c1
        c >>= 1
    return out

begin_addr = 0x0029C1C7
out = ''
addr = begin_addr

while Byte(addr) == 0x0f:
    c = 0
    for i in range(0, 8):
        c <<= 1
        if Byte(addr) == 0x0f:
            for j in range(0, 2):
                addr = NextHead(addr)
            if GetMnem(addr) == 'jz':
                c |= 0
            elif GetMnem(addr) == 'jnz':
                c |= 1
            else:
                print hex(addr)
                raise Exception('unknown instruction')
            for j in range(0, 3):
                addr = NextHead(addr)
        else:
            print hex(addr)
            raise Exception('unknown instruction')
    out += chr(bit_reverse(c))

print out

And the output is:
ch10_out1

Ok, it asks us to try another IoControlCode 0x22E068. We can use the same way described before to calculate the jump destination and the function we should care about this time is located at address 0x0002D2E0, another terrible function:
ch10_disass8

There are a lot of branches in this function which makes it difficult for us to understand the logic. But one good thing is that no matter what code path the function will walk through, a call at the end of this function will be executed:
ch10_disass9

This call received three arguments, and a quick analysis on it suggests that it is possibly uses the Tiny Encryption Algorithm (TEA) to decrypt some data. So how do we know what data will be decrypted? or what is the decryption result? I believe the easiest way to answer this is through debugging.

Let’s setup the kernel debugging environment at first since we are going to debug a driver. Here I use two machines, one is my physical machine (running Windows 8) and the other one is a Virtual Machine with Windows 7 installed. There is a wonderful tool named VirtualKD can make things easier. And there are a lot of tutorials online to teach you how to setup a kernel debugging environment, so I will not talk about it in details here, you may refer to the following article if you have not try this:
http://www.hexblog.com/?p=123

Now let’s set a breakpoint on the call at 0x000ADC31 and a breakpoint at the beginning of the dispatch routine at 0x0029CD20, then run the executable file of this challenge in the VM. If nothing goes wrong, we will stop at the dispatch routine like below:
ch10_dbg3

Then we can just modify the IoControlCode to 0x22E068 (or use the ioctl.exe to issue this IoControlCode) and continue, and we will stop at the call we are interested:
ch10_dbg4

However, after executes this function, we get nothing we want, the decrypted data seems meaningless. So what is the problem here? As this call aims at decrypting some data, let’s see what data is being decrypted. The data to be decrypted is passed as the first argument which should be a byte array, and if you look at this argument during debugging, you may be surprised that the array is all zeros when the decryption function is called, that is to say, the data to be decrypted is not initialized! Why?

If you look at the data reference of each byte in that array, you will find that each byte will be assigned a value by a separate function. However, those functions may not get executed when we reach the decryption function and that’s may be the reason why we have an empty array. Knowing this, we can try to initialize the array manually before we execute the decryption function. This can be done by the following IDA Python script:

from idaapi import *

begin_addr = 0xA22A0210
out = ''
addr = begin_addr

for i in range(0, 0x28):

    found = False
    
    for ref in DataRefsTo(addr):
        if GetMnem(ref) == 'mov':
            prev_addr = ref
            for i in range(0, 2):
                prev_addr = PrevHead(prev_addr)
            if GetMnem(prev_addr) == 'mov':
                print hex(addr), GetOpnd(prev_addr, 1)
                PatchByte(addr, GetOperandValue(prev_addr, 1))
            else:
                print hex(addr)
                raise Exception('unknown instruction')
            found = True
            break
        else:
            print hex(addr)
            raise Exception('unknown instruction')

    if found == False:
        print hex(addr)
        break

    addr += 1

And this time the decyption function gives us what we want:
ch10_ans

Posted in CTF | Tagged , , , | Comments Off on FLARE On Challenge (2015) #10

FLARE On Challenge (2015) #9

This challenge contains a Windows Portable Executable file in a very small size (4,608 bytes), typically, this kind of files is written in Assembly Lagrange. If you load this file into IDA, you may notice that the file is badly obfuscated and hard to analyze statically. But if you look at the Strings (Shift + F12), you may find some interesting code located at 0x00401000:
ch9_disass1

Those code looks very similar to challenge #1 and challenge #2, so how about executing this program in a debugger and putting a breakpoint at 0x00401000? Unfortunately, the breakpoint here will not being hit. Eh… how about putting a breakpoint on the API it may calls like WriteFile() or ReadFile()? Badly, it seems cannot work either. But if you are not give up, you will finally find that a breakpoint on GetStdHandle() will eventually interrupt the execution of this program. That’s a good thing! Let’s execute this program step by step after hit the second GetStdHandle(). After several instructions, I am surprised that the program actually calls the API WriteFile():
ch9_dbg1

So why our previous breakpoint on WriteFile() did not work? When I look at the breakpoints lists, I realized that it is because the debugger “play a joke” on me:
ch9_dbg2

The breakpoint here is set on the WriteFile() function from module Kernelbase.dll, however, the WriteFile() function being called at real time comes from module kernel32.dll:
ch9_dbg3

Understand this, now we can put a breakpoint on the ReadFile() function in the kernel32.dll and it will hit as expected. After provides some fake input to the ReadFile() function, I continue executing this program on single steps. And finally, it comes to the interesting part:
ch9_dbg4

If you execute the code from here step by step, you will find that although the code is badly obfuscated, the verification logic is easy to understand: firstly, it will initialize a table on the stack, and retrieve an index from this table, and then it will use this index to retrieve a second index from the same table. Next, it will use the second index to retrieve a byte from that table and XOR it with the relevant byte of our input data. After this, it will use the second index to retrieve another byte from that table and do a left rotation with the XOR result. And finally it will compare the rotate result with a third byte retrieved from that table.

If you cannot understand the algorithm clearly through my description, the following Python script may help:

table  = '\xCC\xA9\x6D\x77\x0A\xF2\x4E\x58\xC1\x8A\x2F\x9F\xD8\x6C\x58\x43'
table += '\xF9\x1D\xC8\x92\x1F\x5C\xC3\x98\xC7\xF8\xD7\x03\xB0\x8A\x3C\xE7'
table += '\x1F\x1A\xCD\x02\x13\xF6\x07\x73\xF2\x33\xC8\xF0\xBA\xC2\xCC\xB8'
table += '\x80\xF0\x48\x77\xF9\x9A\x50\x55\xDC\x01\xE1\xEB\x16\x5A\x6B\xC6'
table += '\xC3\x0E\x8E\x27\x5D\xC3\xF4\x4E\x06\x42\xBC\xF2\x5D\x56\x30\x2E'
table += '\x19\x53\x53\x66\xD2\xF9\x03\xCF\x12\x06\x17\x1A\x0A\x0D\x02\x1E'
table += '\x16\x10\x1F\x09\x23\x0F\x14\x25\x1D\x03\x19\x21\x0B\x28\x13\x00'
table += '\x26\x08\x1B\x0C\x04\x27\x24\x1C\x01\x22\x18\x20\x11\x15\x05\x0E'
table += '\x07\x2A\x2B\x2C\xAC\xBE\xF5\x4E\x75\xBC\x87\xB8\x16\x67\x6B\x5C'
table += '\xFA\xF1\xF9\x93\xF2\xD4\xF8\x23\xB9\xC8\x11\x7E\xCA\x56\xD6\x1B'
table += '\x0A\xDA\x6E\xB5\x01\xC1\x55\x9B\xB8\x61\xCE\x4C\x6E\xBC\xEE\x08'
table += '\xF4\x64\x15\x8C\x65\x60\xE2\x1B\x8E\x40\x4A\x34\x45\xE3\x96\x4C'
table += '\xEB\xC9\x0D\xEB\x8E\x67\x26\xEF\x32\x46\xB5\xBD\xB2\xE6\x9F\xFF'
table += '\xF1\x74\xEF\xA8\x46\xC4\x60\x39\x65\x31\xAB\x9F\x01\x00\x00\x00'

def ror(n, c):
    c = c % 8
    t1 = (n >> c) & 0xff
    t2 = (n << (8-c)) & 0xff
    return t1 | t2

out = ''
for i in range(0, 41):
    index1 = ord(table[i+0x58])
    index2 = ord(table[index1+0x58])
    ch1 = ord(table[index2+0xB0])
    ch2 = ord(table[index2+0x84])
    ch3 = ord(table[index2+0x2C])
    out += chr(ror(ch3, ch2) ^ ch1)

print out

Run this script we can get the following result:
ch9_ans

Posted in CTF | Tagged , , , | Comments Off on FLARE On Challenge (2015) #9

FLARE On Challenge (2015) #8

When you open this challenge directly in IDA, you may as disappointed as me, there is only a few code available at the Entry Point and they seems do nothing useful:
ch8_disass1

However, if you open this file in a text editor, you may find the secret: there are a lot of Base64-like strings inside this file:
ch8_text

After decodes those strings, you can have a PNG file looks like below:
ch8_png

The first time I saw this picture, I believe that some information must be hidden into it. After Googling, I found a wonderful tool named Stegsolve which can help with analyzing the hidden information in a picture.

If you are not familiar with Steganography, I recommend you to read the following Wiki page:
https://en.wikipedia.org/wiki/Steganography

One famous approach to hide information into a picture is to store each bit of the information into the least significant bit (LSB) of the 8-bit color value of each pixel (24-bit bitmap). So let’s use the Stegsolve to extract this information, and the extracted data looks like below:
ch8_extract

At the first glance, there seems no useful inforamtion. However, if you are very familiar with the Windows Portable Executable file format, you may find some similarities if you compare the extracted binary data with a PE file:
ch8_cmp

Now we can guess that the information hidden in this picture is actually an encrypted PE file and let’s see if we can decrypted it. From the comparison above, we can at least know two things:
1. The file seems encrypted by a byte-by-byte encryption algorithm.
2. Some special bytes like 0x00 and 0xFF seems keep the same after encryption.

Next, let’s assume the first two bytes are the magic string “MZ” and the name of the first section is “.text” so that we can compare those bytes to see how they get encrypted:
ch8_cmpbytes

What’s your finding here? Yes, the ciphertext only reverse the bit order of the plaintext! Now we can write a Python script to decrypt the file:

def bit_reverse(byte):
    out = 0
    for i in range(0, 8):
        out += (byte & 0x01)
        out = (out << 1)
        byte = (byte >> 1)
    out = (out >> 1)
    return out

data = ''
for ch in open('gdssagh.bin', 'rb').read():
    data += chr(bit_reverse(ord(ch)))

open('gdssagh.out.exe', 'wb').write(data)
print 'Result written to file gdssagh.out.exe.'

And run the decrypted file we can get the email address:
ch8_ans

Posted in CTF | Tagged , , , | Comments Off on FLARE On Challenge (2015) #8

FLARE On Challenge (2015) #7

This challenge is a .NET application. There are many tools to decompile a .NET application and here I use the ILSpy.

A quick look at the decompile result I found that this application is probably obfuscated by SmartAssembly :
ch7_decomp1

There is a powerful tool named d4dot which can help with the deobfuscation. The tool is easy to use so I will not talk about more details here. After the deobfuscation the code looks much better and let’s go to the Entry Point function at first:
ch7_decomp2

This function is easy to understand, and the most important logic here is to compare your input with a string stored in a variable named “b”. And the string “b” is made up with two parts that generated by two different functions Class3.smethod_0() and Class3.smethod_3(). The code of Class3.smethod_0() is shown as below:
ch7_decomp3

This function does a simple XOR calculation on the array returned by function Class3.smethod_2() and the second arguments passed to this function. Here is the code of function Class3.smethod_2():
ch7_decomp4

This function is interesting, it returns the byte code of method 100663297 as a byte array. So what is method 100663297? If you are not familiar with .NET file format, I recommend you to read the article here at first:
http://www.codeproject.com/Articles/12585/The-NET-File-Format

In short, the 100663297 (0x06000001) is a .NET token which can be split into two parts, the highest 8 bit (0x06) is an index for a MetaData table, and the rest 24 bits (0x000001) is the index of the item in that table. Following is a list of .NET MetaData tables:
ch7_meta

The index 0x06 here represents the MethodDef table. We can use the CFF explorer to view the .NET tables. With this tool we can find that the first item (0x000001) in the MethodDef table is a function named .cctor, and we can also disassemble this function:
ch7_cff

Now that we have the byte code of the function, and we also know the second augments passed to the Class3.smethod_0() (from the decompiled code), we can easily calculate the first part of the string “b”:

bytecode =(0x72, 0x01, 0x00, 0x00, 0x70, 0x26, 0x2A)
arg2 = (31, 100, 116, 97, 0, 84, 69, 21, 115, 97, 109, 29, 79, 68, 21, 104, 115, 104, 21, 84, 78)

bytecode_len = len(bytecode)
out = ''
i = 0

for b in arg2:
    out += chr(b ^ bytecode[i%bytecode_len])
    i+= 1

print out

The string we want is:
ch7_str1

Ok, now we can move to the second part of string “b”, which is generated by function Class3.smethod_3():
ch7_decomp5

This function also straightforward, it concatenates all the CustomAttributeData as a string and then calculates the MD5 hash of it. The CustomAttributeData can be easily read from the .NET decompiler:
ch7_decomp6

However, this is a problem that we don’t know the correct order to put those CustomAttributeData together as a string. Since there are not too many CustomAttributeData, one possible way is to brute-force all the orders. But I will not solve this challenge in this way.

As we already have the first part of the string “b”, when this program compares the string with our input, the string must present in the memory, so why not just search it in the memory?

With this idea I launched this program in a debugger and let it run directly. It will ask us to enter a password:
ch7_output

We can input any password and continue executing the program until it exits. Now open the memory window and search for the first part of the string in Unicode format:
ch7_search

And finally we will  get what we want in the memory:
ch7_key

Provide the correct password to this program we have the email address:
ch7_ans

Posted in CTF | Tagged , , , | Comments Off on FLARE On Challenge (2015) #7

FLARE On Challenge (2015) #6

This challenge is an Android application. There are a lot of tools can be used to analyze Android application and the JEB Decompiler is my favorite one. Let’s open this challenge in JEB and look at the Manifest file at first:
ch6_manifest

The Manifest gives us many information and the most important are the two Activity: com.flareon.flare.MainActivity and com.flareon.flare.ValidateActivity.

Let’s see what will the MainActivity do:
ch6_decomp1

The functionality looks simple: it reads the input and stores it as an extra data named com.flare_on.flare.MESSAGE, and then it launches the ValidateActivity. So we move to the ValidateActivity:
ch6_decomp2

The ValidateActivity will retrieve the extra data stored in the com.flare_on.flare.MESSAGE, then verify if it is an ASCII string, if so it will pass this string to a function named “validate”, if not it will set the text “No”. So what does the function validate() do? From the keyword “native” in the function definition we can know that this is actually a native function which could exist in a native library. If you decompress the APK file with a decompression tool like 7-zip you may notice that there is a folder named lib and inside this folder there is an interesting file named libvalidate.so, from the file name we can guess that this one should be the file we want. So let’s disassemble it in IDA. From the Entry Point List (Ctrl + E) we can see a function named Java_com_flareon_flare_ValidateActivity_validate, which is interesting:
ch6_disass1

A quick navigate on this function we can find that there are two strings “That’s it!” and “No” present at the end of this function:
ch6_disass2

Those strings confirm that this function is what we need, so the next thing to do is no more than read the disassembly code to understand the functionality of this function. In short, this function will verify if the length of the input string is equal to or less than 46 at first, then it will convert two adjacent bytes of the input string to a 16-bit WORD (e.g. if two adjacent bytes are 0x11 and 0x22, they will be converted to 0x1122), and next, it will calculate the remainder of the WORD by dividing each 16-bit WORD in a table (I call it mod_table) located at address 0x00002214, if the remainder is zero, it will increase the relevant byte in another table (I call it reminder_table) by 1, and use the dividing result as a new DWORD to calculate the remainder until the dividing result equal to or less than 1. And finally it will compare the reminder_table with a hard-coded table (I call it key_table) to see if they are match. The following pseudo code may help you understand the flow:

if (input.length > 46)
    return "No"

result = 0
for (i = 0, table_index = 0; i <= input.length; i += 2, table_index++)
{
    n = (input[i] << 8) | input[i+1]

    j = 0
    while (j < 0xD94)
    {
        while ((n % mod_table[j]) == 0)
        {
            reminder_table[table_index][j] += 1
            n = n / mod_table[j]
            if (n <= 1)
                goto label_next
        }
        j++
    }
label_next:
    if (reminder_table[table_index] == key_table[table_index])
    {
        result += 1
    }
}

if (result == 0x17)
    return "That's it!"

return "No"

Obviously, this algorithm can be easily reversed, the following IDA Python script can be used to calculate the correct input:

from idaapi import *
import struct

mod_table = 0x00002214
key_tables = 0x00005004
key_tables_len = 0x17
key_table_len = 0xD94
out_str = ''

for i in range(0, key_tables_len):
    key_table = Dword(key_tables + i*4)
    out = 1
    for j in range(0, key_table_len):
        index = Word(key_table + j*2)
        if index != 0:
            num = Word(mod_table + j*2)
            out = out * (num ** index)
    out_str += struct.pack('<H', out)[::-1]

print out_str

And the answer is:
ch6_ans

Posted in CTF | Tagged , , , | Comments Off on FLARE On Challenge (2015) #6

FLARE On Challenge (2015) #5

This challenge is an easy one. It contains two files, one is a Windows Portable Executable file and another one is a PCAP file. Let’s look at the PCAP file at first:
ch5_pcap1

In the PCAP file we can see a series of HTTP traffics, each of them only POST 4 characters to a localhost server and the server will response with “1”:
ch5_pcap2

There is no more information we can find from the PCAP file, so let’s move to the executable.

The functionality of the executable file whose name is sender is really simple. Firstly, it will read some data from a file named key.txt:
ch5_disass1

Next, the data read from the key.txt will be encrypted by a function located at address 0x00401250:
ch5_disass2

The encryption algorithm is easy to understand: it adds the string “flarebearstare” to the data read from key.txt byte by byte.

After the encryption, the encrypted data will be encoded by Base64 algorithm with a custom character set:
ch5_disass4

And finally, the data will be split into 4 bytes strings and send out to the server:
ch5_disass5

So our task is easy, just assemble the 4 bytes strings in the PCAP file and then do a reverse calculation on the assembled string, the following Python script can help with the reversing:

import string
import base64

def base64_decode(s):
    table = string.maketrans(
      string.lowercase + string.uppercase + string.digits + "+/",
      string.uppercase + string.lowercase + string.digits + "+/"
    )
    s = s.translate(table)
    return base64.b64decode(s)

enc_data = 'UDYs1D7bNmdE1o3g5ms1V6RrYCVvODJF1DpxKTxAJ9xuZW=='
key_str = 'flarebearstare'
key_len = len(key_str)

b64_str = base64_decode(enc_data)
out_str = ''
i = 0

for ch in b64_str:
    out_str += chr((ord(ch) - ord(key_str[i % key_len])) & 0xff)
    i += 1

print out_str

And the answer is:
ch5_ans

Posted in CTF | Tagged , , , | Comments Off on FLARE On Challenge (2015) #5

FLARE On Challenge (2015) #4

File youPecks is a 32 bit Windows Portable Executable file packed with UPX (according to the section name). And it can be unpacked with the UPX utility:
ch4_upx

When executes the unpacked file, it only prints out a strange expression “2 + 2 = 5”, and then exits:
ch4_output

Both the file structure and the resource show no suspicious, so I move on to the disassembly code in IDA. The APIs calls of this program suggest that it is probably written in C++. And at the beginning of the mian() function, here is a strange logic:
ch4_disass1

From above picture you can see that this program calls the function atoi() to convert a string “5” to an integer and stores it to register esi, and later it compares the esi with number 5 and jumps if it does not match. This conditional jump makes no sense because the esi should always equal to 5! However, if the jump does not take place, the program will just terminate itself. So let’s ignore this logic and just see what will happen if it jumps to the unreachable branch.

Firstly, it will check if the number of the argument is equal to 2:
ch4_disass2

If so, it will convert the second argument to an integer and calculate the MD5 hash of the least significant byte of this integer:
ch4_disass3

Next, a branch of bas64-like strings will be initialized and “appended” to a list. Please notice that the Append() function here (in the picture below) may not correct because I did not analyze it in details, the only thing I saw while debugging is that this function will put those bas64-like strings into an array:
ch4_disass4

After above Initializations, one of the Base64 string will be chosen and decoded, then the decoded data will compare with the MD5 hash calculated before:
ch4_disass5

Once the MD5 hash matches the Base64 decoded data, the program will continue executing, or it will exit. Now that we know the MD5 hash is only calculated based on one byte, we can brute-force all the possible value with following Python script:

import base64
import hashlib

for i in range(0, 256):
    print str(i) + ': ' + base64.b64encode(hashlib.md5(chr(i)).digest())

By observing the output of the script, we can find that those Base64 strings in this program are actually Base64 encoded MD5 hash of byte 0x00 to byte 0x17 (0 to 23), with all the uppercase letters changed to lowercase letters and all the lowercase letters changed to uppercase letters. That is to say, those Base64 strings are encoded with a different character set than the standard Base64 character set.

Now we have only one question left: which Base64 string will be picked out to compare with the hash calculated from our input? If you look back to the previous code, it is easy to answer: it is determined by the time, or more precise, the current hour (and that’s why it only need value from 0 to 23).

Knowing this, the things become easy, we can just patch the “jnz” at address 0x0040147F to “jz” so it can run into the unreachable branch and patch the “jnz” at address 0x00401BBC with “nops” so that no matter the hash matches or not the file can continue executing. Then we can execute this file with different time settings to see what will happen. The following Python code can help with this task:

import sys
import time
import win32api
import datetime
import subprocess

def set_hour(hour):
    time_tuple = ( 2015,    # Year
                      1,    # Month
                      1,    # Day
                      hour, # Hour
                      0,    # Minute
                      0,    # Second
                      0,    # Millisecond
                  )
    
    dayOfWeek = datetime.datetime(*time_tuple).weekday()
    time_tuple = time_tuple[:2] + (dayOfWeek,) + time_tuple[2:]
    win32api.SetSystemTime(*time_tuple)

for i in range(0, 24):
    set_hour(i)
    local_hour = time.localtime().tm_hour
    p = subprocess.Popen(['youPecks_patch.exe', str(local_hour)])
    time.sleep(1)

Unfortunately, things seems not that easy, the output of this script is as below:
ch4_pyout

Obviously, there is nothing useful. So I have to continue with the disassembly code to see what is going on. After compares the MD5 hash, another branch of Base64 strings are initialized and “appended” to another list. Then one of the Base64 strings will be picked out and decoded, and then XORed with the previous Base64 decoded data. Finally, the XOR result will be printed out.

Wait, the final output depends on the previous Base64 data, and the previous Base64 decoded data should match the MD5 hash of our input (from 0 to 23). However, according to current analysis, the first set of the Base64 strings seems uses a different character set than the standard one, if we decoded it using the standard Base64 character set which used by this program, the MD5 hash will never match!

What is the problem here? I must missed something!

It is time to go back to the original file (before unpack), let’s try to execute it in a CMD:
ch4_output1

What?! Instead of printing out “2 + 2 = 5”, it prints “2 + 2 = 4”!!! So how about we pass the current hour to it? Oops, we have the email address:
ch4_ans

Now I realize the secret of this challenge, the file unpacked by the unpack stub of itself is different from the file unpacked using the UPX tool! Here is how the unpacked file looks like in the debugger:
ch4_dbg1

And the Base64 character set also different from the file unpacked by the UPX tool:
ch4_b64_2
ch4_b64_1

Posted in CTF | Tagged , , , | Comments Off on FLARE On Challenge (2015) #4