Skip to content

Instantly share code, notes, and snippets.

@matthw
Created February 21, 2023 08:08
Show Gist options
  • Save matthw/4ce2a4b9e76eb7008d55661e4792f463 to your computer and use it in GitHub Desktop.
Save matthw/4ce2a4b9e76eb7008d55661e4792f463 to your computer and use it in GitHub Desktop.
XPacker 0xL4ugh CTF

XPacker

1. Quick Peek

We get 2 PE files evil.exe and mypacker.exe. Looking at evil.exe, we can follow the invoke_main() function up to

undefined8 FUN_1400117f0(void)

{
    __CheckForDebuggerJustMyCode(&DAT_1400210f2);
    MessageBoxW(NULL,
                L"Hi there, are you having fun with this CTF?, I hope so :), and BTW I\'m just a container, nothing useful here",
                L"Hello World",0);
    return 1;
}

which doesnt do anything besides greeting us with a popup.

opening mypacker.exe and following invoke_main again, we can see it reads a 3rd PE file, XOR it and store it in a PE resource:

    handle = CreateFileW(L"C:\\Users\\OT\\source\\repos\\mypacker\\evil.exe",0x80000000,0,NULL,3,0x80,NULL);
    if (handle == (HANDLE)0xffffffffffffffff) {
        DVar1 = GetLastError();
        thunk_FUN_140016760("Cannot obtain a valid handle %d",(ulonglong)DVar1,uVar9,uVar10);
    }
    uVar3 = 0;
    size = GetFileSize(handle,NULL);
    if (size == 0) {
        thunk_FUN_140016760("Cannot calculate fileszie",uVar3,uVar9,uVar10);
    }
    uVar10 = 0x40;
    uVar9 = 0x3000;
    uVar4 = (ulonglong)size;
    file_content = (byte *)VirtualAlloc(NULL,uVar4,0x3000,0x40);
    if (file_content == NULL) {
        thunk_FUN_140016760("allocation failed",uVar4,uVar9,uVar10);
    }
    uVar9 = 0;
    uVar4 = (ulonglong)size;
    pbVar5 = file_content;
    BVar2 = ReadFile(handle,file_content,size,NULL,NULL);
    if (BVar2 != 0) {
        thunk_FUN_140016760("reading the content succesfully..",pbVar5,uVar4,uVar9);
    }
    ptr_file_content = file_content;

the encryption is here:

    for (i = 0; i < size; i = i + 1) {
        pwVar6 = L"PHALANX";
        puVar8 = key;
        for (n = 0x10; n != 0; n = n + -1) {
            *(undefined *)puVar8 = *(undefined *)pwVar6;
            pwVar6 = (wchar_t *)((longlong)pwVar6 + 1);
            puVar8 = (ushort *)((longlong)puVar8 + 1);
        }
        byte = *ptr_file_content;
                    /* xor */
        if ((byte != 0) && (local_24 = (uint)byte, (uint)byte != (uint)key[(ulonglong)i % 6])) {
            local_24 = (uint)byte;
            *ptr_file_content = byte ^ (byte)key[(ulonglong)i % 6];
        }
        ptr_file_content = ptr_file_content + 1;
    }

then write the resource:

    lpflOldProtect = local_114;
    VirtualProtect(file_content,8,0x40,lpflOldProtect);
    local_f0 = BeginUpdateResourceW(L"C:\\Users\\OT\\source\\repos\\mypacker\\IThinkThisWhatYouWant.exe",0);
    if ((local_f0 == NULL) || (local_f0 == (HANDLE)0xffffffffffffffff)) {
        uVar9 = 0x10000;
        uVar4 = (ulonglong)size;
        VirtualFree(file_content,uVar4,0x10000);
        thunk_FUN_140016760("BeginUpdateResourceA fails.\n",uVar4,uVar9,lpflOldProtect);
    }
    else {
        uVar9 = 0;
        BVar2 = UpdateResourceW(local_f0,L"UNKNOWN",(LPCWSTR)0x45,0,file_content,size);
        if (BVar2 == 0) {
            uVar10 = 0x10000;
            uVar4 = (ulonglong)size;
            VirtualFree(file_content,uVar4,0x10000);
            thunk_FUN_140016760("UpdateResourceW fails\n",uVar4,uVar10,uVar9);
        }
        else {
            BVar2 = EndUpdateResourceW(local_f0,0);
            if (BVar2 == 0) {
                uVar10 = 0x10000;
                uVar4 = (ulonglong)size;
                VirtualFree(file_content,uVar4,0x10000);
                thunk_FUN_140016760("EndUpdateResourceA fails\n",uVar4,uVar10,uVar9);
            }
        }
    }

2. Unpacking

Indeed evil.exe contains an extra resource as shown by pestudio or here, binref:

% emit evil.exe| perc -l    
UNKNOWN/69/0        <<< here
MANIFEST/1/1033

we can use a simple script to extract and decrypt the resource:

import pefile

pe = pefile.PE("evil.exe")

#key = b'PHALANX'
key = b'PHALAN'

def get_resource(pe):
    for rsrc_type in pe.DIRECTORY_ENTRY_RESOURCE.entries:
        for rsrc in rsrc_type.directory.entries:
            rsrc = rsrc.directory.entries[0].data.struct
            return pe.get_data(rsrc.OffsetToData, rsrc.Size)

def decrypt(data, key):
    out = bytearray(len(data))
    for n, b in enumerate(data):
        if data[n] == 0:
            continue
        if data[n] == key[n % len(key)]:
            out[n] = data[n]
            continue
        out[n] = data[n] ^ key[n % len(key)]
    return out

data = get_resource(pe)

yo = decrypt(data, key)

with open("dumped.pe", "wb") as fp:
    fp.write(yo)

The key string is "PHALANX", but there's a hardcoded length of 6 in the packer code, so only the first 6 chrs are used.

we're left with another executable:

% file dumped.pe
dumped.pe: PE32+ executable (console) x86-64, for MS Windows, 10 sections

3. Dumped bin

getting to the mainwe can see we're on the right track:

undefined8 main(void)

{
    int iVar1;
    
    __CheckForDebuggerJustMyCode(&DAT_1400230a2);
    iVar1 = do_something();
    if (iVar1 == 0) {
        MessageBoxA(NULL,"BAD!, Try harder PLZ...","Info",0);
    }
    else {
        MessageBoxA(NULL,"Correct UserName!, YOUR Flag will be FLAG{USERNAME}","Info",0);
    }
    return 0;
}

further down, it get the user the binary runs as:

int do_something(void)

{
    //

    GetUserNameA(username,size);
    check_username(username);

    //
}

the meat is in check_username:

it initialize a 4 int key:

    tea_key[0] = 0x2134;
    tea_key[1] = 0x2121;
    tea_key[2] = 0x2123;
    tea_key[3] = 0x65f2;

then intialize an array:

                    /* encrypted username */
    enc_username[0] = 0x6d;
    enc_username[1] = 0xe4;
    enc_username[2] = 0x9e;
    enc_username[3] = 0xff;
    enc_username[4] = 0x82;
    enc_username[5] = 0xa6;
    enc_username[6] = 0xe;
    enc_username[7] = 0x82;

and then use TEA to encrypt the current username (GetUserNameA):

    for (j = 0; j < len; j = j + 8) {
        TEA_encrypt((uint *)(username + (longlong)j * 4),tea_key);
    }

at the end it checks whether the result of the encryption == the enc_username. Ghidra fails to shows it, but IDA Free does it just fine:

  key[0] = 8500;
  key[1] = 8481;
  key[2] = 8483;
  key[3] = 26098;
  memset(&key[4], 0, 0x30ui64);
  len = j_strlen(username);
  if ( len % 8 )
    len += 8 - len % 8;
  enc_username = (char (*)[8])0x820EA682FF9EE46Di64;
  ptr_username = username;
  for ( j = 0; j < len; j += 8 )
    TEA((unsigned int *)&ptr_username[4 * j], key, v5);
  v13 = 0;
  return (char)enc_username == *username;           // HERE
}

TEA is easy to identify:

void TEA_encrypt(uint *param_1,int *key)

{
    uint v0;
    uint v1;
    uint sum;
    uint n;

    __CheckForDebuggerJustMyCode(&DAT_1400230a2);
    v0 = *param_1;
    v1 = param_1[1];
    sum = 0;
    for (n = 0; n < 0x20; n = n + 1) {
        sum = sum + 0x9e3779b9;
        v0 = v0 + (v1 * 0x10 + *key ^ v1 + sum ^ (v1 >> 5) + key[1]);
        v1 = v1 + (v0 * 0x10 + key[2] ^ v0 + sum ^ (v0 >> 5) + key[3]);
    }
    *param_1 = v0;
    param_1[1] = v1;
    return;
}

just google the constant 0x9e3779b9 and you'll find it (you'll also find XTEA, but the algorithm is slightly different).

4. Solving

We know the key: [0x2134, 0x2121, 0x2123, 0x65f2], we know the encrypted result b'm\xe4\x9e\xff\x82\xa6\x0e\x82', we know the algorithm TEA, so we can just decrypt:

from pwn import *
from ctypes import c_uint32

# https://gist.github.com/twheys/4e83567942172f8ba85058fae6bfeef5
def decipher(msg, k):
    y = c_uint32(u32(msg[:4]))
    z = c_uint32(u32(msg[4:8]))
    
    sum_ = c_uint32(0xC6EF3720)

    for x in range(32):
        z.value -= (y.value << 4) + k[2] ^ y.value + sum_.value ^ (y.value >> 5) + k[3]
        y.value -= (z.value << 4) + k[0] ^ z.value + sum_.value ^ (z.value >> 5) + k[1]
        sum_.value -= 0x9e3779b9

    return p32(y.value) + p32(z.value)


k = [0x2134, 0x2121, 0x2123, 0x65f2]
enc = b'm\xe4\x9e\xff\x82\xa6\x0e\x82'

print(decipher(enc, k))

and get the flag:

% python meh.py
b'RYouLost'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment