一、前言
(本来已经放弃了,感谢kanxue、作者卓桐、小编的鼓励。使得能够在快结束时提交答案)
本题使用了一些android加固技术:包括
1)多种反调试
遇到的一些困难:2)ollvm混淆
3)native函数多次动态注册
4) 简单的dex vmp虚拟机(advmp)
5) java层字符混淆
6) java部分函数插花指令
7)so 字符串加密(应该是ollvm实现的)
8)java字符串加密
1、java混淆使用0 O o之类的字符,使得分析java源码非常费劲。
2、在so中加入了很多反调试功能,尤其是vmp引擎函数的部分指令码中也加入了,过掉这些反调试虽然简单,但是比较繁琐。
3、程序中使用了大量的ollvm混淆,其中dexvmp引擎就有26K多,程序只提供arm指令so库,由于angr反混淆对arm支持的不够友好,没时间利用符号执行去掉ollvm.稍微分析下程序,可得到部分垃圾指令,将其去掉后,使用F5反汇编这个函数,花费了6个小时才完成。当然这需要更改ida配置使得max_funsition size由64k 改为1024K或者更多。即使不F5,直接分析汇编代码,IDA动态调试时其自动分析功能也需要大概15到20分钟(此时单步调试会非常卡顿,需要等IDA分析完成后才混顺畅)。也就是是说分析一个虚拟机引擎从程序开始启动,到15分钟后才能流畅的跟踪。
4、将VMP大部分指令识别后,还原出加解密代码,发现输入竟然是随机的,因为其java层将输入的flag通过一些查表变换后使用了base64进行加密,但是这个base64加密的基准字符,使用了一个random随机生成的。当再次加密时这个基准又重新随机生成,所以2次输入相同的sn,base64的结果是不样的。此时整个人都不好了。以为掉了一个坑里面,这个vmp引擎是假的,再次分析程序,包括另外的一个libmyqtest.so,也没发现端倪,果断放弃。
5、作者在群里提示说 random的随机种子是固定的,我理解的是“对于正确的sn无论输入多少次,结果都应该是正确的“,看来作者的意图是第一次输入正确就算通过。再次开始分析。这里是造成多解的一个原因:第一次可以不正确,后面几次是可能正确的。
6、由于base64的基准是随机生成的,难免会有重复的,一般base64的基准字符是不重复的, 这样加密和解密才能对应。但是随机生成的基准字符有重复的造成加密和解密的结果是不一致的,因此对于重复的字符需要进行分别尝试,这也是可能造成多解的一个原因。
7、在java层对输入的flag使用了多种编码格式的转换,使得反推flag的时候花费了点时间。
二、流程分析
(一)java部分代码分析
1 java字符串解密
反编译后可以看到大部分字符串都是加密的,加密函数为:OooOO0OOli.d,为了快速定位到检测SN入口,需要将所有加密的字符串进行解密。使用C对dex文件中的加密字符串进行解密如下():
int readUnsignedLeb128(unsigned char** pStream) { unsigned char* ptr = *pStream; int result = *(ptr++); if (result > 0x7f) { int cur = *(ptr++); result = (result & 0x7f) | ((cur & 0x7f) << 7); if (cur > 0x7f) { cur = *(ptr++); result |= (cur & 0x7f) << 14; if (cur > 0x7f) { cur = *(ptr++); result |= (cur & 0x7f) << 21; if (cur > 0x7f) { /* * Note: We don't check to see if cur is out of * range here, meaning we tolerate garbage in the * high four-order bits. */ cur = *(ptr++); result |= cur << 28; } } } } *pStream = ptr; return result; } unsigned char* GetFileNameBuffer(unsigned char* fileName, int* size) { FILE *fd; unsigned char *pName; int sign = 0; int filesize; if (NULL == fileName) { return NULL; } fd = fopen((char*)fileName, "rb"); if (NULL == fd) { return NULL; } fseek(fd, 0, SEEK_END); filesize = ftell(fd); pName = (unsigned char*)malloc(filesize + 100); if (NULL == pName) { fclose(fd); return NULL; } memset(pName, 0, filesize + 100); fseek(fd, 0, SEEK_SET); fread(pName, 1, filesize, fd); fclose(fd); *size = filesize; return pName; } bool WriteFileNameBuffer(const char* fileName, unsigned char* buf, int size) { FILE *fd; if (NULL == fileName) { return false; } unlink((const char*)fileName); fd = fopen((char*)fileName, "wb"); if (NULL == fd) { return NULL; } fwrite(buf, 1, (size_t)size, fd); fclose(fd); return true; } char dd1(char key) { if (key <= 0x39 && key >= 0x30) return key - 0x30; else if (key <= 'F' && key >= 'A') { return key - 0x37; } else { return -1; } } bool isnNeedDecryptString(char* arg8,unsigned int len) { for (unsigned int i = 0; i < len; i++) { if (dd1(arg8[i]) < 0) { return false; } } return true; } bool dexStringDecrypt(char* fileName, char* outFile) { int size; //打开dex文件 unsigned char* pbuf1 = GetFileNameBuffer((unsigned char*)"fileName", &size); if (NULL == pbuf1) return false; unsigned int stringCnt = *(unsigned int*)(pbuf1 + 0x38); unsigned int stringOffset = *(unsigned int*)(pbuf1 + 0x3c); unsigned int* stringId = (unsigned int*)(pbuf1 + stringOffset); for (unsigned int i = 0; i < 2488; i++) { if (i == 144 || i == 2145 || i == 2146 || i == 2147 || i == 2148 || i == 2308 || i == 2309 || i == 1900) continue; unsigned int off = stringId[i]; unsigned char * pbuf = pbuf1 + off; int len = readUnsignedLeb128(&pbuf); if (len <3) continue; if (false == isnNeedDecryptString((char*)pbuf, len)) continue; char* outString = decryptString((char*)pbuf, len); if (NULL == outString) continue; printf("i = %d, out = %s\n",i, outString); memcpy(pbuf, outString, strlen(outString)); } WriteFileNameBuffer(outFile, (unsigned char*)pbuf1, size); return true; }
i = 117, out = No keylines defined for i = 193, out = Keyline index i = 194, out = Keyframe i = 195, out = Key error! i = 297, out = Fragment no longer exists for key i = 302, out = FLAG_REQUEST_FILTER_KEY_EVENTS i = 476, out = CAPABILITY_CAN_FILTER_KEY_EVENTS i = 482, out = Bad fragment at key i = 681, out = The key is correct and the decryption begins! i = 758, out = Result key can't be null i = 933, out = keyframe i = 934, out = key == null i = 935, out = key == null || value == null i = 941, out = intent_extra_data_key i = 1149, out = android.arch.lifecycle.ViewModelProvider.DefaultKey: i = 1182, out = android.support.groupKey i = 1201, out = android.support.sortKey i = 1241, out = action_key i = 1397, out = resultKey
2 manifest分析
从manifest文件可知:
a)没有application
b)主activity为:android.support.v4.app.o000000o
c)注册了一个provider:android.support.v4.app.OO0OOOO0
因为 provider会在入口类走之前运行,因此第一个执行的类为 android.support.v4.app.OO0OOOO0,正其父类android.support.v4.app.O0OO00O的init函数中加载‘libmydvp.so’
3、 android.support.v4.app.o000000o入口类 1)其init函数会加载 “libmyqtest.so 2)后面的流程就比较复杂,而且字符混淆非常严重,可以从输入提示入手,即 "Key error!", 可以检测函数在类android.support.v4.app.O000000o中,如下: 可以看出OO0o0.O00000Oo(o000000o.g_string2, this.O000000o.O0000O0o.getText().toString())函数返回真即为正确。其中参数
this.O000000o.O0000O0o.getText().toString()为输入的flag。下面继续跟进函数“
OO0o0.O00000Oo
” 这部分流程比较简单 1)分析输入的sn是否是flag{xxx}格式的如果是就截取xxx。 2)调用OO0o0.O0000O0o(flag)函数。该函数实际上就是用输入的sn索引下面的表,然后转换成一个long型的字符串。 OO0o0.O0000O0o = new char[]{'d', 'c', '2', 'l', '{', '}', 'f', 'g', 'e', 'm', 'a', 'b', 'h', '0', '8', 'y'}; 3)再次使用long型的字符串进行chartobyte后索引一个常量表: OO0o0.O0000OOo = new char[]{'加', '载', '本', '件', '请', '卸', '要', '微', '软', '不', '可', '以', '来', '去', '安', 'a', '人', '7', '减', '好', 'l', '卓', '测', 'p', '试', 'p', '3', '7', '乘', '吗', 'b', '桐', 'c', 'e', '眼', 'q', '6', '4', '以', '为', '神', 'd', '无', 'f', '功', '圣', '名', '至', '己', '0', '何', '解', '忧', 'g', '唯', '有', '1', '杜', '2', '康', 'h', '}', '{'}; 并且当时9的倍数是使用'+'字符代替,并且将原始的9个字符串拷贝到之后。 4) 调用
OO0o0.O00000Oo(v3_1.toString().getBytes()) 这个函数实际上是个变形的base64。但是其基准字符是随机生成的,随机种子固定。部分代码如下: 对应第一次的生成的基准如下:System.loadLibrary(OooOO0OOli.d("mydvpB393F"));
System.loadLibrary(OooOO0OOli.d("myqtestB2A433B"));
public void onClick(View arg3) {
String v1;
O0OOOOO v3;
if(this.O000000o.O000oOo) {
v3 = this.O000000o.O0000oo0();
v1 = OooOO0OOli.d("The decryption has already started! Please don\'t touch it!083D1B0A2B6E101F2309083C0A4F2B205E683B4C1D201A0C276F593B6E");
}
else if(OO0o0.O00000Oo(o000000o.g_string2, this.O000000o.O0000O0o.getText().toString())) {
this.O000000o.O000oOo = true;
Toast.makeText(this.O000000o.O0000oo0(), OooOO0OOli.d("The key is correct and the decryption begins!F3B27556F2B090A3D161F3B265F216F0E0C2806013C6E"), 0).show();
this.O000000o.mybutton.setText(OooOO0OOli.d("In decryptionD361C1D260001"));
return;
}
else {
v3 = this.O000000o.O0000oo0();
v1 = OooOO0OOli.d("Key error!423D201E48");
}
Toast.makeText(((Context)v3), ((CharSequence)v1), 0).show();
}
2)检测函数
OO0o0.O00000Oo
public static boolean O00000Oo(String randomString, String flag) {
byte[] key;
int index = 0;
if(TextUtils.isEmpty(((CharSequence)flag))) {
return 0;
}
int v2 = 1;
if((flag.startsWith(OooOO0OOli.d("flag{E2834"))) && (flag.endsWith(OooOO0OOli.d("32")))) {
flag = flag.substring(flag.indexOf(123) + 1, flag.length() - 1);
}
flag.getBytes();
flag = OO0o0.O0000O0o(flag) + "";
System.out.println(flag);
if(flag.startsWith(OooOO0OOli.d("62"))) {
flag = flag.substring(1);
}
new byte[0];
try {
key = flag.getBytes(OooOO0OOli.d("utf-896277"));
}
catch(UnsupportedEncodingException v3) {
v3.printStackTrace();
}
StringBuilder v3_1 = new StringBuilder();
int len = key.length;
while(index < len) {
int keyValue = key[index];
int v6 = keyValue - 48;
if(v6 > 9) {
v6 = keyValue - 65;
if(v6 <= 25) {
v6 += 10;
}
else if(keyValue == 125) {
v6 = 62;
}
else {
v6 += -61;
}
}
char v5_1 = OO0o0.O0000OOo[v6];
v6 = v2 % 9;
if(v6 == 0) {
v5_1 = '+';
}
v3_1.append(v5_1);
if(v6 == 0) {
keyValue = v2 / 9;
v3_1.append(flag.substring((keyValue - 1) * 9, keyValue * 9));
}
++v2;
++index;
}
return OO0o0.O000000o(randomString, OO0o0.O00000Oo(v3_1.toString().getBytes()));
}
for(inputLen = 0; inputLen < OO0o0.seed.length; ++inputLen) {
try {
OO0o0.seed[inputLen] = ((byte)(OO0o0.O0000OoO.nextInt() % 256));
}
catch(Throwable v2_3) {
v2_3.printStackTrace();
}
catch(NumberFormatException v2_1) {
v2_1.printStackTrace();
}
}
unsigned char base64char[65] =
{ 0xa9, 0x06, 0xab, 0x48, 0x03, 0x8b, 0x08, 0xf0, 0xe5, 0x38, 0x29, 0xe9, 0x2b, 0x7e, 0x26, 0x57,
0x36, 0x75, 0xa2, 0x4d, 0x10, 0x35, 0x98, 0x3a, 0xd4, 0x63, 0x5b, 0xa3, 0x4c, 0x0e, 0xd7, 0x12,
0xdb, 0xbb, 0x1c, 0x4e, 0x9d, 0xbe, 0x1d, 0xcb, 0xcc, 0xcb, 0xb4, 0x63, 0x65, 0xcc, 0xe8, 0x46,
0x0b, 0xf4, 0x72, 0x2f, 0x2a, 0x13, 0x7d, 0xd8, 0x5e, 0x2b, 0xae, 0xb0, 0x16, 0x44, 0x63, 0xca,
0x74 };
其中最后一个字符为站位字符其数值为0x74,这里注意的是再次调用次base64函数后,这个基准数值将发生变化,重新随机生成。
5)调用OO0o0.O000000o(randomString, OO0o0.O00000Oo(v3_1.toString().getBytes()))
以一个随机产生的字符串(无用的),以及base64结果作为参数调用函数 OO0o0.O000000o。其返回如果为true则flag正确。
4、函数 :public static boolean O000000o(String paramString1, String paramString2)
public static boolean O000000o(String paramString1, String paramString2) { int j; try { paramString2 = paramString2.getBytes(OooOO0OOli.d("ISO-8859-1087A764158")); j = paramString2.length; i = 0; } catch (UnsupportedEncodingException paramString1) { paramString1.printStackTrace(); } paramString1 = new OO0OOOO().OO0OOOO(paramString2, paramString1.getBytes()); paramString2 = new OO00OO(); paramString2.O000000o = ""; int i = 0; for (;;) { if (i < paramString1.length) { StringBuilder localStringBuilder = new StringBuilder(); localStringBuilder.append(paramString2.O000000o); localStringBuilder.append(OO00OO.O00000Oo(paramString1[i])); paramString2.O000000o = localStringBuilder.toString(); i += 1; } else { boolean bool = "820e52333de3bcb42467f0a20564c145af5edbf2e923df33be21f0af159710c92cbc43f79f94ec930a7ae86021af5b3ae263369299de5436b85f297be08a032a28dc357391961ecc26931bfc97d67a5e74d8781fb4105b9afbe613a2041dd8c3".equals(paramString2.O000000o); return bool; } } while (i < j) { int k = paramString2[i]; i += 1; } }从上面的函数可以知道,最后结果为“820e52333de3bcb42467f0a20564c145af5edbf2e923df33be21f0af159710c92cbc43f79f94ec930a7ae86021af5b3ae263369299de5436b85f297be08a032a28dc357391961ecc26931bfc97d67a5e74d8781fb4105b9afbe613a2041dd8c3”时flag正确。
函数开始是进行了编码转换后执行如下语句
paramString1 = new OO0OOOO().OO0OOOO(paramString2, paramString1.getBytes());
其是一个native函数,原型如下:
public native byte[] OO0OOOO(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2);
这个native函数输入的是一个base64加密后的结果。将其返回的结果进行bytetochar转换后与上面的标定字符串比较相等即为正确。因此后续核心分析转为native函数 OO0OOOO。首先要定位到此函数。
三 libmydvp.so 初始化函数分析
1、初始化数组如下:
init_array:000E0AD8 75 4A 01 00 DCD init_sshho+1 .init_array:000E0ADC 09 2A 02 00 DCD init_tracePidStringDecode+1 .init_array:000E0AE0 91 31 02 00 DCD init_nop+1 .init_array:000E0AE4 29 5B 07 00 DCD init_decryptString+1 .init_array:000E0AE8 E9 87 07 00 DCD init_decryptString2+1 .init_array:000E0AEC A1 5D 08 00 DCD init_decrypt3+1 .init_array:000E0AF0 F5 EF 08 00 DCD init_decrypt4+1 .init_array:000E0AF4 ED 04 09 00 DCD init_decrypt5+1 .init_array:000E0AF8 B9 05 09 00 DCD int_nop1+1 .init_array:000E0AFC E9 4A 09 00 DCD init_decrypt6+1 .init_array:000E0B00 C9 1B 01 00 DCD init_g_sspbahh1+1 .init_array:000E0B04 E9 AE 01 00 DCD init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal+1 .init_array:000E0B08 55 ED 01 00 DCD init_antiDebug2_fork+1 .init_array:000E0B0C CD 1B 01 00 DCD sub_11BCC+1 .init_array:000E0B10 A9 1C 01 00 DCD sub_11CA8+1 .init_array:000E0B14 85 1D 01 00 DCD sub_11D84+1 .init_array:000E0B18 C1 1D 01 00 DCD sub_11DC0+1 .init_array:000E0B1C D1 1D 01 00 DCD sub_11DD0+1
前面的几个函数都是解密字符串的,其中包括对native函数 OO0OOOO原型的解密。
2、反调试函数init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal(1AEE8)
由于此函数使用ollvm混淆,并且加入了一些垃圾代码使得F5失效,简单写个脚本去掉相关垃圾代码,F5即可成功。如下
#coding=utf-8 import struct from idaapi import * from idc import * from idautils import * def patchNopCode(ea, endAddr, data, len): while ea < endAddr: ea = find_binary(ea, SEARCH_DOWN| SEARCH_NEXT, data, radix=16) if BADADDR == ea: break patch_bytes(ea, '\xC0\x46' * len) print 'ea = ' + str(hex(ea)) ea = ea + len def KillNop(): lajiData = '2D E9 F0 47 BD E8 F0 47 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 0E E0 10 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 E8 E7 BD E8 B1 40 ED E7 2D E9 F0 47 BD E8 F0 47' nopData = '\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46' nopCode1 = 'B1 B5 82 B0 12 46 02 B0 00 F1 01 00 A0 F1 01 00 1B 46 BD E8 B1 40 01 F1 01 01 A1 F1 01 01' lajiData2 = '2D E9 F0 40 BD E8 F0 40 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 14 E0 16 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 EF F3 00 84 10 B4 10 BC 84 F3 00 89 E2 E7 BD E8 B1 40 E7 E7 2D E9 F0 47 BD E8 F0 47 FF E7' libBase = GetLibAddr('libmydvp.so') startAddr = libBase endAddr = libBase + 0x8916A print 'startAddr= ' + str(hex(startAddr)) print 'endAddr = ' + str(hex(endAddr)) patchNopCode(startAddr, endAddr, lajiData, 35) KillNop()F5之后的代码也很多,将近2000行,只说下核心代码
pthread_create((pthread_t *)&g_antiDebug_thread, 0, (void *(*)(void *))antidebugThread1, 0); pid = getpid(); sub_B7BB0(&v230); buf_1 = std::operator|(16, 8); sub_A83CC((int)&v249, buf_1); sub_A37CC((int)&v250, pid); sub_951B4(&v249, &v230);
1、函数开始创建了一个线程antidebugThread1(174CC)这个线程后面在说。
2、然后剩下的将近1800多行就是个tracepid反调试。
3、如果tracepid的值不为0 ,则执行如下代码:
if ( v161 != -942489079 ) break; LODWORD(v181) = fd; // 反调试设置为1 HIDWORD(v181) = pid; *(_QWORD *)&g_antiDebug_Global = v181; v161 = -1048192996;
如果在调试状态下 全局变量g_antiDebug_Global(E3550)将被设置为文件句柄值,在 antidebugThread1会访问在这个值,实际上其应该是一个类似于jvm,env的指针,被设置成文件句柄后,会使程序访问其时发生异常崩溃。知道这些后过掉次函数反调试就比较容易了。(后面会有一个过掉所有反调试的脚步)
3、反调试线程 antidebugThread1(174CC)
这个函数看上去依然很大(2000多行),起始代码也很简单,贴一下核心代码。
1)其会检索 g_antiDebug_Global(E3550)如果其值为0,则调用usleep随眠。
2)如果不为0,而是之前反调试的结果为文件句柄,将发生异常程序退出。
3)如果不为0,而是一个合法的值,执行如下代码:
nativeRegisterFlag = __PAIR__(realNativeFun, decryptString50((int)&v294));
(*(void (**)(void))(*(_DWORD *)((unsigned int)&dword_E3554 & ~dword_E3554 | dword_E3554 & ~(unsigned int)&dword_E3554)
+ 860))();
其中 realNativeFun(14A9C)实际上就是之前我们要寻找的native函数。但是由于 g_antiDebug_Global = 0,所以次部分代码没有执行。
4)剩下其他1800多号代码又是个tracepid反调试功能。与 init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal类似过掉方法也一样。
4、初始化函数init_antiDebug2_fork(1ED54)
核心代码如下:
newthread = &v410; haystack = (char *)&v409; v417 = (char *)&v409; v416 = (char *)&v406; v413 = &v405; v421 = &v404; v418 = (char *)&v404; needle = (char *)&v403; v419 = &v402; v426 = &v401; v410 = pthread_self(); pipe(&g_pipe); antidebugptraceFork(); v392 = pthread_create(newthread, 0, (void *(*)(void *))sub_1D404, 0); v393 = &v411; v394 = &v409; v395 = &savedregs; v396 = -1357197669; v22 = getpid();
2) 创建一个管道,同时调用函数 antidebugptraceFork();
3)依然tracepid反调试,采用相同方法 bypass
4 antidebugptraceFork(1D580)
核心代码如下:
sprintf((char *)&v125, v24, pid); sonPid = fork();1)fork一个子进程
2)子进程调用 ptrace(0, 0, 0, 0, ststusBuf_2);
3)子进程读取tracepid,并将其值通过管道write给父进程:write(dword_E356C, &buf, 4u);
4)父进程流程比较简单,直接函数返回了。
5)父进程的线程 sub_1D404为读取管道,代码如下:
int sub_1D404() { signed int v0; // r0 int v1; // r1 __pid_t v2; // r0 int result; // r0 int v4; // [sp+8h] [bp-20h] int v5; // [sp+Ch] [bp-1Ch] v4 = -1; close(dword_E356C); read(g_pipe, &v4, 4u); sleep(1u); LABEL_3: v0 = 1819773075; while ( 1 ) { v1 = v0 & 0x7FFFFFFF; if ( (v0 & 0x7FFFFFFF) == 340946481 ) { v4 = -1; goto LABEL_3; } if ( v1 == 1314826255 ) break; if ( v1 == 1819773075 ) { read(g_pipe, &v4, 4u); v0 = 1314826255; if ( !v4 ) v0 = 340946481; } } kill(g_sonPid, 9); v2 = getpid(); kill(v2, 9); result = _stack_chk_guard - v5; if ( _stack_chk_guard == v5 ) result = 0; return result; }了解了以上反调试功能后:
首先可以直接将父进程的线程 sub_1D404杀掉,让其直接返回。然后nop掉fork指令,并使其结果r0=0就可以。
至此,初始化函数就分析完了。
四、jni_onload函数
这个函数代码量相对较小,贴出来吧
int __fastcall JNI_OnLoad(int a1) { char v1; // r1 char v2; // r1 unsigned int v3; // r2 signed int v4; // r1 signed int v5; // r0 unsigned int v6; // r6 int v7; // r0 unsigned int v8; // r12 signed int v9; // r3 signed int v10; // r4 bool v11; // zf signed int v12; // r3 int v13; // r0 struct globalInfo *v14; // r0 int v15; // r0 signed int v16; // r1 signed int v17; // r3 signed int v18; // r3 clock_t v19; // r0 signed int v20; // r0 signed int v21; // r2 struct _JNIEnv **v22; // r0 int *v23; // r1 struct _JNIEnv **v24; // r2 struct _JNIEnv *v25; // r0 clock_t clockTime2; // r0 int size; // r0 signed int v28; // r1 int result; // r0 int v30; // [sp-1Ch] [bp-84h] unsigned int v31; // [sp+4h] [bp-64h] int *g_dword_E35501; // [sp+8h] [bp-60h] _DWORD *v33; // [sp+Ch] [bp-5Ch] struct _JavaVM *JVM; // [sp+10h] [bp-58h] char v35; // [sp+16h] [bp-52h] char v36; // [sp+17h] [bp-51h] struct _JNIEnv **v37; // [sp+18h] [bp-50h] int *v38; // [sp+1Ch] [bp-4Ch] char v39; // [sp+23h] [bp-45h] clock_t clockTime1; // [sp+24h] [bp-44h] int v41; // [sp+28h] [bp-40h] v33 = &_stack_chk_guard; v1 = 0; if ( y_47 < 10 ) v1 = 1; v36 = v1; v2 = 0; if ( !((~-x_46 * x_46 ^ 0xFFFFFFFE) & ~-x_46 * x_46) ) v2 = 1; v35 = v2; JVM = (struct _JavaVM *)a1; v3 = (unsigned int)&g_antiDebug_Global & 0x1A04BD11; g_dword_E35501 = &g_antiDebug_Global; v31 = (~(unsigned int)&g_antiDebug_Global & 0xE5FB42EE | (unsigned int)&g_antiDebug_Global & 0x1A04BD11) ^ (a1 & 0x1A04BD11 | ~a1 & 0xE5FB42EE); v4 = 1974858797; while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { v5 = v4; v6 = v3; if ( v4 <= 286463644 ) break; if ( v4 > 1387993772 ) { if ( v4 > 1974858796 ) { if ( v4 == 2007388415 ) { v4 = 102368831; } else if ( v4 == 1974858797 ) { v4 = 1146459914; if ( v36 ) v4 = -217046703; if ( !v35 ) v4 = 1146459914; if ( v35 != v36 ) v4 = -217046703; } } else if ( v4 == 1387993773 ) { v3 = -1; v4 = -1667146202; } else if ( v4 == 1813294739 ) { v16 = 0; if ( y_47 < 10 ) v16 = 1; v17 = 0; if ( (~(x_46 * (x_46 - 1)) | 0xFFFFFFFE) == -1 ) v17 = 1; v11 = v16 == v17; v18 = 407986737; if ( !v11 ) v18 = -1082095904; v4 = v18; if ( (~(x_46 * (x_46 - 1)) | 0xFFFFFFFE) == -1 ) v4 = -1082095904; if ( y_47 >= 10 ) v4 = v18; } } else if ( v4 <= 719945599 ) { if ( v4 == 286463645 ) { v4 = 0xEFC89CB9; if ( v39 ) v4 = 0x52BB1AAD; } else if ( v4 == 407986737 ) { v4 = -1082095904; } } else { v3 = 65540; v4 = -1667146202; if ( v5 != 719945600 ) { if ( v5 != 1146459914 ) { v28 = 795111493; goto LABEL_92; } v30 = 0; getENV(JVM); v3 = v6; v4 = -217046703; } } } if ( v4 <= -770792315 ) break; if ( v4 > -123417836 ) { if ( v4 == 0xF8A4CB15 ) { size = readSSSAndGetSize(g_apkPath, (int)algn_E353C); v4 = -1557264936; if ( !size ) v4 = 1813294739; g_sssSize = size; v3 = v6; } else if ( v4 == 102368831 ) { v19 = clock(); v4 = 0x2AE97F80; if ( (double)(signed int)(v19 - clockTime1) / 1000000.0 > 5.0 ) v4 = 0xD20EA486; v3 = v6; } } else if ( v4 == -272065351 ) { clockTime1 = clock(); callnativeRegister(*v37); v22 = v37; v23 = g_dword_E35501; *g_dword_E35501 = v31; v24 = v37; v23[1] = (unsigned int)(v23 + 1) & ~(unsigned int)*v22 | (unsigned int)*v22 & ~(unsigned int)(v23 + 1); v25 = *v24; g_apkPath = (int)GetApkPath(); clockTime2 = clock(); v4 = 0xF8A4CB15; if ( (double)(signed int)(clockTime2 - clockTime1) / 1000000.0 > 2.0 ) v4 = 0x2F647045; v3 = v6; } else if ( v4 == -217046703 ) { v37 = (struct _JNIEnv **)&v30; v38 = &v30; v30 = 0; v7 = getENV(JVM); v8 = (x_46 * ~-x_46 ^ 0xFFFFFFFE) & x_46 * ~-x_46; v9 = 0; if ( !v8 ) v9 = 1; v10 = 0; if ( y_47 < 10 ) v10 = 1; v11 = v10 == v9; v12 = 1146459914; if ( !v11 ) v12 = 286463645; v4 = v12; if ( !v8 ) v4 = 286463645; if ( v7 ) LOBYTE(v7) = 1; v39 = v7; if ( y_47 >= 10 ) v4 = v12; v3 = v6; } else { v28 = -770792314; LABEL_92: v11 = v5 == v28; v3 = v6; v4 = v5; if ( v11 ) abort(); } } if ( v4 <= -1509585911 ) break; v4 = 102368831; if ( v5 != -1509585910 ) { v4 = v5; if ( v5 == -1082095904 ) { v4 = 407986737; if ( !(x_46 * (x_46 - 1) & (x_46 * (x_46 - 1) ^ 0xFFFFFFFE)) ) v4 = -1509585910; if ( y_47 >= 10 ) v4 = 407986737; v20 = 0; if ( !(x_46 * (x_46 - 1) & (x_46 * (x_46 - 1) ^ 0xFFFFFFFE)) ) v20 = 1; v21 = 0; if ( y_47 < 10 ) v21 = 1; if ( v21 != v20 ) v4 = -1509585910; v3 = v6; } } } if ( v4 == -1667146202 ) break; if ( v4 == 2737702360 ) { v13 = operator new(0x84u); v14 = (struct globalInfo *)sub_85FB0(v13); g_globalInfo = v14; v15 = initSSSGlobal((int *)v14, *(int *)algn_E353C, g_sssSize); v4 = 2007388415; if ( v15 ) v4 = 102368831; v3 = v6; } } result = *v33 - v41; if ( *v33 == v41 ) result = v3; return result; }
1、第一部分
1) 获得env:etENV(JVM);
2 ) 获得当前clockTime1 = clock();
3 ) 调用函数 callnativeRegister(*v37);
4 ) 调用g_apkPath = (int)GetApkPath();
5 ) 调用clockTime2 = clock();
6 ) 执行 clockTime2- clockTime1
貌似有个 时间反调试,为了方便,直接修改clock函数返回值即可。
2、 callnativeRegister函数(16CFC)
unsigned int __fastcall callnativeRegister(struct _JNIEnv *env) { char v1; // r2 char v2; // r1 unsigned int result; // r0 signed int v4; // r1 signed int v5; // r2 unsigned int v6; // r12 signed int v7; // r3 signed int v8; // r1 bool v9; // zf signed int v10; // r3 struct _JNIEnv *env1; // [sp+Ch] [bp-24h] unsigned __int8 v12; // [sp+11h] [bp-1Fh] char v13; // [sp+12h] [bp-1Eh] unsigned __int8 v14; // [sp+13h] [bp-1Dh] env1 = env; v1 = 0; v2 = 0; if ( y_45 < 10 ) v2 = 1; v13 = v2; result = (~((x_44 - 1) * x_44) | 0xFFFFFFFE) + 1; if ( (~((x_44 - 1) * x_44) | 0xFFFFFFFE) == -1 ) v1 = 1; v12 = v1; v4 = -1230185785; do { while ( 1 ) { while ( 1 ) { while ( v4 <= 1146544159 ) { switch ( v4 ) { case -1230185785: result = v12; v5 = 626878466; if ( v12 != (unsigned __int8)v13 ) v5 = 1146544160; v4 = v5; if ( v13 ) v4 = 1146544160; if ( !v12 ) v4 = v5; break; case -299872326: result = v14; v4 = 1603580720; if ( v14 ) v4 = 1673631727; break; case 626878466: result = nativeRegister(env1); v4 = 1146544160; break; } } if ( v4 != 1146544160 ) break; result = nativeRegister(env1); v6 = (x_44 * ~-x_44 ^ 0xFFFFFFFE) & x_44 * ~-x_44; v7 = 0; if ( !v6 ) v7 = 1; v8 = 0; if ( y_45 < 10 ) v8 = 1; v9 = v8 == v7; v10 = 626878466; if ( !v9 ) v10 = -299872326; v4 = v10; if ( !v6 ) v4 = -299872326; v14 = result; if ( y_45 >= 10 ) v4 = v10; } if ( v4 != 1603580720 ) break; v4 = 1673631727; } } while ( v4 != 1673631727 ); return result; }
看出是一个native注册函数,其又调用152C0进行真正的nativeregister,其部分代码如下:
v73 = NewGlobalRef(&env1->functions, v107); dword_E3558 = (unsigned int)&dword_E3558 & ~v73 | v73 & ~(unsigned int)&dword_E3558; RegisterNatives(&env1->functions, v107, v106, 1);
其实这个函数是个虚假的native函数。这里就不分析了。
3、第二部分
创建一个结构体 ,bin进行初始化
v13 = operator new(0x84u); v14 = (struct globalInfo *)sub_85FB0(v13); g_globalInfo = v14; v15 = initSSSGlobal((int *)v14, *(int *)algn_E353C, g_sssSize);
整个这部分代码的功能就是分析apk中的assets\ssspbahh.so文件。这个文件实际上是个变形的dex文件。其格式如下:
偏移 大小 功能
+0x00 8byte magic
+0x08 int 文件头大小
+0x18 int method个数
+0x1c int field type个数
+0x20 int field offset
+0x5c int[0x0e]t 每个filed type字符串长度
+0x94 char[0x0e*2] filed type字符串 offset
+0xb0 int[0x0e] field type value
+0xC8 int method对象
+0xd0 int accessflag
+0xd4 int 函数输入参数个数
+0xd8 int 寄存器个数
+0xe0 char[2] 输入参数类型字符串
+0xe4 short[0x574] 指令码(变形的smali
当然在jni_onload中会将env\jvm指针赋给 g_antiDebug_Global ,从而使得线程 antidebugThread1 再次注册jni函数:14A9C
五、native函数 14A9C 虚拟机引擎
1、反调试:
再次吐槽下这个函数的混淆。之前的反调试已经可以了,没必要再在虚拟机里面加反调试了,只是徒增工作量而已。这里增加了2中新的反跳检测:
TCP端口和 /proc/pid/wchan文件检测。下面的脚本是过掉所有反调试的
#coding=utf-8
import struct
from idaapi import *
from idc import *
from idautils import *
'''====================================================
函数名:GetLibAddr
用 途:根据模块名获得模块起始地址
备 注:此函数在SHT被破坏时将不起作用
====================================================='''
def GetLibAddr(targetName):
targetBase = 0
for i in Modules():
#print i.name
if targetName in i.name:
targetBase = int("%x"%(i.base), 16)
#print 'targetName:=' + targetName + 'targetBase:=' + str(targetBase)
break
if targetBase == 0:
#print 'targetBase None!!!'
return False
return targetBase
'''====================================================
函数名:GetSegAddrByName
用 途:根据段名获得段的起始地址
备 注:
====================================================='''
def GetSegAddrByName(targetName):
tempAddr = FirstSeg()
while tempAddr!=0:
if tempAddr == 0:
break
name = SegName(tempAddr)
if targetName == name:
return tempAddr
tempAddr = NextSeg(tempAddr)
return 0
'''====================================================
函数名:writeMemoryForAddr
用 途:向模块地址写入指定shellcode
备 注:
====================================================='''
def writeMemoryForAddr(targetName, offset, buf):
targetBase = 0
targetBase = GetSegAddrByName(targetName)
if targetBase == 0:
#print 'targetBase None!!!'
return False
addr = targetBase + offset
if not dbg_write_memory(addr, buf):
return False
refresh_debugger_memory()
return True
'''====================================================
函数名:addBptForAddr
用 途:给模块指定偏移下断点
备 注:
====================================================='''
def addBptForAddr(targetName, offset, comm):
soAddr = GetSegAddrByName(targetName)
if soAddr > 0 :
AddBpt(soAddr + offset)
SetBptAttr(soAddr + offset, BPTATTR_FLAGS, BPT_ENABLED | BPT_BRK)
MakeComm(soAddr + offset, comm)
else :
print 'create point fail'
return True
'''====================================================
函数名:DelBptForAddr
用 途:删除模块指定偏移的断点
备 注:
====================================================='''
def DelBptForAddr(targetName, offset):
soAddr = GetSegAddrByName(targetName)
if soAddr > 0 :
AddBpt(soAddr + offset)
DelBpt(soAddr + offset)
return True
'''====================================================
函数名:makenameForAddr
用 途:给指定模块函数重命名
备 注:
====================================================='''
def makenameForAddr(targetName, offset, comm):
soAddr = GetSegAddrByName(targetName)
if soAddr > 0 :
MakeName(soAddr + offset, comm)
else :
print 'makenameForAddr fail'
return True
'''====================================================
函数名:makeCommForAddr
用 途:给指定模块地址下注释
备 注:
====================================================='''
def makeCommForAddr(targetName, offset, comm):
soAddr = GetSegAddrByName(targetName)
if soAddr > 0 :
MakeComm(soAddr + offset, comm)
else :
print 'makeCommForAddr fail'
return True
'''====================================================
函数名:dumpMem
用 途:dump 指定大小的内存数据
备 注:
====================================================='''
def dumpMem(start, l, target):
block = 1024
c = l / block
left = l % block
fd = open(target, 'wb')
if c > 0:
for i in range(c):
rawdex = idaapi.dbg_read_memory(start, block)
fd.write(rawdex)
start += block
if left > 0:
rawdex = idaapi.dbg_read_memory(start, left)
print rawdex == True
fd.write(rawdex)
fd.close()
'''====================================================
函数名:doDump
用 途:通过窗口dump内存数据
备 注:
====================================================='''
def doDump():
start = AskAddr(0, 'Input Addr start in hex: ')
print('start is ' + str(hex(start)))
end = AskAddr(0, 'Input Addr end in hex: ')
print('end is ' + str(hex(end)))
l = end - start
target = AskStr('./dump.mem', 'Input the dump file path')
if l > 0 and start > 0x0 and target and AskYN(1, 'start is 0x%0x, len is %d, dump to %s' % (start, l, target)) == 1:
dumpMem(start, l, target)
print('Dump Finish')
'''====================================================
函数名:readIntFromMemory
用 途:从内存中读取一个int
备 注:
====================================================='''
def readIntFromMemory(addr):
flag=0
temp = addr
temp1= temp
if temp%2:
temp1= temp-1
flag=1
else:
temp1=temp
m4=dbg_read_memory(temp1, 4+flag)
return struct.unpack('i', m4)[0+flag]
'''====================================================
函数名:dbg_read_every_memory
用 途:从内存中读取一个int
备 注:优化过的
====================================================='''
def dbg_read_every_memory(start, len):
tempStart=start - (start%0x1000)
maxLen = (start%0x1000) + len
#print maxLen
#print tempStart
rawdex = idaapi.dbg_read_memory(tempStart, maxLen)
#print rawdex
left = start%0x1000
#print left
#print rawdex[left:maxLen]
return bytearray(rawdex[left:maxLen])
'''====================================================
函数名:readShortFromMemory
用 途:从内存中读取一个short
备 注:
====================================================='''
def readShortFromMemory(addr):
m2=dbg_read_memory(addr, 2)
return struct.unpack('h', m2)[0]
'''====================================================
函数名:readCharFromMemory
用 途:从内存中读取一个char
备 注:
====================================================='''
def readCharFromMemory(addr):
m1=dbg_read_memory(addr, 1)
return struct.unpack('c', m2)[0]
'''====================================================
函数名:readUIntFromMemory
用 途:从内存中读取一个uint
备 注:
====================================================='''
def readUIntFromMemory(addr):
m4=dbg_read_memory(addr, 4)
return struct.unpack('I', m4)[0]
'''====================================================
函数名:readUShortFromMemory
用 途:从内存中读取一个ushort
备 注:
====================================================='''
def readUShortFromMemory(addr):
m2=dbg_read_memory(addr, 2)
return struct.unpack('H', m2)[0]
'''====================================================
函数名:readUCharFromMemory
用 途:从内存中读取一个uchar
备 注:
====================================================='''
def readUCharFromMemory(addr):
m1=dbg_read_memory(addr, 1)
return struct.unpack('C', m1)[0]
'''====================================================
函数名:copyMem
用 途:拷贝内存到另外一个区域r
备 注:
====================================================='''
def copyMem(start, l, target):
addrTemp = target
print 'copy start'
block = 1024
c = l / block
left = l % block
if c > 0:
for i in range(c):
print str(hex(start))
print str(hex(addrTemp))
rawdex = idaapi.dbg_read_memory(start, block)
dbg_write_memory(addrTemp, rawdex)
refresh_debugger_memory()
start += block
addrTemp += block
if left > 0:
rawdex = idaapi.dbg_read_memory(start, left)
dbg_write_memory(addrTemp, rawdex)
refresh_debugger_memory()
print 'copy end'
'''====================================================
函数名:get_uleb128
用 途:
备 注:
====================================================='''
def get_uleb128(content):
value = 0
for i in xrange(0,5):
tmp = ord(content[i]) & 0x7f
value = tmp << (i * 7) | value
if (ord(content[i]) & 0x80) != 0x80:
break
if i == 4 and (tmp & 0xf0) != 0:
print "parse a error uleb128 number"
return -1
return i+1, value
'''====================================================
函数名:get_uleb128_forIda
用 途:
备 注:
====================================================='''
def get_uleb128_forIda(addr):
flag=0
temp = addr
if temp%2:
temp1= temp-1
flag=1
else:
temp1=temp
staticFieldSizeAddr=dbg_read_memory(temp1, 5+flag)
s,v1 = get_uleb128(staticFieldSizeAddr[flag:])
return s,v1
'''====================================================
函数名:FindData
用 途:从指定位置查找指定字节的数据
备 注:返回第一个找到的位置
====================================================='''
def FindData(addr, target):
a = -1
segStart = SegStart(addr)
segEnd = SegEnd(addr)
len=segEnd-segStart
for i in range(len):
rawdex1 = idaapi.dbg_read_memory(segStart, 1024)
a = rawdex1.find(target)
if a>= 0:
a = a + segStart
break;
segStart = segStart+512
return a
'''====================================================
函数名:FindDataEx
用 途:从指定位置查找指定字节的数据
备 注:返回第一个找到的位置
====================================================='''
def FindDataEx(addr, target,offset, target2):
a = -1
segStart = SegStart(addr)
segEnd = SegEnd(addr)
len=segEnd-segStart
for i in range(len):
rawdex1 = idaapi.dbg_read_memory(segStart, 1024)
a = rawdex1.find(target)
if a>= 0:
rawdex2 = dbg_read_every_memory(segStart+a+offset, 512)
b = rawdex2.find(target2)
if b == 0:
a = a + segStart
break;
segStart = segStart+512
return a
#set debug breakpoint
C_Linker_InitAddr0=0x18B9A
C_Linker_InitAddr=0x1892E
C_Dvm_JniOnloadAddr=0x22988C
C_Dvm_IsDebugAddr=0x2D5768
C_Dvm_NativeRegisterAddr = 0x29AD1C
C_Dvm_JarLoadedAddr=0x29EE44
'''-------------------------------------------------------------
在调试器开始时,只执行一次
-------------------------------------------------------------'''
if not 'setflag' in dir():
print 'set breakpoint'
#addBptForAddr('linker', C_Linker_InitAddr0, 'C_Linker_InitAddr0')
addBptForAddr('linker', C_Linker_InitAddr, 'C_Linker_InitAddr')
#addBptForAddr('libart.so', C_Dvm_JniOnloadAddr,'jni_onload')
#addBptForAddr('libart.so', C_Dvm_NativeRegisterAddr, 'native register') # set nativeregister
setflag = "tmp"
'''-------------------------------------------------------------
执行init 处理
-------------------------------------------------------------'''
pcValue=GetRegValue('pc')
libBase=GetLibAddr('linker')
if pcValue-libBase == C_Linker_InitAddr:
print 'C_Linker_InitAddr start'
#bypass tracepid
writeMemoryForAddr('libmydvp.so', 0x1C888, '\xc0\x46\xc0\x46\xc0\x46\xc0\x46\xc0\x46') # bypass traspid1
writeMemoryForAddr('libmydvp.so', 0x22456, '\x00\x20\xc0\x46') # bypass traspid2
writeMemoryForAddr('libmydvp.so', 0x18DFC, '\x00\x20\xc0\x46') # bypass traspidThread
writeMemoryForAddr('libmydvp.so', 0x1D404, '\x70\x47\xc0\x46') #bypass antidebugThread2
writeMemoryForAddr('libmydvp.so', 0x1D906, '\xf8\x20\xc0\x46') # bypass fork
writeMemoryForAddr('libmydvp.so', 0x49D52, '\x00\x20\xc0\x46') # bypass ooooo tracepid
writeMemoryForAddr('libmydvp.so', 0x5F09C, '\x00\x20\xc0\x46') # bypass ooooo wchan
writeMemoryForAddr('libmydvp.so', 0x67290, '\x00\x20\xc0\x46') # bypass ooooo 23946
writeMemoryForAddr('libmydvp.so', 0x68318, '\x00\x20\xc0\x46') # bypass ooooo wchan 2
writeMemoryForAddr('libmydvp.so', 0x60C96, '\x01\x20\xc0\x46') # bypass ooooo wchan 3
writeMemoryForAddr('libmydvp.so', 0x44878, '\x00\x20\xc0\x46') # bypass ooooo tracepid 2
writeMemoryForAddr('libmydvp.so', 0x4FD1A, '\x00\x20\xc0\x46') # bypass ooooo clock
writeMemoryForAddr('libmydvp.so', 0x34BD6, '\x00\x20\xc0\x46') # bypass ooooo clock 2
addBptForAddr('libmydvp.so', 0x242E0, 'ooooooooo')
addBptForAddr('libmydvp.so', 0x14A9C, 'native')
KillNop()
使用方法:
1) 在程序载入时,执行一次脚本,会在 linker调用so的初始化函数位置设置断点,这个是米8的,不同机型修改C_Linker_InitAddr=0x1892E这个值即可
2)当断在初始化位置时,再次执行脚本,会bypss所有反调试,同时bypass掉部分垃圾指令,使得F5可以成功。
3) 然后就虚拟机入口设置断点。
2、关于虚拟机的分析
分析dex的vmp虚拟机核心是找到不同opcode的处理分支,这个分支在data段off_E0B20位置具体如下:
.data.rel.ro:000E0B20 ; sub_242E0+4552A↑w .data.rel.ro:000E0B21 37 DCB 0x37 ; 7 .data.rel.ro:000E0B22 03 DCB 3 .data.rel.ro:000E0B23 00 DCB 0 .data.rel.ro:000E0B24 1E 3F 03 00 DCD loc_33F1E .data.rel.ro:000E0B28 F0 41 03 00 DCD loc_341F0 .data.rel.ro:000E0B2C FE 75 03 00 DCD loc_375FE .data.rel.ro:000E0B30 4A 3D 03 00 DCD loc_33D4A .data.rel.ro:000E0B34 F2 A8 03 00 DCD loc_3A8F2 .data.rel.ro:000E0B38 E2 A9 03 00 DCD loc_3A9E2 .data.rel.ro:000E0B3C BE D5 02 00 DCD loc_2D5BE .data.rel.ro:000E0B40 54 AA 03 00 DCD loc_3AA54 .data.rel.ro:000E0B44 2C 62 02 00 DCD loc_2622C .data.rel.ro:000E0B48 2C 62 02 00 DCD loc_2622C .data.rel.ro:000E0B4C 2C 62 02 00 DCD loc_2622C .data.rel.ro:000E0B50 2C 62 02 00 DCD loc_2622C .data.rel.ro:000E0B54 2C 62 02 00 DCD loc_2622C .data.rel.ro:000E0B58 2C 62 02 00 DCD loc_2622C .data.rel.ro:000E0B5C 78 46 03 00 DCD loc_34678 .data.rel.ro:000E0B60 48 AE 03 00 DCD loc_3AE48 .data.rel.ro:000E0B64 76 AE 03 00 DCD loc_3AE76 .data.rel.ro:000E0B68 48 96 03 00 DCD loc_39648 .data.rel.ro:000E0B6C AE AE 03 00 DCD loc_3AEAE
找到这个分支就好办了,直接在每个分支上设置断点,分析各个opcode的具体功能就可以恢复了
addBptForAddr('libmydvp.so', 0x3377C, '0x00 iput-wide vx,vy, field_id') addBptForAddr('libmydvp.so', 0x33F1E, 'ins 0x01') addBptForAddr('libmydvp.so', 0x341F0, 'ins 0x02') addBptForAddr('libmydvp.so', 0x375FE, 'ins 0x03 if-nez vx,target') addBptForAddr('libmydvp.so', 0x33D4A, '0x04 aget-byte vx,vy,vz') addBptForAddr('libmydvp.so', 0x3A8F2, 'ins 0x05') addBptForAddr('libmydvp.so', 0x3A9E2, 'ins 0x06') addBptForAddr('libmydvp.so', 0x2D5BE, '0x07 iget-wide vx,vy,field_id') addBptForAddr('libmydvp.so', 0x3AA54, '0x08 iget vx, vy, field_id') addBptForAddr('libmydvp.so', 0x2622C, 'ins 0x09') addBptForAddr('libmydvp.so', 0x2622C, 'ins 0x0a') addBptForAddr('libmydvp.so', 0x2622C, 'ins 0x0b') addBptForAddr('libmydvp.so', 0x2622C, 'ins 0x0c') addBptForAddr('libmydvp.so', 0x2622C, 'ins 0x0d') addBptForAddr('libmydvp.so', 0x2622C, 'ins 0x0e') addBptForAddr('libmydvp.so', 0x34678, '0x0f ?????') addBptForAddr('libmydvp.so', 0x3AE48, 'ins 0x10') addBptForAddr('libmydvp.so', 0x3AE76, 'ins 0x11') addBptForAddr('libmydvp.so', 0x39648, 'ins 0x12') addBptForAddr('libmydvp.so', 0x3AEAE, '0x13 if-ne vx,vy,target') addBptForAddr('libmydvp.so', 0x3AEDC, 'ins 0x14') addBptForAddr('libmydvp.so', 0x26302, 'ins 0x15') addBptForAddr('libmydvp.so', 0x3AF34, 'ins 0x16') addBptForAddr('libmydvp.so', 0x3AF7E, '0x17 if-eq vx,vy,target ') addBptForAddr('libmydvp.so', 0x3AFBE, 'ins 0x18') addBptForAddr('libmydvp.so', 0x3AFFE, 'ins 0x19') addBptForAddr('libmydvp.so', 0x2E49A, 'ins 0x1a') addBptForAddr('libmydvp.so', 0x3B048, 'ins 0x1b') addBptForAddr('libmydvp.so', 0x3B0FC, 'ins 0x1c') addBptForAddr('libmydvp.so', 0x39576, 'ins 0x1d') addBptForAddr('libmydvp.so', 0x3147E, 'ins 0x1e') addBptForAddr('libmydvp.so', 0x32FE6, 'ins 0x1f') addBptForAddr('libmydvp.so', 0x3B19C, 'ins 0x20') addBptForAddr('libmydvp.so', 0x2D26E, 'ins 0x21') addBptForAddr('libmydvp.so', 0x3B1F4, 'ins 0x22') addBptForAddr('libmydvp.so', 0x3B27A, '0x23 goto/16 target') addBptForAddr('libmydvp.so', 0x37F20, '0x24 goto target ') addBptForAddr('libmydvp.so', 0x3E14E, 'ins 0x25') addBptForAddr('libmydvp.so', 0x3B2E0, '0x26 get-quick vx,vy,offset') addBptForAddr('libmydvp.so', 0x37870, 'ins 0x27') addBptForAddr('libmydvp.so', 0x3E946, 'ins 0x28') addBptForAddr('libmydvp.so', 0x33FA2, '0x29 new-array vx,vy,type_id') addBptForAddr('libmydvp.so', 0x3B3FC, 'ins 0x2a') addBptForAddr('libmydvp.so', 0x3B434, '0x2B array-length vx,vy') addBptForAddr('libmydvp.so', 0x2524A, 'ins 0x2c') addBptForAddr('libmydvp.so', 0x2F532, 'ins 0x2d') addBptForAddr('libmydvp.so', 0x3E494, 'ins 0x2e') addBptForAddr('libmydvp.so', 0x3B48E, 'ins 0x2f') addBptForAddr('libmydvp.so', 0x3B4EE, 'ins 0x30') addBptForAddr('libmydvp.so', 0x3948C, 'ins 0x31') addBptForAddr('libmydvp.so', 0x3B546, 'ins 0x32') addBptForAddr('libmydvp.so', 0x2B056, 'ins 0x33') addBptForAddr('libmydvp.so', 0x3E32C, '0x34 const-wide vx, lit64') addBptForAddr('libmydvp.so', 0x3B5B4, 'ins 0x35') addBptForAddr('libmydvp.so', 0x3B620, '0x36 const/16 vx,lit16') addBptForAddr('libmydvp.so', 0x27908, 'ins 0x37') addBptForAddr('libmydvp.so', 0x30C46, 'ins 0x38') addBptForAddr('libmydvp.so', 0x3B6A4, '0x39 const/16 vx,lit16') addBptForAddr('libmydvp.so', 0x3B720, '0x3A const/4 vx,lit4') addBptForAddr('libmydvp.so', 0x3B7E6, 'ins 0x3b') addBptForAddr('libmydvp.so', 0x3B836, 'ins 0x3c') addBptForAddr('libmydvp.so', 0x3B896, 'ins 0x3d') addBptForAddr('libmydvp.so', 0x3B906, 'ins 0x3e') addBptForAddr('libmydvp.so', 0x3E7CA, 'ins 0x3f') addBptForAddr('libmydvp.so', 0x275F0, 'ins 0x40') addBptForAddr('libmydvp.so', 0x3B96C, 'ins 0x41') addBptForAddr('libmydvp.so', 0x3B9EC, 'ins 0x42') addBptForAddr('libmydvp.so', 0x27E20, 'ins 0x43') addBptForAddr('libmydvp.so', 0x37D52, '0x44 move/from16 vAA, vBBBB') addBptForAddr('libmydvp.so', 0x39F3A, 'ins 0x45') addBptForAddr('libmydvp.so', 0x3E4F4, 'ins 0x46') addBptForAddr('libmydvp.so', 0x3BA96, 'ins 0x47') addBptForAddr('libmydvp.so', 0x3E1DC, '0x48 move vx,vy') addBptForAddr('libmydvp.so', 0x3BB1A, 'ins 0x49') addBptForAddr('libmydvp.so', 0x3BB9A, '0x44 move/from16 vAA, vBBBB') addBptForAddr('libmydvp.so', 0x3BC26, '0x4B move vx,vy int to int') addBptForAddr('libmydvp.so', 0x3BC8C, 'ins 0x4c') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x4d') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x4e') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x4f') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x50') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x51') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x52') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x53') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x54') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x55') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x56') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x57') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x58') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x59') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x5a') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x5b') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x5c') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x5d') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x5e') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x5f') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x60') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x61') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x62') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x63') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x64') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x65') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x66') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x67') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x68') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x69') addBptForAddr('libmydvp.so', 0x31746, 'ins 0x6a') addBptForAddr('libmydvp.so', 0x3C180, 'ins 0x6b') addBptForAddr('libmydvp.so', 0x3C1DA, 'ins 0x6c') addBptForAddr('libmydvp.so', 0x3C246, 'ins 0x6d') addBptForAddr('libmydvp.so', 0x3C2AE, 'ins 0x6e') addBptForAddr('libmydvp.so', 0x3C354, 'ins 0x6f') addBptForAddr('libmydvp.so', 0x3E9B6, '0x70 div-long vx, vy, vz ') addBptForAddr('libmydvp.so', 0x2D138, 'ins 0x71') addBptForAddr('libmydvp.so', 0x2D42C, '0x72 mul-int vx, vy, vz') addBptForAddr('libmydvp.so', 0x3C3B8, 'ins 0x73') addBptForAddr('libmydvp.so', 0x3EA38, '0x74 add-int/lit8 vx,vy,lit8 ') addBptForAddr('libmydvp.so', 0x3E3EC, 'ins 0x75') addBptForAddr('libmydvp.so', 0x3C42C, 'ins 0x76') addBptForAddr('libmydvp.so', 0x397AC, '0x77 and-int/lit16 vx,vy,lit16') addBptForAddr('libmydvp.so', 0x3054E, 'ins 0x78') addBptForAddr('libmydvp.so', 0x3C4F6, 'ins 0x79') addBptForAddr('libmydvp.so', 0x3A4EE, 'ins 0x7a') addBptForAddr('libmydvp.so', 0x2A9AE, 'ins 0x7b') addBptForAddr('libmydvp.so', 0x3C550, 'ins 0x7c') addBptForAddr('libmydvp.so', 0x3C550, 'ins 0x7d') addBptForAddr('libmydvp.so', 0x3C66C, 'ins 0x7e') addBptForAddr('libmydvp.so', 0x3C71C, 'ins 0x7f') addBptForAddr('libmydvp.so', 0x3C7C2, 'ins 0x80') addBptForAddr('libmydvp.so', 0x3C880, 'ins 0x81') addBptForAddr('libmydvp.so', 0x313A2, 'ins 0x82') addBptForAddr('libmydvp.so', 0x3C8DA, 'ins 0x83') addBptForAddr('libmydvp.so', 0x34EEE, 'ins 0x84') addBptForAddr('libmydvp.so', 0x3C97C, 'ins 0x85') addBptForAddr('libmydvp.so', 0x3E72C, 'ins 0x86') addBptForAddr('libmydvp.so', 0x3CA24, 'ins 0x87') addBptForAddr('libmydvp.so', 0x3CAE0, '0x88 shr-int vx, vy, vz') addBptForAddr('libmydvp.so', 0x29B88, '0x89 shl-long/2addr vx, vy ') addBptForAddr('libmydvp.so', 0x3E838, 'ins 0x8a') addBptForAddr('libmydvp.so', 0x35932, 'ins 0x8b') addBptForAddr('libmydvp.so', 0x3CBA8, 'ins 0x8c') addBptForAddr('libmydvp.so', 0x3E57A, 'ins 0x8d') addBptForAddr('libmydvp.so', 0x373C6, 'ins 0x8e') addBptForAddr('libmydvp.so', 0x3CC84, 'ins 0x8f') addBptForAddr('libmydvp.so', 0x3E64E, 'ins 0x90') addBptForAddr('libmydvp.so', 0x3E388, 'ins 0x91') addBptForAddr('libmydvp.so', 0x2D048, 'ins 0x92') addBptForAddr('libmydvp.so', 0x3CD3A, 'ins 0x93') addBptForAddr('libmydvp.so', 0x32D22, '0x94 shl-int/2addr vx, vy') addBptForAddr('libmydvp.so', 0x3CDE4, 'ins 0x95') addBptForAddr('libmydvp.so', 0x30D26, 'ins 0x96') addBptForAddr('libmydvp.so', 0x3CE8A, 'ins 0x97') addBptForAddr('libmydvp.so', 0x3CF32, '0x98 div-int vx,vy,vz') addBptForAddr('libmydvp.so', 0x307C4, 'ins 0x99') addBptForAddr('libmydvp.so', 0x3EADE, 'ins 0x9a') addBptForAddr('libmydvp.so', 0x2EF5A, 'ins 0x9b') addBptForAddr('libmydvp.so', 0x3CFA8, '0x9c add-int/2addr vx,vy') addBptForAddr('libmydvp.so', 0x3CFA8, '0x9c add-int/2addr vx,vy') addBptForAddr('libmydvp.so', 0x3D002, 'ins 0x9e') addBptForAddr('libmydvp.so', 0x3D0CC, 'ins 0x9f') addBptForAddr('libmydvp.so', 0x327A4, 'ins 0xa0') addBptForAddr('libmydvp.so', 0x260EE, 'ins 0xa1') addBptForAddr('libmydvp.so', 0x3D18C, 'ins 0xa2') addBptForAddr('libmydvp.so', 0x3D246, 'ins 0xa3') addBptForAddr('libmydvp.so', 0x3D304, 'ins 0xa4') addBptForAddr('libmydvp.so', 0x3D3C6, 'ins 0xa5') addBptForAddr('libmydvp.so', 0x37BCE, 'ins 0xa6') addBptForAddr('libmydvp.so', 0x387F8, 'ins 0xa7') addBptForAddr('libmydvp.so', 0x3D494, '0xA8 shr-long vx,vy,vz') addBptForAddr('libmydvp.so', 0x26E2E, '0xA9 shl-long vx, vy, vz ') addBptForAddr('libmydvp.so', 0x39B4E, '0xAA xor-long vx, vy, vz ') addBptForAddr('libmydvp.so', 0x28F88, '0xAB or-long/2addr vx, vy') addBptForAddr('libmydvp.so', 0x3D574, '0xAC and-long vx, vy, vz') addBptForAddr('libmydvp.so', 0x344F6, 'ins 0xad') addBptForAddr('libmydvp.so', 0x3D67C, 'ins 0xae') addBptForAddr('libmydvp.so', 0x3D70C, 'ins 0xaf') addBptForAddr('libmydvp.so', 0x3E27C, 'ins 0xb0') addBptForAddr('libmydvp.so', 0x3D764, 'ins 0xb1') addBptForAddr('libmydvp.so', 0x3E5F4, 'ins 0xb2') addBptForAddr('libmydvp.so', 0x3E2D4, 'ins 0xb3') addBptForAddr('libmydvp.so', 0x3308E, 'ins 0xb4') addBptForAddr('libmydvp.so', 0x3D838, 'ins 0xb5') addBptForAddr('libmydvp.so', 0x3581A, 'ins 0xb6') addBptForAddr('libmydvp.so', 0x38AD6, 'ins 0xb7') addBptForAddr('libmydvp.so', 0x342FA, 'ins 0xb8') addBptForAddr('libmydvp.so', 0x361C2, 'ins 0xb9') addBptForAddr('libmydvp.so', 0x3D8E8, 'ins 0xba') addBptForAddr('libmydvp.so', 0x2AF9C, 'ins 0xbb') addBptForAddr('libmydvp.so', 0x3D97E, 'ins 0xbc') addBptForAddr('libmydvp.so', 0x3982A, 'ins 0xbd') addBptForAddr('libmydvp.so', 0x312CC, 'ins 0xbe') addBptForAddr('libmydvp.so', 0x3D9EC, '0xBF move vx,vy byte to int') addBptForAddr('libmydvp.so', 0x3DA44, 'ins 0xc0') addBptForAddr('libmydvp.so', 0x33E50, 'ins 0xc1') addBptForAddr('libmydvp.so', 0x2D97C, 'ins 0xc2') addBptForAddr('libmydvp.so', 0x3DAD0, 'ins 0xc3') addBptForAddr('libmydvp.so', 0x29652, 'ins 0xc4') addBptForAddr('libmydvp.so', 0x368E4, 'ins 0xc5') addBptForAddr('libmydvp.so', 0x3DB68, 'ins 0xc6') addBptForAddr('libmydvp.so', 0x3DBFE, 'ins 0xc7') addBptForAddr('libmydvp.so', 0x3DC5A, '0xC8 move-wide long to int') addBptForAddr('libmydvp.so', 0x3DCF4, 'ins 0xc9') addBptForAddr('libmydvp.so', 0x36CBE, 'ins 0xca') addBptForAddr('libmydvp.so', 0x3DD58, '0xCB move-wide') addBptForAddr('libmydvp.so', 0x3DDB0, 'ins 0xcc') addBptForAddr('libmydvp.so', 0x3DE52, 'ins 0xcd') addBptForAddr('libmydvp.so', 0x3DEE6, 'ins 0xce') addBptForAddr('libmydvp.so', 0x2F446, 'ins 0xcf') addBptForAddr('libmydvp.so', 0x399CA, 'ins 0xd0') addBptForAddr('libmydvp.so', 0x3DF40, 'ins 0xd1') addBptForAddr('libmydvp.so', 0x3DF40, 'ins 0xd2') addBptForAddr('libmydvp.so', 0x3DF40, 'ins 0xd3') addBptForAddr('libmydvp.so', 0x3E06E, 'ins 0xd4') addBptForAddr('libmydvp.so', 0x39E2E, 'ins 0xd5') addBptForAddr('libmydvp.so', 0x3E0E0, 'ins 0xd6') addBptForAddr('libmydvp.so', 0x324A6, 'ins 0xd7') addBptForAddr('libmydvp.so', 0x26546, 'ins 0xd8') addBptForAddr('libmydvp.so', 0x26546, 'ins 0xd9') addBptForAddr('libmydvp.so', 0x3E008, 'ins 0xda') addBptForAddr('libmydvp.so', 0x3DF98, 'ins 0xdb') addBptForAddr('libmydvp.so', 0x3394A, 'ins 0xdc') addBptForAddr('libmydvp.so', 0x3C5F6, 'ins 0xdd') addBptForAddr('libmydvp.so', 0x2622C, 'ins 0xde') addBptForAddr('libmydvp.so', 0x391E6, 'ins 0xdf') addBptForAddr('libmydvp.so', 0x39BC8, 'ins 0xe0') addBptForAddr('libmydvp.so', 0x3C128, 'ins 0xe1') addBptForAddr('libmydvp.so', 0x3C0E6, 'ins 0xe2') addBptForAddr('libmydvp.so', 0x3C076, 'ins 0xe3') addBptForAddr('libmydvp.so', 0x2A662, 'ins 0xe4') addBptForAddr('libmydvp.so', 0x3BFCE, 'ins 0xe5') addBptForAddr('libmydvp.so', 0x2E7B0, 'ins 0xe6') addBptForAddr('libmydvp.so', 0x2EBCA, 'ins 0xe7') addBptForAddr('libmydvp.so', 0x3BF68, 'ins 0xe8') addBptForAddr('libmydvp.so', 0x3BF0E, 'ins 0xe9') addBptForAddr('libmydvp.so', 0x37206, 'ins 0xea') addBptForAddr('libmydvp.so', 0x27770, 'ins 0xeb') addBptForAddr('libmydvp.so', 0x3BEB4, 'ins 0xec') addBptForAddr('libmydvp.so', 0x2CB42, 'ins 0xed') addBptForAddr('libmydvp.so', 0x3BE50, 'ins 0xee') addBptForAddr('libmydvp.so', 0x3A190, 'ins 0xef') addBptForAddr('libmydvp.so', 0x25B6E, 'ins 0xf0') addBptForAddr('libmydvp.so', 0x36B28, 'ins 0xf1') addBptForAddr('libmydvp.so', 0x2A78E, 'ins 0xf2') addBptForAddr('libmydvp.so', 0x377D6, 'ins 0xf3') addBptForAddr('libmydvp.so', 0x3BDBC, 'ins 0xf4') addBptForAddr('libmydvp.so', 0x27502, 'ins 0xf5') addBptForAddr('libmydvp.so', 0x2EE7E, 'ins 0xf6') addBptForAddr('libmydvp.so', 0x3A2FC, 'ins 0xf7') addBptForAddr('libmydvp.so', 0x38610, 'ins 0xf8') addBptForAddr('libmydvp.so', 0x3BD3C, 'ins 0xf9') addBptForAddr('libmydvp.so', 0x3BCC6, 'ins 0xfa') addBptForAddr('libmydvp.so', 0x3AD66, 'ins 0xfb') addBptForAddr('libmydvp.so', 0x37DF6, 'ins 0xfc') addBptForAddr('libmydvp.so', 0x3ACEC, '0xFD aput-char vx,vy,vz') addBptForAddr('libmydvp.so', 0x3AC14, 'ins 0xfe') addBptForAddr('libmydvp.so', 0x3AB38, 'ins 0xff')
真个变形的smali大概有40多个指令,具体如下:(可能有部分不准确,不过不影响分析):
指令 含义 0x00 iput-wide vx,vy, field_id 0x03 if-nez vx,target 0x04 aget-byte vx,vy,vz 0x07 iget-wide vx,vy,field_id 0x08 iget vx, vy, field_id 0x0f ????? 0x13 if-ne vx,vy,target 0x17 if-eq vx,vy,target 0x23 goto/16 target 0x24 goto target 0x26 get-quick vx,vy,offset 0x29 new-array vx,vy,type_id 0x2B array-length vx,vy 0x34 const-wide vx, lit64 0x36 const-wide/16 vx, lit16 0x39 const/16 vx,lit16 0x3A const/4 vx,lit4 0x48 move vx,vy 0x44 move/from16 vAA, vBBBB 0x4A move/from16 vAA, vBBBB 0x4B move vx,vy 0x70 div-long vx, vy, vz 0x72 mul-int vx, vy, vz 0x74 add-int/lit8 vx,vy,lit8 0x77 and-int/lit16 vx,vy,lit16 0x88 shr-int vx, vy, vz 0x89 shl-long/2addr vx, vy 0x94 shl-int/2addr vx, vy 0x98 div-int vx,vy,vz 0x9c add-int/2addr vx,vy 0x9d add-int/2addr vx,vy 0xA8 shr-long vx,vy,vz 0xA9 shl-long vx, vy, vz 0xAA xor-long vx, vy, vz 0xAB or-long vx, vy, vz 0xAC and-long vx, vy, vz 0xBF move vx,vy 0xC8 move-wide 0xCB move-wide 0xFD aput-char vx,vy,vz
分析到这里一种是直接将还原smali,但是其实能够还原也表示看懂了算法了,直接还原算法就可以了。
六、虚拟机算法还原
1、生成一个long g_constTable[0x20]
程序前部分代码是生成一个long型的数组。
1)是生成一个16个byte 数组:21 26 23 28 25 2A 27 2C 29 2E 2B 30 2D 32 2F 34
for(int i = 0; i < 8; i++)
{
p3[i] = 0x21 + i*2;
p3[i+1] = 0x25+i*2;
}
2)与 C6 BA B2 A3 50 33 AA 16 97 91 4D 67 DC 32 70 B2 进行亦或‘
3)然后再次与一个 long g_constTable[0x40] 与 char g_constTable1[256]进行异或及索引后生成如下常量table
8F1A4404 FFF2E7BB 1E937DC8 00708BD4 7F7F73C3 0074F871 B328B155 FFE90BBF 1009F1C3 FFDCFEB9 FFE4254A 00330BFC F2BA3ED7 0021DED8 FE2D5F02 FF9A6DF5 C917FCF0 FFFF7EED 1A19EDF0 00329985 33E7164F 0026EF98 8B35A805 FFB28997 BB562A98 FF849AFA C92B0A64 00693E49 BCC4EAC9 0004C9FE C451397E FF812002 223F99D9 FFA6F1DE 713992DA 0007FC82 0B4E883C 005F48FA 6106622D FFC51D0A E0B88ACA FFE720EF 03996681 0072D9F4 0A7CBE18 003B4522 81836FEA FF8C0AF6 062A5D20 FFE3A7D6 14BAC6DF 004C7B9E FF98252C 005BF4AD CE471681 FF9405EF BD0958EC FFE4EFA2 03A69C77 00317701 1C0A19C3 004F960B D6334DF1 FFFDF083然后开始对输入的sn进行加密,算法如下:
long long constkey[0x20] = { 0xFFF2E7BB8F1A4404, 0x00708BD41E937DC8, 0x0074F8717F7F73C3, 0xFFE90BBFB328B155, 0xFFDCFEB91009F1C3, 0x00330BFCFFE4254A, 0x0021DED8F2BA3ED7, 0xFF9A6DF5FE2D5F02, 0xFFFF7EEDC917FCF0, 0x003299851A19EDF0, 0x0026EF9833E7164F, 0xFFB289978B35A805, 0xFF849AFABB562A98, 0x00693E49C92B0A64, 0x0004C9FEBCC4EAC9, 0xFF812002C451397E, 0xFFA6F1DE223F99D9, 0x0007FC82713992DA, 0x005F48FA0B4E883C, 0xFFC51D0A6106622D, 0xFFE720EFE0B88ACA, 0x0072D9F403996681, 0x003B45220A7CBE18, 0xFF8C0AF681836FEA, 0xFFE3A7D6062A5D20, 0x004C7B9E14BAC6DF, 0x005BF4ADFF98252C, 0xFF9405EFCE471681, 0xFFE4EFA2BD0958EC, 0x0031770103A69C77, 0x004F960B1C0A19C3, 0xFFFDF083D6334DF1, }; unsigned char constkey0[256] = { 0x37, 0x23, 0xEC, 0x34, 0xA2, 0x4B, 0x65, 0xA1, 0x35, 0x9C, 0x2B, 0x46, 0x8A, 0xD2, 0x54, 0xF9, 0x43, 0xCE, 0x4A, 0x47, 0xCD, 0x4F, 0x5B, 0x78, 0xC9, 0x30, 0x69, 0xFC, 0x56, 0x95, 0xDA, 0x50, 0x74, 0x1C, 0xC4, 0xE7, 0xA3, 0x1E, 0xAE, 0xF4, 0x09, 0xDF, 0x55, 0x83, 0x6B, 0xAC, 0x5F, 0xD8, 0xFF, 0x7D, 0x6A, 0x57, 0xC3, 0x0E, 0xE9, 0xFE, 0x28, 0x03, 0x71, 0x42, 0x61, 0x68, 0xD1, 0x52, 0x01, 0xED, 0xE1, 0xA8, 0x04, 0xB3, 0x82, 0x49, 0x29, 0xB2, 0xB5, 0xC1, 0x88, 0x84, 0x86, 0x1B, 0x87, 0xB6, 0xBE, 0x58, 0x0F, 0xA0, 0x20, 0xB8, 0x3E, 0x2C, 0x63, 0xD0, 0x99, 0x9D, 0xF0, 0xAB, 0x31, 0xBD, 0x91, 0x18, 0xE0, 0x5E, 0xFD, 0x26, 0x19, 0x15, 0x67, 0xB4, 0x48, 0x92, 0x79, 0xB7, 0xF1, 0x44, 0x93, 0xC0, 0xD9, 0x77, 0x41, 0xD7, 0xF8, 0xC7, 0x60, 0xFB, 0xEB, 0x7B, 0xAF, 0xB9, 0xA5, 0x80, 0xCC, 0x24, 0x7E, 0x9B, 0xDD, 0x1D, 0x0D, 0x97, 0x4C, 0xBF, 0xEE, 0x13, 0x22, 0x45, 0x8F, 0xF3, 0x9F, 0xA7, 0x11, 0x64, 0xC6, 0x3A, 0x59, 0x89, 0xAD, 0x6F, 0xF5, 0x8B, 0xF2, 0x75, 0xFA, 0x06, 0x5C, 0x70, 0xEF, 0x2A, 0xEA, 0xCF, 0x2F, 0x08, 0x6D, 0x00, 0x73, 0xE3, 0xBC, 0x2E, 0x07, 0xE4, 0xA9, 0x33, 0xC2, 0x9E, 0x36, 0xBA, 0x85, 0x6C, 0x2D, 0x3C, 0x5A, 0x05, 0x8D, 0x25, 0x7A, 0x0C, 0x16, 0x17, 0x7C, 0x02, 0xD3, 0x5D, 0x96, 0xE2, 0x3B, 0x90, 0x53, 0x21, 0xDE, 0x76, 0x4E, 0xF6, 0xDC, 0xCB, 0x51, 0x3D, 0x0B, 0x94, 0x8E, 0xE6, 0x72, 0xC5, 0x8C, 0xA6, 0x6E, 0x7F, 0xD5, 0x9A, 0x1A, 0xA4, 0x39, 0x27, 0x1F, 0xB1, 0xE5, 0x98, 0xC8, 0x4D, 0x10, 0xDB, 0x40, 0x66, 0x3F, 0xCA, 0xF7, 0x12, 0xAA, 0xD6, 0xE8, 0x62, 0xBB, 0x38, 0x32, 0x14, 0x0A, 0xD4, 0xB0, 0x81, }; unsigned char* encode(unsigned char* key, int keylen, int* outLen) { //补齐 unsigned char left = 0x10 - keylen % 0x10; int key1Len = keylen + left; unsigned char* key1 = (unsigned char*)malloc(key1Len); unsigned char* outKey = (unsigned char*)malloc(key1Len); memset(outKey, 0x00, key1Len); memset(key1, 0x00, key1Len); long long inputKey[0x24]; memset((char*)&inputKey, 0x00, sizeof(inputKey)); for (int i = 0; i < key1Len; i++) { if (i < keylen) key1[i] = key[i]; else key1[i] = left; } int maxCnt = key1Len / 0x10; for (int m = 0; m < maxCnt; m++) { memset((char*)&inputKey, 0x00, sizeof(inputKey)); for (int j = 0; j < 4; j++) { inputKey[j] = (long long)key1[m * 0x10 + j * 4 + 0] << 24; inputKey[j] |= (long long)key1[m * 0x10 + j * 4 + 1] << 16; inputKey[j] |= (long long)key1[m * 0x10 + j * 4 + 2] << 8; inputKey[j] |= (long long)key1[m * 0x10 + j * 4 + 3] << 0; } for (int i = 0; i < 0x20; i++) { if (i == 0x1f) { int xxx = 0; } long long r15 = inputKey[i + 1] ^ inputKey[i + 2] ^ inputKey[i + 3]; long long r19 = constkey[i] ^ r15; unsigned char tempkey4[4]; tempkey4[0] = (r19 >> 24) & 0xff; tempkey4[1] = (r19 >> 16) & 0xff; tempkey4[2] = (r19 >> 8) & 0xff; tempkey4[3] = (r19)& 0xff; unsigned char tempkey5[4]; tempkey5[0] = constkey0[tempkey4[0]]; tempkey5[1] = constkey0[tempkey4[1]]; tempkey5[2] = constkey0[tempkey4[2]]; tempkey5[3] = constkey0[tempkey4[3]]; //将 tempkey5 按字节置换 long long r4 = ((long long)tempkey5[0] << 24); r4 |= ((long long)tempkey5[1] << 16); r4 |= ((long long)tempkey5[2] << 8); r4 |= (long long)tempkey5[3]; long long r13 = (r4 << 2) | (r4 >> 30); long long r16 = r13 ^ r4; r13 = (r4 << 0x0a) | (r4 >> 0x16); long long r17 = r13 ^ r16; r13 = (r4 << 0x12) | (r4 >> 0x0e); long long r18 = r13 ^ r17; r13 = (r4 << 0x18) | (r4 >> 0x8); r18 = r13 ^ r18; inputKey[i + 4] = inputKey[i] ^ r18; } for (int j = 0; j < 4; j++) { outKey[m * 0x10 + j * 4 + 0] = (inputKey[0x23 - j] >> 24) & 0x0ff; outKey[m * 0x10 + j * 4 + 1] = (inputKey[0x23 - j] >> 16) & 0x0ff; outKey[m * 0x10 + j * 4 + 2] = (inputKey[0x23 - j] >> 8) & 0x0ff; outKey[m * 0x10 + j * 4 + 3] = (inputKey[0x23 - j]) & 0x0ff; } int xx = 1; } *outLen = key1Len; return outKey; } unsigned char* decode(unsigned char* key, int keylen, int* outLen) { //补齐 unsigned char left = 0; if (keylen % 0x10) left = 0x10 - keylen % 0x10; int key1Len = keylen + left; unsigned char* key1 = (unsigned char*)malloc(key1Len+100); unsigned char* outKey = (unsigned char*)malloc(key1Len + 100); memset(outKey, 0x00, key1Len + 100); memset(key1, 0x00, key1Len + 100); long long inputKey[0x24]; memset((char*)&inputKey, 0x00, sizeof(inputKey)); for (int i = 0; i < key1Len; i++) { if (i < keylen) key1[i] = key[i]; else key1[i] = left; } int maxCnt = key1Len / 0x10; for (int m = 0; m < maxCnt; m++) { memset((char*)&inputKey, 0x00, sizeof(inputKey)); for (int j = 0; j < 4; j++) { inputKey[0x23 - j] = (long long)key1[m * 0x10 + j * 4 + 0] << 24; inputKey[0x23 - j] |= (long long)key1[m * 0x10 + j * 4 + 1] << 16; inputKey[0x23 - j] |= (long long)key1[m * 0x10 + j * 4 + 2] << 8; inputKey[0x23 - j] |= (long long)key1[m * 0x10 + j * 4 + 3] << 0; } for (int i = 0x1F; i >=0; i--) { long long r15 = inputKey[i+1] ^ inputKey[i+2] ^ inputKey[i+ 3]; long long r19 = constkey[i] ^ r15; unsigned char tempkey4[4]; tempkey4[0] = (r19 >> 24) & 0xff; tempkey4[1] = (r19 >> 16) & 0xff; tempkey4[2] = (r19 >> 8) & 0xff; tempkey4[3] = (r19)& 0xff; unsigned char tempkey5[4]; tempkey5[0] = constkey0[tempkey4[0]]; tempkey5[1] = constkey0[tempkey4[1]]; tempkey5[2] = constkey0[tempkey4[2]]; tempkey5[3] = constkey0[tempkey4[3]]; //将 tempkey5 按字节置换 long long r4 = ((long long)tempkey5[0] << 24); r4 |= ((long long)tempkey5[1] << 16); r4 |= ((long long)tempkey5[2] << 8); r4 |= (long long)tempkey5[3]; long long r13 = (r4 << 2) | (r4 >> 30); long long r16 = r13 ^ r4; r13 = (r4 << 0x0a) | (r4 >> 0x16); long long r17 = r13 ^ r16; r13 = (r4 << 0x12) | (r4 >> 0x0e); long long r18 = r13 ^ r17; r13 = (r4 << 0x18) | (r4 >> 0x8); r18 = r13 ^ r18; inputKey[i] = inputKey[i+4] ^ r18; } for (int j = 0; j < 4; j++) { outKey[m * 0x10 + j * 4 + 0] = (inputKey[j] >> 24) & 0x0ff; outKey[m * 0x10 + j * 4 + 1] = (inputKey[j] >> 16) & 0x0ff; outKey[m * 0x10 + j * 4 + 2] = (inputKey[j] >> 8) & 0x0ff; outKey[m * 0x10 + j * 4 + 3] = (inputKey[j]) & 0x0ff; } int xx = 1; } *outLen = key1Len; outKey[key1Len] = 0; return outKey; }其中 encode为加密,decode为解密。
unsigned char realKey[96] = { 0x82, 0x0e, 0x52, 0x33, 0x3d, 0xe3, 0xbc, 0xb4, 0x24, 0x67, 0xf0, 0xa2, 0x05, 0x64, 0xc1, 0x45, 0xaf, 0x5e, 0xdb, 0xf2, 0xe9, 0x23, 0xdf, 0x33, 0xbe, 0x21, 0xf0, 0xaf, 0x15, 0x97, 0x10, 0xc9, 0x2c, 0xbc, 0x43, 0xf7, 0x9f, 0x94, 0xec, 0x93, 0x0a, 0x7a, 0xe8, 0x60, 0x21, 0xaf, 0x5b, 0x3a, 0xe2, 0x63, 0x36, 0x92, 0x99, 0xde, 0x54, 0x36, 0xb8, 0x5f, 0x29, 0x7b, 0xe0, 0x8a, 0x03, 0x2a, 0x28, 0xdc, 0x35, 0x73, 0x91, 0x96, 0x1e, 0xcc, 0x26, 0x93, 0x1b, 0xfc, 0x97, 0xd6, 0x7a, 0x5e, 0x74, 0xd8, 0x78, 0x1f, 0xb4, 0x10, 0x5b, 0x9a, 0xfb, 0xe6, 0x13, 0xa2, 0x04, 0x1d, 0xd8, 0xc3 }; int outLen = 0; unsigned char* inSn = decode(realKey, 96, &outLen);执行上述代码得到如下数据:
ae 29 63 d8 2b cb 72 65 2b cb 72 65 ae e9 7d 44 2b e9 1c 7e 2b e9 1c 7e ae e9 7d 44 ae 29 63 d8 29 2f 36 72 2b 4e 03 2b 26 4d 03 2a 7e 63 1c 44 63 63 a2 5e 4e d7 a2 5e 4e d7 1c 44 63 63 98 7e e8 26 98 63 63 e8 1c 46 cc 63 1c 1d db 36 74 74当java调用native函数是传入上述数据,将会提示sn正确。
七 base64解密
1、由于base64的输入是 OO0o0.O0000OOo = new char[]{'加', '载', '本', '件', '请', '卸', '要', '微', '软', '不', '可', '以', '来', '去', '安', 'a', '人', '7', '减', '好', 'l', '卓', '测', 'p', '试', 'p', '3', '7', '乘', '吗', 'b', '桐', 'c', 'e', '眼', 'q', '6', '4', '以', '为', '神', 'd', '无', 'f', '功', '圣', '名', '至', '己', '0', '何', '解', '忧', 'g', '唯', '有', '1', '杜', '2', '康', 'h', '}', '{'};中的一个。虽然能限制部分多解,但是由于base64本身每次加密的基准字符不同,而且基准字符还存在重合的现象。所有不可避免会出现多解。
1、对于第一次输入,base64解码后得到一个字符串“ 请本本不不载请微+422991479不卸请要 ”;
2、再次逆推,得到一个long型字符串“ 42219914789985746 ”
3、可得到一个解:m}y8hm8yecc002
[培训]《安卓高级研修班》彻底搞定函数抽取型壳!现在报名得源码和安卓8.1脱壳机!10月20日深圳专场不见不散!
最后于 14小时前 被oooAooo编辑 ,原因: