Tags: ida obfuscation injection re
Rating:
The flag format is MeePwnCTF{correct_input}
Unzip password: MeePwn
Here's the main function:
If the program is ran without any arguments (and thus argc==1), it spawns 2 additional processess with args 1
and 1 2
, and goes on to execute argc_switch
:
Generally, the program contains 3 different processes that check the password asynchronically using thread locks.
Before we get a clear assembly though, there are 2 obfuscation tricks that we have to go through:
There are conditional jumps that are always taken and the next instruction after the bogus jz
is a bad instruction to screw up our disassembly: (notice the addr+1
)
We can correctly set the data/code stuff in ida and get a pretty good dissasembly:
Additionaly, if we want IDA to recognize this code as a function we're gonna need to patch the jz
instructions to jmp
.
Next thing is encrypted code:
BOOL __cdecl switch_stuff(LPVOID lpParameter)
{
void *v2; // [esp+1Ch] [ebp-14h]
HANDLE hProcess; // [esp+20h] [ebp-10h]
SIZE_T i; // [esp+24h] [ebp-Ch]
_BYTE *lpBuffer; // [esp+28h] [ebp-8h]
SIZE_T dwSize; // [esp+2Ch] [ebp-4h]
hProcess = OpenProcess(dwDesiredAccess, 0, process_stuff.field_0);
if ( !hProcess )
{
exit(0);
}
dwSize = 0;
lpBuffer = 0;
if ( lpParameter == 1 )
{
dwSize = 1024;
lpBuffer = subfun_1;
}
if ( lpParameter == 2 )
{
dwSize = 864;
lpBuffer = sunfun_2;
}
if ( lpParameter == 3 )
{
dwSize = 1040;
lpBuffer = subfun_3;
}
v2 = VirtualAllocEx(hProcess, 0, dwSize, 0x1000u, 0x40u);
if ( !v2 )
{
exit(0);
}
for ( i = 0; i < dwSize; ++i )
{
lpBuffer[i] ^= 0x33u;
}
if ( !WriteProcessMemory(hProcess, v2, lpBuffer, dwSize, 0) )
{
exit(0);
}
if ( !CreateRemoteThread(hProcess, 0, 0, v2, lpParameter, 0, 0) )
{
exit(0);
}
return CloseHandle(hProcess);
}
Basing on the number of arguments, a different code section is decrypted and injected into a new thread. Dexoring the buffer and loading the buffers as additional binaries in IDA works pretty good.
Here's a tree of execution that sums it up pretty well:
Each node creates the next one by writing code hardcoded in the function body to itself using WriteProcessMemory:
The aesenc
block represents a code section that encrypts the global string with a one round of aes by using the aesenc
instruction and a hard-coded key:
So:
aesenclast
is the last round of aesSo all we have to do is reverse the operations and we'll get the flag.
We can't just shuffle the aes rounds though, so we have 2 options:
Obviously, brute-forcing is faster as there are only 8! possible combinations (even less when when we take into consideration the block order).
We whim up a quick python script that does the job for us:
import itertools
from aes import AES
from crypto_commons.generic import xor_string, is_printable
def convert_array_to_strings(arr):
return "".join(map(chr, arr))
possible_rounds = [
[0x45,0x33,0x7E,0x3C,0xF0,0x7F,0x2D,0xAC,0x33,0x44,0x3B,0x75,0x48,0x2A,0xC5,0x46,],
[0x3E,0x43,0x95,0x3C,0x69,0xD0,0x73,0x67,0x22,0x97,0xD1,0xB1,0xA3,0x61,0xFD,0x4A,],
[0x4D,0xF0,0xEC,0x1A,0x3D,4,0xA9,0xDB,0xF5,0xD5,8,0x1A,0x80,0x70,0x93,6,],
[0x60,0x9D,0x47,0x31,0xB5,0xDD,0x36,0x7E,0xEF,0x99,0x7A,0xD8,0x49,0x5C,0x45,0x23,],
[0xFA,0xEC,0xDB,0xBB,0x93,0xB2,0x3A,0xEF,0x68,0xE4,0xBE,0x6D,0x2F,0xF6,0x6B,0x4C,],
[0x2E,0xEB,0xCF,0x46,5,0xAE,0x3D,0x94,0xBA,0x8C,0xCC,0xF4,0x4C,0xA1,0x1D,0x4C,],
[0xBA,0xF0,0xAB,0x1F,0xAC,0x2F,0x58,0x81,0xF1,0x25,0xB1,0x59,0xF9,0x79,0xDE,3,],
[0x34,0xAF,0xFF,0x57,0x51,0x3A,0xF,0xEC,0x8B,0xA0,0xE6,0x5F,0x8C,0x98,0x60,0x78,],
[0x74,0xF9,0xC5,0x42,0x7F,0x7A,0x6E,0xE2,0xB1,0x1F,0x2C,0xC2,0x18,4,0xB8,0xF7,],
]
last = [0xA,0x98,0x63,0x1D,0x84,0x69,0x82,8,7,0xCA,0x31,0xF7,0x1D,0x33,0x56,0x29]
ct = [0x68,0xCE,0xDF,0xDD,0x58,0x6C,0x37,0xE4,0xC4,0xE1,0xAC,0xB4,9,0x7F,0x97,0xA4]
xorkey = [0x6A, 0x15, 0x6D, 0xB, 0x9D, 0xF0, 0xC2, 0x34, 0x74, 0x8A, 0xD4, 0x4F, 0x50, 0x84, 0xA0, 0x7F, ]
possible_rounds = map(convert_array_to_strings, possible_rounds)
ct = convert_array_to_strings(ct)
last = convert_array_to_strings(last)
xorkey = convert_array_to_strings(xorkey)
for i in itertools.permutations(possible_rounds):
A = AES()
rounds = i
try_ct = A.sr_decryptlast(ct, last)
for r in rounds:
try_ct = A.sr_decrypt(try_ct, r)
print(xor_string(try_ct, xorkey))
And get the most printable key: 5B4D656550776E5D
\o/