The-FLARE-On-Challenge-2015/Challenge-4
You are here | Challenge 4
|
File
Uncompress CB931CA00859C5D1356CB2733B11EBF2.zip (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
You will notice a big difference of execution between the packed and the unpacked versions:
Packed file | Unpacked file |
---|---|
C:\_malware>youPecks.exe 2 + 2 = 4 |
C:\_malware>youPecks_unpacked.exe 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
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] 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' >>> s[52:] '0123456789+/' >>> ''.join([chr(ord(i) ^ 0x20) for i in s[:52]]) + s[52:] 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/'
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:
Arguments
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:
Thank you for your comprehension.
Script and Proof of Concept
Script
#!/usr/bin/env python
import base64
import sys
def main(what_hour):
print "---"
print "Hour: %s" % what_hour
h = ['K7IfRF4nOiNn9Jsqt9wFCq==',
'vAvack0BPyMQiq0MkChFqq==',
'NMImwkviE46VACNHafRqVW==',
'HMzOnqAQZzalVvP0Re7FAa==',
'7h9+E7q3qS6gGux3htE1pa==',
'I7BbEdHKp5ArZgPn5Suxcq==',
'bUYHTdFhKeZdZMvgYbebea==',
'IEDozaUmrIv6kD4gfNLnxq==',
'4RQqw/mg9g+SOIptYYdIZW==',
'xNmQghI+i0lB/V9F48PAOW==',
'AlmP2PIt40czX9ITxlNjqa==',
'e8J/2xCbnWoNaC+oeD6Szq==',
'wmIvyVwp0NB1KKiaAnUmcq==',
'3lM+l2boxFKD65zzVTr0Jq==',
'tE2YjaOEdWonZCIZ3PiMta==',
'2dHPhL1k0gH5YNiuqUId1a==',
'AZg9+N+B/S4Mm4h/QrVwQq==',
'r+1Zo40qVIjEZRO0tvm1HG==',
'QerwgAVqJZUG6/YZeyp3+q==',
'/+uDpN2cLYn1Ihbo7DXQSG==',
'fFqAlPA640hD5iw7dNJ0Hq==',
'9AFKD80WqRsAKixwiWFnka==',
'V21SGz7jDBbdRSucfNW9fq==',
'Hp8u+Kw+pkrZNNWcDXELqq=='
]
k = ['XTd3NiPLZBQ5N1FqkBN+a/Av6SpqBS/K',
'am0YoDLZYlREsg5Mt62+mZcil2AdEmRK',
'YWd+ADeGfR3BakQHzJAXZFTf4ZAlkXtJ',
'0W4AbhlcOkn/1dK0YIk+gUnlb1SOYAl9',
'UrCmsSbFl/3Y6cA3E1VutOLserwAvc2J',
'3T6ZsuWmuQxLPqKnGkL2E+6BRHywb1d7',
'u4ttHuoV/x+3PWygRN1GyMpbZTOzPp8H',
'3i88vx/KkXyoql1gCbuSl+ZkiqOL7YLi',
'T9lIAODUMvZyY0ctRuYdVyEx/ZxDzzSc',
'cXTykqZwtNgVL5WFHAy70tTErxzw3uWV',
'pDTB6+Z7JNpTRRVToTwOmG2ErRs28iWT',
'rQcn6anPwJdtAkZoD7lnf3BLKlDzyLHU',
'dAdNu4hNV0wb+YfadRFTEZ3L+GZB7l0B',
'IDhmhHqMmmPPGVuz2lGv/7Mu0ufoltku',
'gixafx52yJd5PkVZUp5hpIJa3uOKFwbU',
'JvaBlYKIVvSnOXfujitIPR0vbNbZkB8f',
'pLNpYWVZK/1swUk/Z3E32W4C0Prr+jgJ',
'eOubcVL40XeQP9L0kZ9u9clahfwJC9fp',
'/sWKkn+44GJuGP/ZD++wI81PoxEfS+bw',
'QO1VdWNQ+Hab4rmoI7alWjRiCLbt4FHo',
'qjXOh+lsJNkPJEB7Absv93dzDuc42yWS',
'Om+wrRLyl4FU+EAwrwUSwPckIXNJuY3z',
'6GuESoQHgim3X6zcCbbCz9Paa++WQHRD',
'0zDMYZhwuzCh9X9cexVem+hsE5rR3vpj'
]
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 ./solution.py $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]
Comments
Keywords: reverse-engineering challenge flare fireeye