本文以乐固2.8.1(后面还有2.10.3.1)为例,介绍乐固壳的分析和脱壳过程。
一、绕过反调试
首先,使用ida加载libshella-2.8.1.so后,出现以下section错误提示,点“OK”后仍然可以加载成功:
这说明“乐固”在section做了一些手脚。要解决这个问题,我们需要明白关于section和segment的一些不同:
1. ELF中的section主要提供给Linker使用, 而segment提供给Loader用,Linker需要关心.text, .rel,.text, .data, .rodata等等,关键是Linker需要做relocation。而Loader只需要知道Read/Write/Execute的属性。
2.一个executable的ELF文件可以没有section,但必须有segment。ELF文件中间部分是共用的(也就是代码段、数据段等)
由第2点可知,既然ELF可以没有section,我们就将section_header_table的数据全部清0,然后再用ida打开,发现没有这个错误提示了。
在修复的libshella-2.8.1.so的Exports表中找到JNI_OnLoad函数,发现该函数是加密的:
我们知道so 文件加载时首先会查看 .init 或 .init_array 段是否存在,如果存在那么就先运行这段的内容,如果不存在的话那么就检查是否存在JNI_OnLoad,存在则执行。所以JNI_OnLoad的解密就应该是在 .init 或 .init_array 段中。
因为 .init 或者 .init_array 在 IDA 动态调试的时候是不会显示出来的,所以需要静态分析出这两段的偏移量然后动态调试的时候计算出绝对位置,然后在 make code(快捷键:c),这样才可以看到该段内的代码内容。
那么,如何定位到执行so文件的init或init_array函数呢?
方法1:
1)从手机或虚拟机中pull出来linker
2)搜索字符串“[ Calling %s @ %p for '%s' ]”,见下图:
3)查找引用此字符串的地址:
4)到sub_271C处,见下图 :
方法2:使用 IDA 加载 .so 文件,按ctrl+s快捷键查看 “Segments” 视图,这里会列出不同类型的代码段信息,如下图所示。
方法3:readelf -a查看init_array的偏移地址:
我尝试了前两种方法都没成功,不知为什么。使用第三种方法,如上图,得到init_array地址:0x9e20。通过ida定位到该地址,发现调用了两个函数tencent1115357682105733551029和sub_14D4
第一个函数tencent1115357682105733551029为空函数,可以直接忽略:
主要看第二个函数
1 int sub_B6F084D4() 2 { 3 unsigned int v0; // ST44_4 4 unsigned int v1; // ST40_4 5 unsigned __int64 v2; // kr00_8 6 char v3; // ST27_1 7 int j; // [sp+14h] [bp-44h] 8 int v6; // [sp+18h] [bp-40h] 9 int v7; // [sp+1Ch] [bp-3Ch] 10 char v8; // [sp+2Eh] [bp-2Ah] 11 char v9; // [sp+2Fh] [bp-29h] 12 char v10; // [sp+30h] [bp-28h] 13 char v11; // [sp+33h] [bp-25h] 14 unsigned int v12; // [sp+34h] [bp-24h] 15 _DWORD *v13; // [sp+38h] [bp-20h] 16 unsigned int v14; // [sp+48h] [bp-10h] 17 unsigned int i; // [sp+4Ch] [bp-Ch] 18 19 for ( i = (unsigned int)sub_B6F084D4 & 0xFFFFF000; *(_DWORD *)i != 1179403647; i -= 4096 ) 20 ; 21 v14 = 0; 22 v13 = (_DWORD *)(i + *(_DWORD *)(i + 28)); 23 v12 = 0; 24 while ( v12 < *(unsigned __int16 *)(i + 44) ) 25 { 26 if ( *v13 != 1 || v13[6] != 5 ) 27 { 28 if ( *v13 == 1 && v13[6] == 6 ) 29 { 30 v0 = v13[2] & 0xFFFFF000; 31 v1 = (v13[2] + v13[4] + 4095) & 0xFFFFF000; 32 break; 33 } 34 } 35 else 36 { 37 v14 = (v13[2] + v13[4] + 4095) & 0xFFFFF000; 38 } 39 ++v12; 40 v13 += 8; 41 } 42 v11 = 43; 43 v10 = -103; 44 v9 = 32; 45 v8 = 21; 46 v2 = (unsigned __int64)word_B6F11010[0] << 16; 47 v7 = LOWORD(word_B6F11010[0]); 48 v6 = LOWORD(word_B6F11010[0]) - (word_B6F11010[0] >> 16); 49 mprotect_0(i + HIDWORD(v2), (v6 + 4095) & 0xFFFFF000, 3); 50 for ( j = HIDWORD(v2); j <= v7; ++j ) 51 { 52 v3 = *(_BYTE *)(i + j); 53 *(_BYTE *)(i + j) ^= (unsigned __int8)((j ^ (v10 - v9)) + v8) ^ v11; 54 *(_BYTE *)(i + j) += v10 ^ v9 & v8; 55 v11 += v3 & (v10 + v9 - v8) & j; 56 v10 += v3 ^ (v11 + j); 57 v9 ^= (unsigned __int8)(v3 - v11) ^ (unsigned __int8)j; 58 v8 -= v3 + v11 - j; 59 } 60 mprotect_0(i + HIDWORD(v2), (v6 + 4095) & 0xFFFFF000, 5); 61 sub_B6F083DC(i + HIDWORD(v2), v6); 62 word_B6F11010[0] = i; 63 dword_B6F11380 = i; 64 unk_B6F11384 = v14; 65 return sub_B6F0F630(); 66 }
我们看到这个函数主要进行了一些字节变换,然后调用了mprotect,sub_B6F083DC和sub_B6F0F630三个函数。sub_B6F083DC函数就是做了 cacheflush和syscall操作,不管它。
我们来看看sub_B6F0F630函数,发现该函数调用pthread_create创建了一个线程,第三个参数是线程运行函数的起始地址。
通过ida定位这个地址,查看pthread_create创建的线程调用的函数如下:
1 int __fastcall sub_B6F0EBD0(int a1) 2 { 3 int v2; // r0 4 int v3; // r0 5 char *v4; // r3 6 int *v5; // r1 7 int *v6; // r3 8 int v7; // r2 9 unsigned int v8; // r0 10 char *v9; // r3 11 char v10; // [sp-10h] [bp-490h] 12 char v11; // [sp-Fh] [bp-48Fh] 13 char v12; // [sp-Eh] [bp-48Eh] 14 char v13; // [sp-Dh] [bp-48Dh] 15 char v14; // [sp-Ch] [bp-48Ch] 16 char v15; // [sp-Bh] [bp-48Bh] 17 char v16; // [sp-Ah] [bp-48Ah] 18 char v17; // [sp-9h] [bp-489h] 19 char v18; // [sp-8h] [bp-488h] 20 char v19; // [sp-7h] [bp-487h] 21 char v20; // [sp-6h] [bp-486h] 22 char v21; // [sp-5h] [bp-485h] 23 char v22; // [sp-4h] [bp-484h] 24 char v23; // [sp-3h] [bp-483h] 25 char v24; // [sp-2h] [bp-482h] 26 int v25; // [sp+0h] [bp-480h] 27 int v26; // [sp+4h] [bp-47Ch] 28 int v27; // [sp+8h] [bp-478h] 29 int v28; // [sp+Ch] [bp-474h] 30 int v29; // [sp+10h] [bp-470h] 31 int v30; // [sp+14h] [bp-46Ch] 32 int v31; // [sp+18h] [bp-468h] 33 int v32; // [sp+1Ch] [bp-464h] 34 int v33; // [sp+20h] [bp-460h] 35 int v34; // [sp+24h] [bp-45Ch] 36 int v35; // [sp+28h] [bp-458h] 37 int v36; // [sp+2Ch] [bp-454h] 38 int v37; // [sp+30h] [bp-450h] 39 int v38; // [sp+34h] [bp-44Ch] 40 int v39; // [sp+38h] [bp-448h] 41 int v40; // [sp+3Ch] [bp-444h] 42 void *v41; // [sp+40h] [bp-440h] 43 char *v42; // [sp+44h] [bp-43Ch] 44 void *v43; // [sp+48h] [bp-438h] 45 char v44; // [sp+4Ch] [bp-434h] 46 char v45; // [sp+4Dh] [bp-433h] 47 char v46; // [sp+4Eh] [bp-432h] 48 char v47; // [sp+4Fh] [bp-431h] 49 char v48; // [sp+50h] [bp-430h] 50 char v49; // [sp+51h] [bp-42Fh] 51 char v50; // [sp+52h] [bp-42Eh] 52 char v51; // [sp+53h] [bp-42Dh] 53 char v52; // [sp+54h] [bp-42Ch] 54 char v53; // [sp+55h] [bp-42Bh] 55 char v54; // [sp+56h] [bp-42Ah] 56 char v55; // [sp+57h] [bp-429h] 57 char v56; // [sp+58h] [bp-428h] 58 char v57; // [sp+59h] [bp-427h] 59 char v58; // [sp+5Ah] [bp-426h] 60 char v59; // [sp+5Bh] [bp-425h] 61 int watch_fd; // [sp+5Ch] [bp-424h] 62 char v61; // [sp+60h] [bp-420h] 63 int v62; // [sp+460h] [bp-20h] 64 int v63; // [sp+464h] [bp-1Ch] 65 int v64; // [sp+468h] [bp-18h] 66 int v65; // [sp+46Ch] [bp-14h] 67 68 v65 = a1; 69 ++dword_B6F11378; 70 v43 = &loc_B6F10F44; 71 v42 = &v44; 72 v41 = &loc_B6F10F44; 73 unk_B6F11388 = sub_B6F08218(1024); // malloc 74 v40 = sub_B6F0DDEC(); 75 unk_B6F1138C = sub_B6F0DEE0(0, v65, unk_B6F11384); 76 v64 = getppid_0(); 77 v50 = unk_B6F112B7 ^ 0x96; 78 v49 = unk_B6F112B6 ^ 0xF7; 79 v58 = unk_B6F112BF ^ 0xE1; 80 v51 = unk_B6F112B8 ^ 0x9E; 81 v46 = unk_B6F112B3 ^ 0x85; 82 v53 = unk_B6F112BA ^ 0x98; 83 v59 = unk_B6F112C0; 84 v44 = unk_B6F112B1 ^ 0xE9; 85 v47 = unk_B6F112B4 ^ 0x95; 86 v56 = unk_B6F112BD ^ 0xCA; 87 v54 = unk_B6F112BB ^ 0xEE; 88 v48 = unk_B6F112B5 ^ 0xEA; 89 v45 = unk_B6F112B2 ^ 0x97; 90 v57 = unk_B6F112BE ^ 0xD3; 91 v55 = unk_B6F112BC ^ 0xA5; 92 v52 = unk_B6F112B9 ^ 0xFB; 93 v63 = sub_B6F082D8((int)&v44); // opendir 94 if ( v63 ) 95 { 96 v39 = 4095; 97 watch_fd = init(); 98 v38 = watch_fd; 99 sub_B6F08338(); // fcntl 100 v2 = sub_B6F08338(); 101 v22 = byte_B6F112CD ^ 0x8D; 102 v10 = unk_B6F112C1 ^ 0x91; 103 v19 = byte_B6F112CA ^ 0xC5; 104 v21 = byte_B6F112CC ^ 0x80; 105 v13 = byte_B6F112C4 ^ 0xD3; 106 v15 = byte_B6F112C6 ^ 0xE1; 107 v17 = byte_B6F112C8 ^ 0xCA; 108 v18 = byte_B6F112C9 ^ 0xE1; 109 v24 = byte_B6F112CF; 110 v23 = byte_B6F112CE ^ 0xC8; 111 v11 = byte_B6F112C2 ^ 0x87; 112 v12 = byte_B6F112C3 ^ 0x96; 113 v20 = byte_B6F112CB ^ 0xDA; 114 v14 = byte_B6F112C5 ^ 0xD7; 115 v16 = byte_B6F112C7 ^ 0xE2; 116 v37 = v2; 117 v36 = add_watch(watch_fd, &v10, 4095); 118 while ( 1 ) 119 { 120 v62 = sub_B6F082E4(v63); // readdir 121 if ( !v62 ) 122 break; 123 if ( *(_BYTE *)(v62 + 18) & 4 && &word_2E != (__int16 *)*(unsigned __int8 *)(v62 + 19) ) 124 { 125 *(&v10 - 11) = byte_B6F112DD ^ 0x8D; 126 *(&v10 - 22) = byte_B6F112D2 ^ 0x9E; 127 *(&v10 - 12) = byte_B6F112DC ^ 0xAD; 128 *(&v10 - 5) = byte_B6F112E3 ^ 0x86; 129 *(&v10 - 14) = byte_B6F112DA ^ 0xE1; 130 *(&v10 - 8) = byte_B6F112E0 ^ 0xB1; 131 *(&v10 - 10) = byte_B6F112DE ^ 0xCF; 132 *(&v10 - 15) = byte_B6F112D9 ^ 0xB1; 133 *(&v10 - 24) = unk_B6F112D0 ^ 0xA7; 134 *(&v10 - 13) = byte_B6F112DB ^ 0xE0; 135 *(&v10 - 7) = byte_B6F112E1 ^ 0xA2; 136 *(&v10 - 3) = byte_B6F112E5 ^ 0xAB; 137 *(&v10 - 17) = byte_B6F112D7 ^ 0x8B; 138 *(&v10 - 2) = byte_B6F112E6; 139 *(&v10 - 23) = byte_B6F112D1 ^ 0xBD; 140 *(&v10 - 19) = byte_B6F112D5 ^ 0xCC; 141 *(&v10 - 9) = byte_B6F112DF ^ 0xA7; 142 *(&v10 - 6) = byte_B6F112E2 ^ 0xE6; 143 *(&v10 - 16) = byte_B6F112D8 ^ 0xA0; 144 *(&v10 - 18) = byte_B6F112D6 ^ 0x83; 145 *(&v10 - 21) = byte_B6F112D3 ^ 0x98; 146 *(&v10 - 20) = byte_B6F112D4 ^ 0xD2; 147 *(&v10 - 4) = byte_B6F112E4 ^ 0xC2; 148 v35 = sub_B6F082F0(); // sprintf 149 v34 = add_watch(watch_fd, &v61, 4095); 150 } 151 } 152 v33 = sub_B6F08314(v63); // closedir 153 while ( 1 ) 154 { 155 *(&v10 - 12) = byte_B6F112FC ^ 0x8D; 156 *(&v10 - 7) = byte_B6F11301; 157 *(&v10 - 10) = byte_B6F112FE ^ 0xD9; 158 *(&v10 - 14) = byte_B6F112FA ^ 0xD2; 159 *(&v10 - 17) = byte_B6F112F7 ^ 0xD5; 160 *(&v10 - 19) = byte_B6F112F5 ^ 0x81; 161 *(&v10 - 22) = byte_B6F112F2 ^ 0xB8; 162 *(&v10 - 13) = byte_B6F112FB ^ 0xC7; 163 *(&v10 - 15) = byte_B6F112F9 ^ 0xB2; 164 *(&v10 - 18) = byte_B6F112F6 ^ 0x81; 165 *(&v10 - 24) = unk_B6F112F0 ^ 0x9C; 166 *(&v10 - 9) = byte_B6F112FF ^ 0x95; 167 *(&v10 - 20) = byte_B6F112F4 ^ 0x82; 168 *(&v10 - 23) = byte_B6F112F1 ^ 0x81; 169 *(&v10 - 16) = byte_B6F112F8 ^ 0x88; 170 *(&v10 - 8) = byte_B6F11300 ^ 0xC; 171 *(&v10 - 11) = byte_B6F112FD ^ 0xC8; 172 *(&v10 - 21) = byte_B6F112F3 ^ 0x8B; 173 v3 = sub_B6F0DFA0((int)(&v10 - 24)); 174 if ( v3 != v64 ) 175 { 176 *(&v10 - 16) = byte_B6F112F8 ^ 0x88; 177 *(&v10 - 11) = byte_B6F112FD ^ 0xC8; 178 *(&v10 - 24) = unk_B6F112F0 ^ 0x9C; 179 *(&v10 - 9) = byte_B6F112FF ^ 0x95; 180 *(&v10 - 8) = byte_B6F11300 ^ 0xC; 181 *(&v10 - 10) = byte_B6F112FE ^ 0xD9; 182 *(&v10 - 20) = byte_B6F112F4 ^ 0x82; 183 *(&v10 - 18) = byte_B6F112F6 ^ 0x81; 184 *(&v10 - 7) = byte_B6F11301; 185 *(&v10 - 23) = byte_B6F112F1 ^ 0x81; 186 *(&v10 - 19) = byte_B6F112F5 ^ 0x81; 187 *(&v10 - 14) = byte_B6F112FA ^ 0xD2; 188 *(&v10 - 21) = byte_B6F112F3 ^ 0x8B; 189 *(&v10 - 12) = byte_B6F112FC ^ 0x8D; 190 *(&v10 - 13) = byte_B6F112FB ^ 0xC7; 191 *(&v10 - 22) = byte_B6F112F2 ^ 0xB8; 192 *(&v10 - 15) = byte_B6F112F9 ^ 0xB2; 193 *(&v10 - 17) = byte_B6F112F7 ^ 0xD5; 194 if ( sub_B6F0DFA0((int)(&v10 - 24)) ) 195 { 196 *(&v10 - 6) = byte_B6F11304 ^ 0xAF; 197 *(&v10 - 8) = unk_B6F11302 ^ 0x8B; 198 *(&v10 - 5) = byte_B6F11305 ^ 0xF3; 199 *(&v10 - 7) = byte_B6F11303 ^ 0xCB; 200 *(&v10 - 4) = byte_B6F11306; 201 *(&v10 - 13) = byte_B6F1130A ^ 0xA7; 202 *(&v10 - 5) = byte_B6F11312 ^ 0xDF; 203 *(&v10 - 9) = byte_B6F1130E ^ 0xB5; 204 *(&v10 - 14) = byte_B6F11309 ^ 0xAC; 205 *(&v10 - 4) = byte_B6F11313 ^ 0x80; 206 *(&v10 - 8) = byte_B6F1130F ^ 0xFD; 207 *(&v10 - 12) = byte_B6F1130B ^ 0xC4; 208 *(&v10 - 7) = byte_B6F11310 ^ 0xE7; 209 *(&v10 - 15) = byte_B6F11308 ^ 0xC5; 210 *(&v10 - 16) = unk_B6F11307 ^ 0xA9; 211 *(&v10 - 3) = byte_B6F11314; 212 *(&v10 - 6) = byte_B6F11311 ^ 0x9E; 213 *(&v10 - 10) = byte_B6F1130D ^ 0x93; 214 *(&v10 - 11) = byte_B6F1130C ^ 0xE3; 215 v32 = sub_B6F081C4(6, (int)(&v10 - 8), (int)(&v10 - 16));// _android_log_print 216 v31 = sub_B6F0826C(9); // raise 217 } 218 } 219 if ( read_0(watch_fd, &v61, &dword_354[43]) > 0 ) 220 { 221 *(&v10 - 5) = byte_B6F11305 ^ 0xF3; 222 *(&v10 - 4) = byte_B6F11306; 223 *(&v10 - 6) = byte_B6F11304 ^ 0xAF; 224 *(&v10 - 8) = unk_B6F11302 ^ 0x8B; 225 *(&v10 - 7) = byte_B6F11303 ^ 0xCB; 226 v4 = &v10 - 16; 227 *v4 = unk_B6F11315 ^ 0xB2; 228 *(&v10 - 14) = byte_B6F11317 ^ 0xA6; 229 *(&v10 - 11) = byte_B6F1131A ^ 0xBF; 230 *(&v10 - 13) = byte_B6F11318 ^ 0xBA; 231 *(&v10 - 15) = byte_B6F11316 ^ 0xAF; 232 *(&v10 - 10) = byte_B6F1131B ^ 0x92; 233 *(&v10 - 7) = byte_B6F1131E ^ 0xE7; 234 *(&v10 - 9) = byte_B6F1131C ^ 0x9E; 235 *(&v10 - 8) = byte_B6F1131D ^ 0xB3; 236 *(&v10 - 12) = byte_B6F11319 ^ 0xDD; 237 v4[10] = byte_B6F1131F; 238 v30 = sub_B6F081C4(6, (int)(&v10 - 8), (int)(&v10 - 16));// _android_log_print 239 v29 = sub_B6F0826C(9); // raise 240 } 241 v5 = (int *)((char *)dword_440 + (_DWORD)v43); 242 v6 = (int *)((char *)&dword_440[2] + (_DWORD)v43); 243 ++*(int *)((char *)&dword_354[56] + (_DWORD)v43); 244 v7 = *v5; 245 v28 = *v6; 246 v8 = sub_B6F0DEE0(0, v65, v7); 247 if ( v28 != v8 ) 248 { 249 *(&v10 - 5) = byte_B6F11305 ^ 0xF3; 250 *(&v10 - 4) = byte_B6F11306; 251 *(&v10 - 7) = byte_B6F11303 ^ 0xCB; 252 *(&v10 - 8) = unk_B6F11302 ^ 0x8B; 253 *(&v10 - 6) = byte_B6F11304 ^ 0xAF; 254 v9 = &v10 - 8; 255 *v9 = unk_B6F11320 ^ 0xCD; 256 *(&v10 - 6) = byte_B6F11322 ^ 0x87; 257 *(&v10 - 7) = byte_B6F11321 ^ 0xDF; 258 *(&v10 - 3) = byte_B6F11325 ^ 0xF5; 259 *(&v10 - 5) = byte_B6F11323 ^ 0xFC; 260 *(&v10 - 2) = byte_B6F11326; 261 v9[4] = byte_B6F11324 ^ 0x1A; 262 v27 = sub_B6F081C4(6, (int)(&v10 - 8), (int)(&v10 - 8));// _android_log_print 263 v26 = sub_B6F0826C(9); // raise 264 } 265 v25 = sub_B6F08350(1); // sleep 266 } 267 } 268 return 0; 269 }
这个函数在while(1)循环中根据不同条件调用了3个raise() 函数,肯定是跟反调试相关的。不管怎么样,我们把这个创建线程的代码nop掉就可以了。具体方法是,在ida的Hex View窗口,定位到调用该函数的地址处,按F2,将对应十六进制改成“00 00 A0 E1”,再按F2保存就可以了。
二、找到RegisterNatives
过了反调试,接下来就是找Jni_OnLoad函数了。前面我们知道Jni_OnLoad的相对偏移地址为0x9e20,通过 [基地址+偏移地址] 得到绝对地址,ida定位到该地址,按快捷键C将数据转换成代码,下断点,F9运行到这里,就是Jni_OnLoad了,具体代码如下:
1 int __fastcall sub_763575A8(int a1, int a2) 2 { 3 char v2; // r0 4 int v4; // [sp+0h] [bp-450h] 5 int (__fastcall *v5)(_DWORD, _DWORD); // [sp+4h] [bp-44Ch] 6 int v6; // [sp+8h] [bp-448h] 7 int v7; // [sp+Ch] [bp-444h] 8 int *v8; // [sp+14h] [bp-43Ch] 9 int v9; // [sp+18h] [bp-438h] 10 int v10; // [sp+1Ch] [bp-434h] 11 int v11; // [sp+20h] [bp-430h] 12 int v12; // [sp+28h] [bp-428h] 13 int (__fastcall *mydlsym)(_DWORD, _DWORD); // [sp+2Ch] [bp-424h] 14 int v14; // [sp+30h] [bp-420h] 15 int v15; // [sp+34h] [bp-41Ch] 16 int v16; // [sp+434h] [bp-1Ch] 17 int v17; // [sp+438h] [bp-18h] 18 19 v17 = a1; 20 v16 = a2; 21 v12 = 0; 22 400A2255(&v15, 1024, 0); 23 while ( sub_763579D4(&v15) ) 24 ; 25 if ( !unk_7635B37C ) 26 v11 = 4007B1F9(9); 27 while ( 1 ) 28 { 29 v2 = 0; 30 if ( unk_7635B378 ) 31 v2 = 1; 32 if ( !(((unsigned __int8)v2 ^ 1) & 1) ) 33 break; 34 *((_BYTE *)&v4 - 7) = byte_7635B025 ^ 0xB5; 35 *((_BYTE *)&v4 - 8) = byte_7635B024 ^ 0x81; 36 *((_BYTE *)&v4 - 4) = byte_7635B028 ^ 0xEC; 37 *((_BYTE *)&v4 - 6) = byte_7635B026 ^ 0xD4; 38 *((_BYTE *)&v4 - 5) = byte_7635B027 ^ 0xF5; 39 *((_BYTE *)&v4 - 3) = byte_7635B029; 40 *((_BYTE *)&v4 - 13) = byte_7635B207 ^ 0xBA; 41 *((_BYTE *)&v4 - 6) = byte_7635B20E; 42 *((_BYTE *)&v4 - 14) = byte_7635B206 ^ 0x88; 43 *((_BYTE *)&v4 - 8) = byte_7635B20C ^ 0xD3; 44 *((_BYTE *)&v4 - 9) = byte_7635B20B ^ 0xC9; 45 *((_BYTE *)&v4 - 12) = byte_7635B208 ^ 0xF7; 46 *((_BYTE *)&v4 - 11) = byte_7635B209 ^ 0xED; 47 *((_BYTE *)&v4 - 16) = byte_7635B204 ^ 0xD1; 48 *((_BYTE *)&v4 - 10) = byte_7635B20A ^ 0xEB; 49 *((_BYTE *)&v4 - 15) = byte_7635B205 ^ 0xCE; 50 *((_BYTE *)&v4 - 7) = byte_7635B20D ^ 0x91; 51 v10 = log(6); 52 } 53 v9 = 6; 54 v8 = &v15; 55 v7 = sub_76354568(&v15, dword_7635B020); 56 v14 = link_dlopen(v8, 0); 57 *((_BYTE *)&v4 - 6) = unk_7635B219; 58 *((_BYTE *)&v4 - 14) = unk_7635B211 ^ 0x8B; 59 *((_BYTE *)&v4 - 7) = unk_7635B218 ^ 0xB0; 60 *((_BYTE *)&v4 - 8) = unk_7635B217 ^ 0x92; 61 *((_BYTE *)&v4 - 9) = unk_7635B216 ^ 0xCD; 62 *((_BYTE *)&v4 - 11) = unk_7635B214 ^ 0x84; 63 *((_BYTE *)&v4 - 16) = unk_7635B20F ^ 0xC3; 64 *((_BYTE *)&v4 - 12) = unk_7635B213 ^ 0xA4; 65 *((_BYTE *)&v4 - 13) = unk_7635B212 ^ 0xB1; 66 *((_BYTE *)&v4 - 10) = unk_7635B215 ^ 0x9E; 67 *((_BYTE *)&v4 - 15) = unk_7635B210 ^ 0xDB; 68 mydlsym = (int (__fastcall *)(_DWORD, _DWORD))link_dlsym(); 69 *((_BYTE *)&v4 - 7) = byte_7635B025 ^ 0xB5; 70 *((_BYTE *)&v4 - 4) = byte_7635B028 ^ 0xEC; 71 *((_BYTE *)&v4 - 3) = byte_7635B029; 72 *((_BYTE *)&v4 - 8) = byte_7635B024 ^ 0x81; 73 *((_BYTE *)&v4 - 5) = byte_7635B027 ^ 0xF5; 74 *((_BYTE *)&v4 - 6) = byte_7635B026 ^ 0xD4; 75 *((_BYTE *)&v4 - 13) = unk_7635B21D ^ 0xAA; 76 *((_BYTE *)&v4 - 12) = unk_7635B21E ^ 0xBE; 77 *((_BYTE *)&v4 - 16) = unk_7635B21A ^ 0x84; 78 *((_BYTE *)&v4 - 15) = unk_7635B21B ^ 0xC9; 79 *((_BYTE *)&v4 - 6) = unk_7635B224; 80 *((_BYTE *)&v4 - 7) = unk_7635B223 ^ 0xC7; 81 *((_BYTE *)&v4 - 14) = unk_7635B21C ^ 0xA9; 82 *((_BYTE *)&v4 - 11) = unk_7635B21F ^ 0x95; 83 *((_BYTE *)&v4 - 9) = unk_7635B221 ^ 0x9D; 84 *((_BYTE *)&v4 - 10) = unk_7635B220 ^ 0xDC; 85 *((_BYTE *)&v4 - 8) = unk_7635B222 ^ 0xC2; 86 v6 = log(v9); 87 if ( !mydlsym ) 88 return 65540; 89 v5 = mydlsym; 90 return mydlsym(v17, v16); 91 }
我们看到这段代码调用了dlopen和dlsym两个函数。dlopen打开一个动态链接库,dlsym根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的地址。使用这个函数不但可以获取函数地址,也可以获取变量地址。我们按F7根据这个函数调用,进入到如下代码处:
一个一个地查看各个调用函数,在unk_764F47C4函数中发现了有registerNatives的调用
跟进sub_76389780函数,发现偏移0x35C,这正是registerNatives相对于JNINativeInterface的偏移。在这里下断点,F9运行到这里。
我们知道RegisterNatives的函数原型是:
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods)
第二个参数是JNINativeMethod结构体
typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod;
在这里,就是v6,指向结构体数组地址:0x76AE2004。结构体的第三个成员是函数指针,指向该函数的地址。
第三个参数表示注册的函数个数,在这里为v4,其值为5,表示注册了5个native函数。如下图所示:
我们通过查看这个地址,得到这5个函数分别为:
0x76ad1839 load(Landroid/content/Context;) 0x76acdd71 runCreate(Landroid/content/Context;) 0x76acd985 changeEnv(Landroid/content/Context;) 0x76aca1a9 reciver(Landroid/content/Intent;) 0x76acd8c5 txEntries(Ldalvik/system/DexFile;)
我们把重点放在native函数load上。它在段debug175中的偏移量为0xC839。此函数用于解密隐藏在classes.dex中的数据,以获取真正DEX文件并加载它。这里要注意一点:函数指针指向的地址为0x76ad1839,则真正的函数代码在 0x76ad1839+1 位置。以下是load函数的C代码。
load函数会判断系统是Art还是dalvik,根据不同的系统进行不同的操作。因为本人的系统是4.4,所以选择sub_767B3CD0函数进行跟踪(检测dalvik和art的方法可以参考https://stackoverflow.com/questions/19830342/how-can-i-detect-the-android-runtime-dalvik-or-art)。
三、dump dex
这个函数比较长,这里只截取关键部分。找到"classes.dex"字符串,在这里下断点,F9运行到这里。代码如下:
要理解这段代码的意思,我们需要掌握以下知识点:
(1)dex header的大小固定为0x70
(2)0x28为odex文件格式中dex_header的相对偏移地址,所以(odexAddr + 0x28)为该odex文件格式中dex header的绝对地址,如图所示:
(3)dex header中的data_size和data_off字段偏移分别为0x68和0x6C,如图所示:
orgDexOffset由sub_764918CA函数得出,其计算方法如下:其中a1为(v18+ 0x28)
所以,*(a1+0x6C)和*(a1+0x68)分别表示data_size和data_off字段的取值。这两个字段的值分别为0x38C14和0x9710:
以下是对上面关键代码的说明:
1. 获取odex文件data@app@com.example.helloworld_1.apk@classes.dex的基址。
2. 通过DEX头中的data_size和data_off字段来获得真实DEX文件的偏移量。data_size为0x38C14,data_off为0x9710。偏移量为(0x38C14+0x9710+0x1000)>>12)<<12 = 0x43000。
3. 将真实的DEX的DEX头数据复制到新分配的内存中。真实DEX文件的起始地址等于odex的基地址加上0x43000和0x28。
4. 解密真实DEX文件的DEX头数据到新分配的内存中。
5. dex真实文件大小为0xA7C14。
搞明白这些后,我们就可以进行脱壳了。待dex头解密完成后,使用脚本将其dump下来,dump的起始地址为odex的基址,结束地址可以从下图找到
dump代码如下:
1 static main(void) 2 { 3 auto fp, begin, end, ptr; 4 fp = fopen("d:\\dump.odex", "wb"); 5 begin = 0x75F26000; 6 end = 0x7601A000; 7 for ( ptr = begin; ptr < end; ptr ++ ) 8 fputc(Byte(ptr), fp); 9 }
这是dump的odex文件,我们还需要dump下解密后的dex header。dump起始地址为0xBEFB73FC,大小为0x70。
然后用010editor打开odex文件,依次点击菜单“Edit”=>"Select Range",开始地址填入43028,大小填入0xA7C14,选择这段数据,然后依次点击“File”=>“Save Selection...”保存为dex格式文件。然后再用010editor打开dump的解密后的dex header,依次选择“Edit”=>"Copy As"=>"Copy as Hex Text"复制下这段数据,再打开保存的dex文件,选择dex头部数据,依次点击“Edit”=>"Paste From"=>"Paste from Hex Text",然后保存。用逆向工具打开该dex就可以看到逆向后的代码了:
上面就是乐固的脱壳过程。下面再说一下乐固2.10.3.1版本,其实,这个版本与改变并不是很多。还是按照以下步骤来进行
(1)绕过反调试
(2)找到RegisterNatives
(3)dump dex
具体的过程我这里就不详细描述了,有几个需要注意的地方说下。
下图Jni_OnLoad的部分代码,红色标记处为dlsym调用,跟进这里就可以了。
这里被加密的数据大小为0xE0,所以不再只是dex header部分,还有部分dex_string_ids数据,如图