首页
社区
课程
招聘
[推荐]看雪.纽盾 KCTF 2019 Q3 | 第九题点评及解题思路
发表于: 2019-10-8 14:05 3287

[推荐]看雪.纽盾 KCTF 2019 Q3 | 第九题点评及解题思路

2019-10-8 14:05
3287

从诞生之日起,就被预言为将重新一统大周帝国的王者,从小就展现的惊人天分和气魄也让所有人对他充满期待。人人都臣服他,惧怕他,养成了他高傲自大,肆意无忌的脾气。他对神神秘秘的黑衣御医徐福的实验室产生了兴趣。那是芈月太后绝对禁止他涉足的地方。布满奇奇怪怪机关仪器的房间暗无天日,像极了无边的黑夜。一伙陌生人的侵入成为改变的契机。芈月太后的左膀右臂徐福,被驱逐出王宫。自那之后第十年,嬴政终于真正执掌秦国。阳光明亮得令人炫目,照耀着他。[题目说明]Android程序,仿照一个Android勒索软件,屏蔽了加密文件的逻辑,保留了加密算法。




题目简介


本题共有989人围观,最终只有1支团队攻破成功。A2战队在比赛即将结束的前两个小时,成功破解此题。


攻破此题的战队排名一览:



这道题差一点成为本届Q3的神题,那么它究竟有多么难攻破呢?就让我们一起来揭开它神秘的面纱吧。


看雪评委crownless点评


本题是这次比赛中唯一一道安卓题。本题使用了大量的安卓加固技术,包括:多种反调试、ollvm混淆、native函数多次动态注册、简单的dex vmp虚拟机(advmp)、java层字符混淆、java部分函数插花指令、so字符串加密、java字符串加密,极大增加了分析的难度,让破解难上加难。



出题团队简介


本题出题战队 ech0:



该战队只有卓桐一名成员,这道题却难倒众人,究竟是何方神圣呢?下面是相关简介:

野生Android程序员。



设计思路


出题思路:仿照一个Android勒索软件,屏蔽了加密文件的逻辑,保留了加密算法。一般恶意软件都会做一些反逆向的工作,所以加入了反jadx、jeb等反编译工具(原本是Q2 6月准备的题目,可能工具更新后,现在效果不行了),加入了混淆、简单的花指令、反调试等,更进一步把加密算法抽取出来使用自定义的解释器执行。解题思路:虽然有混淆和字符串加密,但是字符串加密很简单。根据界面上的Decrypt按钮定位到Button控件。


Button绑定的事件,解密字符串得到"The key is correct and the decryption begins!"和"Key error!",所以第二个if分支成立的话就是输入正确。


继续往下追,根据解密后的字符串知道输入值就是flag,而且如果输入格式是falg{...}格式,只取中间的字符串。


之后调用的函数就是反jeb的函数,可以发现jeb无法解析,但是可以看smali代码。


分析发现把输入的字符串经过一个native函数返回一个字节数组,对字节数组转为hex,和"820e52333de3bcb42467f0a20564c145af5edbf2e923df33be21f0af159710c92cbc43f79f94ec930a7ae86021af5b3ae263369299de5436b85f297be08a032a28dc357391961ecc26931bfc97d67a5e74d8781fb4105b9afbe613a2041dd8c3"比较。所以关键是这个native函数。


分析该函数应该在libmydvp.so中,这个so有混淆、花指令、反调试,patch花指令、反调试后或者动态调试,追到这个函数的实现。


追到注册函数的地方,或者hook系统函数得到地址。


分析流程发现:注册jni函数,解压assets/ssspbahh.so文件解析。分析native函数,发现:有200多个分支,从解析的ssspbahh.so数据结构中取2个字节的数据,根据数据不同,进入不同的分支。进而得到指令的对应关系,可以把得到的dalvik指令填会dex,或者调试完整个函数的指令,分析出是个变形的sm4算法。



实现sm4的解密函数,解密:


//解密后

byte[] srcDtat = {-82,41,99,-40,43,-53,114,101,43,-53,114,101,-82,-23,125,68,43,-23,28,126,43,-23,28,126,-82,-23,125,68,-82,41,99,-40,41,47,54,114,43,78,3,43,38,77,3,42,126,99,28,68,99,99,-94,94,78,-41,-94,94,78,-41,28,68,99,99,-104,126,-24,38,-104,99,99,-24,28,70,-52,99,28,29,-37,54,116,116,};

//加密后数据

byte[] enDtat = {-126,14,82,51,61,-29,-68,-76,36,103,-16,-94,5,100,-63,69,-81,94,-37,-14,-23,35,-33,51,-66,33,-16,-81,21,-105,16,-55,44,-68,67,-9,-97,-108,-20,-109,10,122,-24,96,33,-81,91,58,-30,99,54,-110,-103,-34,84,54,-72,95,41,123,-32,-118,3,42,40,-36,53,115,-111,-106,30,-52,38,-109,27,-4,-105,-42,122,94,116,-40,120,31,-76,16,91,-102,-5,-26,19,-94,4,29,-40,-61,};<br>


之后参照java层的转换,得到flag{gyyfadclfcdg8822g}或gyyfadclfcdg8822g,根据题目描述的格式提交,(看雪的格式应该是去掉flag{}的格式),所以为gyyfadclfcdg8822g。解密后的数据使用base64解码后得到:请本本载不不载请+422199147软不不软卸微请要。根据代码分析  “请本本载不不载请+”= 422199147,“ 软不不软卸微请要 ”=89985746long为42219914789985746或者-42219914789985746。


挨个取16进制做为索引,得到字符。(自动生成的flag,限制的是15个字符,但是开始后发现多了2个字符,17个。最简单、最容易得到的flag为:


yyfadclfcdg88228

注:名为mysigned.apk会修改手机的桌面,如果不合要求,可采用mysigned_no_desktop.apk,不会修改手机设置。解题思路


本题解题思路由看雪论坛oooAooo提供:



一、前言


本来已经放弃了,感谢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’ 。


System.loadLibrary(OooOO0OOli.d("mydvpB393F"));

3. android.support.v4.app.o000000o入口类

1)其init函数会加载“libmyqtest.so

System.loadLibrary(OooOO0OOli.d("myqtestB2A433B"));

2)后面的流程就比较复杂,而且字符混淆非常严重,可以从输入提示入手,即"Key error!", 可以检测函数在类android.support.v4.app.O000000o中,如下:

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();
                }

可以看出OO0o0.O00000Oo(o000000o.g_string2, this.O000000o.O0000O0o.getText().toString())函数返回真即为正确。其中参数this.O000000o.O0000O0o.getText().toString()为输入的flag。下面继续跟进函数“ OO0o0.O00000Oo ”。

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()));
    }

这部分流程比较简单。

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。但是其基准字符是随机生成的,随机种子固定。部分代码如下:

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多行),起始代码也很简单,贴一下核心代码。

usleep(0x3E8u);

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();

1)创建线程sub_1D404(后面分析)2)  创建一个管道,同时调用函数 antidebugptraceFork();3)依然tracepid反调试,采用相同方法 bypass4)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解密



由于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。



- End -

合作伙伴



上海纽盾科技股份有限公司(www.newdon.net)成立于2009年,是一家以“网络安全”为主轴,以“科技源自生活,纽盾服务社会”为核心经营理念,以网络安全产品的研发、生产、销售、售后服务与相关安全服务为一体的专业安全公司,致力于为数字化时代背景下的用户提供安全产品、安全服务以及等级保护等安全解决方案。



原文链接:https://mp.weixin.qq.com/s/ODOFCLUZwZBu14lwki_CJQ



[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2019-10-10 15:00 被Editor编辑 ,原因:
收藏
免费 0
支持
分享
最新回复 (1)
雪    币: 1385
活跃值: (5609)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
2
厉害。。。。
2019-10-8 15:31
0
游客
登录 | 注册 方可回帖
返回
//