hamarituc / xz-backdoor

35 stars 1 forks source link

Code of real __get_cpuid #1

Open ljbade opened 3 months ago

ljbade commented 3 months ago

Thanks for this useful disassembly of the backdoor .o file. I too find it fascinating how this hack works. So to add some of my own notes I decided to open this issue thread.

Something I noticed quickly is that if you compare the fake _get_cpuid() to the real GCC __get_cpuid() the disassembled C code is a close match.

It looks like they just grabbed the GCC code here and made their modifications to it.

This means that FUN_0010a740 is actually a modified GCC __get_cpuid_max().

ljbade commented 3 months ago

Additionally it is worth noting the way the backdoor's build script modify the xz source code to call _get_cpuid()_:

V='#endif\n#if defined(CRC32_GENERIC) && defined(CRC64_GENERIC) && defined(CRC_X86_CLMUL) && defined(CRC_USE_IFUNC) && defined(PIC) && (defined(BUILDING_CRC64_CLMUL) || defined(BUILDING_CRC32_CLMUL))\nextern int _get_cpuid(int, void*, void*, void*, void*, void*);\nstatic inline bool _is_arch_extension_supported(void) { int success = 1; uint32_t r[4]; success = _get_cpuid(1, &r[0], &r[1], &r[2], &r[3], ((char*) __builtin_frame_address(0))-16); const uint32_t ecx_mask = (1 << 1) | (1 << 9) | (1 << 19); return success && (r[2] & ecx_mask) == ecx_mask; }\n#else\n#define _is_arch_extension_supported is_arch_extension_supported'

Or with all the extra stuff removed:

int _get_cpuid(int, void*, void*, void*, void*, void*); // this is what makes linker bring in fake _get_cpuid from .o file

static inline bool _is_arch_extension_supported(void) {
  int success = 1;
  uint32_t r[4];
  success = _get_cpuid(1, &r[0], &r[1], &r[2], &r[3], ((char*) __builtin_frame_address(0))-16);
  const uint32_t ecx_mask = (1 << 1) | (1 << 9) | (1 << 19);
  return success && (r[2] & ecx_mask) == ecx_mask;
}

The real __get_cpuid takes four arguements, the first is the EAX in value (selects the CPUID lead), the next four are pointers to variables to write the returned EAX, EBX, ECX, and EDX from the CPUID instruction.

However the backdoor then passes an additional argument __builtin_frame_address(0) - 16. That function is documented at https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html

The frame address offset then makes its way to what used to be _sig in __get_cpuid_max(). In the real function this has the value of EBX or the CPU manufacturer signature (e.g. "GenuineIntel" as bytes). However the fake function doesn't set _sig and instead I think uses this for something else.

I think this

ljbade commented 3 months ago

I think I figured out purpose of __builtin_frame_address(0) - 16 - this gives the fake _get_cpuid() some memory to store the various rootkit context structure on the stack, as _get_cpuid does not allocate room for local variables on the stack.