From aldeid
Jump to navigation Jump to search
You are here
Challenge 4


Uncompress (password is "flare") and you will get a file named youPecks with following properties:

MD5 a3f5054fa43902333ac67dea4c0e7403
SHA1 2a53919219ad2c1fa42ac48430d6c4f1b5255c64
SHA256 b25a0a8e66fd522d73ea051dc8a86d528b088f956f3ae6d9e4616c9a3db368fc
File type PE32 executable (console) Intel 80386, for MS Windows, UPX compressed

Unpack file

The UPX tool

Both the file command and PEiD claim the file is packed with UPX. Moreover, the executable contains the UPX0 and UPX1 sections.

It is possible to unpack the executable with UPX:

C:\_malware>upx -d -o youPecks_unpacked.exe youPecks.exe
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2013
UPX 3.91w       Markus Oberhumer, Laszlo Molnar & John Reiser   Sep 30th 2013

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
     25088 <-     12800   51.02%    win32/pe     youPecks.exe

Unpacked 1 file.

It results in an unpacked file with following properties:

MD5 f73fc86e0120815e6d3a24798cd49027
SHA1 542f914c7779dcccc03ba61b5fee2a6a480e376b
SHA256 3fce0560ca4576eeecaf7ac3e5723b1ef60454bcb0c9fda659d94610a50757d8
File type PE32 executable (console) Intel 80386, for MS Windows

Running the executable

When running the executable, if you have an error message that claims mfc100u.dll is missing, just install Microsoft Visual C++ 2010 redistributable.

You will notice a big difference of execution between the packed and the unpacked versions:

Packed file Unpacked file
2 + 2 = 4
2 + 2 = 5

Analyzing the unpacked executable at location 0x401442 shows that this value ("5") is actually hard coded and that there is a weird test at offset 0x40147C:

.text:00401442                 push    offset Str      ; "5"
.text:00401447                 call    ds:atoi
.text:0040144D                 add     esp, 4
.text:00401450                 mov     esi, eax        ; ESI = 5
.text:00401452                 mov     eax, ds:?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A ; std::basic_ostream<char,std::char_traits<char>> std::cout
.text:00401457                 push    offset asc_405250 ; "\n"
.text:0040145C                 push    esi
.text:0040145D                 push    offset a22      ; "2 + 2 = "
.text:00401462                 push    eax
.text:00401463                 call    f_print
.text:00401468                 add     esp, 8
.text:0040146B                 mov     ecx, eax
.text:0040146D                 call    ds:??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Z ; std::basic_ostream<char,std::char_traits<char>>::operator<<(int)
.text:00401473                 push    eax
.text:00401474                 call    f_print
.text:00401479                 add     esp, 8
.text:0040147C                 cmp     esi, 5
.text:0040147F                 jnz     short loc_4014EE ; Will never jump since ESI = 5

If we analyze the packed version directly in memory, we can see that the value at memory location 0x40524C is not "5" but "4":

The only possible explanation is that the packed executable has a modified unpacking routine (actually the UPX utility is using its own unpacking routine when unpacking the executable and hence will never proceed with the code modifications embedded in the initial executable's unpacking routine).

To confirm that assumption, I set a access harwdare breakpoint at memory location 0x40524C and ran the executable. The program stopped at 0x4060B where we can see the value 0x34 ("4") written to our memory location:

UPX1:0040B5FB                 add     edi, 51B8h                ; memory location 0x51B8 accessed and saved to EDI
UPX1:0040B601 loc_40B601:
UPX1:0040B601                 xor     byte ptr [ecx+edi], 20h   ; characters of the string at offset 0x51B8 are XOR'ed with 0x20
UPX1:0040B605                 loop    loc_40B601
UPX1:0040B607                 xor     byte ptr [ecx+edi], 20h
UPX1:0040B60B                 mov     byte ptr [esi+424Ch], 34h ; '4' written to memory location 0x424C
UPX1:0040B612                 pop     ecx
UPX1:0040B613                 popa
UPX1:0040B614                 lea     eax, [esp-80h]

Values at memory locations 0x51B8 and 0x524C:

.rdata:004051B8 aAbcdefghijklmn db 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',0
.rdata:0040524C Str             db '5',0

This modified unpacking routine is not only changing the value "5" to "4" but is also accessing the string at location 0x51B8 ("ABCD.....890+/") and XOR's the first 52 characters with 0x20 so that it becomes as follows (the XOR operation actually swaps characters):

>>> s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
>>> s[:52]
>>> s[52:]
>>> ''.join([chr(ord(i) ^ 0x20) for i in s[:52]]) +  s[52:]

Knowing these 2 modifications brought by the internal unpacking subroutine, you have several possibilities, including:

  • continuing the analysis in memory (OllyDbg) to be sure that the unpacking subroutine used is the one embedded in the original executable
  • unpacking the executable with the UPX utility and patching the resulting executable

Patching the unpacked executable

Here is how you can patch the executable that you have unpacked with the upx utility (I have used McAfee FileInsight to patch the hex directly):

Offset Initial value (hex) Patched value (hex)
0x524C 35 34
0x51B8 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A

Code analysis & Solution

Quick and dirty way

There is a quick and dirty way to resolve this challenge. Indeed, the workflow can be easily modified till the end, where the solution is then displayed. When I attended this challenge, this is actually the way I solved it :).

  • First install Microsoft Visual Studio 2010 and run the packed executable in OllyDbg with 123 as argument.
  • Put a breakpoint (F2) at offset 0x40B621 (JMP youPecks.00403A8A).
  • Run the executable (F9) till the breakpoint and press (F7) to step into the call. You have reached the unpacked section of the code.
  • Now, patch the executable as follows:
Offset Initial value Patched value Comments
0x4014F2 JE SHORT youPecks.00401560 JMP SHORT youPecks.00401560 Force the jump to 0x401560 to be taken (only necessary if no argument has been provided)
0x401BBC JNZ 0040247B NOP NOP out this instruction to be sure that it won't jump too short to the end
  • Put a breakpoint at offset 0x40246D, just after the call to the sub_402D30 function.
  • Run the executable and you're done:


The program expects 1 argument, as depicted below:

The argument is converted to an integer at offset 0x401564 and is then used as argument for the sub_4012E0 function (renamed f_get_crypt_hash) at 0x401584.

An analysis of the function shows that CryptCreateHash is called with the Algid parameter set to 0x8003 (CALG_MD5).

.text:0040130F push    ecx             ; phHash
.text:00401310 push    0               ; dwFlags
.text:00401312 push    0               ; hKey
.text:00401314 push    CALG_MD5        ; Algid (0x8003)
.text:00401319 push    edx             ; hProv
.text:0040131A call    ds:CryptCreateHash

Only 1 byte of the hash is actually used:

.text:00401320 mov     eax, [ebp+pbData]
.text:00401323 mov     ecx, [ebp+phHash]
.text:00401326 push    0               ; dwFlags
.text:00401328 push    1               ; dwDataLen
.text:0040132A push    eax             ; pbData
.text:0040132B push    ecx             ; hHash
.text:0040132C call    ds:CryptHashData

Manipulating base64 strings and keys

As depicted on the following graph, the code is building 2 sets of strings:

  • base64 strings for each hour
  • array of keys for each hour

Then there is a XOR operation applied to each character of the key with the rotating characters of the hour string:

This section/article is being written and is therefore not complete.
Thank you for your comprehension.

Script and Proof of Concept


#!/usr/bin/env python

import base64
import sys

def main(what_hour):

    print "---"
    print "Hour: %s" % what_hour
    h = ['K7IfRF4nOiNn9Jsqt9wFCq==',

    k = ['XTd3NiPLZBQ5N1FqkBN+a/Av6SpqBS/K',
    hour = base64.b64decode(h[int(what_hour)].swapcase())
    key = base64.b64decode(k[int(what_hour)].swapcase())

    return ''.join([chr(ord(hour[i % len(hour)]) ^ ord(key[i])) for i in range(len(key))])

if __name__ == '__main__':
    print main(sys.argv[1])

Proof of Concept

$ for i in $(seq 0 23);do ./ $i;done
Hour: 0
[email protected]
Hour: 1
[email protected]
Hour: 2
[email protected]
Hour: 3
[email protected]
Hour: 4
[email protected]
Hour: 5
[email protected]
Hour: 6
[email protected]
Hour: 7
[email protected]
Hour: 8
[email protected]
Hour: 9
[email protected]
Hour: 10
[email protected]
Hour: 11
[email protected]
Hour: 12
[email protected]
Hour: 13
[email protected]
Hour: 14
[email protected]
Hour: 15
[email protected]
Hour: 16
[email protected]
Hour: 17
[email protected]
Hour: 18
[email protected]
Hour: 19
[email protected]
Hour: 20
[email protected]
Hour: 21
[email protected]
Hour: 22
[email protected]
Hour: 23
[email protected]


Keywords: reverse-engineering challenge flare fireeye