樂固殼分析


本文以樂固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數據,如圖

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM