该crackme主要实现都在so中,用ida加载libqihoo.so,出现以下错误

第一个错误说明是节区头部表格的表项大小错误,第二个错误是指节区头部表格的大小或偏移值错误。不管它,点击“Yes”继续加载。找到JNI_OnLoad函数,发现该函数已经加密:

我们知道so 文件加载时首先会查看 .init 或 .init_array 段是否存在,如果存在那么就先运行这段的内容,如果不存在的话那么就检查是否存在JNI_OnLoad,存在则执行。所以JNI_OnLoad的解密可能在 .init 或 .init_array 段中。
因为 .init 或者 .init_array 在 IDA 动态调试的时候是不会显示出来的,所以需要静态分析出这两段的偏移量然后动态调试的时候计算出绝对位置,然后在 make code(快捷键:c),这样才可以看到该段内的代码内容。
查看 .init_array 段的地址有两种办法:
(1).可以使用 IDA 加载 .so 文件,按ctrl+s快捷键查看 “Segments” 视图,这里会列出不同类型的代码段信息,如下图所示。

(2).可以使用二进制工具 readelf 来查看 .so 文件的结构,在 OS X 上面可以使用 greadelf 代替。

从上述(1)可以看出,并没有显示该so的.init 或者 .init_array段,所以我们需要对该so进行修复。
因为节区头部表的偏移值e_shoff为0x2118c(在0x20h处),所以根据ELF文件的结构,从该偏移值开始到文件结尾的数据为整个节区头部表。由于节区头部表项的大小(e_shentsize)固定为0x28,所以我们可以由:(0x214fb - 0x2118c + 1)/0x28 = 0x16 得出真正的节区头部表项数目(e_shnum)为0x16。
下面再来看e_shstrndx字段,我们从ELF文件中可以明显地看出,字符串表节区为最后一个节区,所以它的索引值应当为0x15。

根据上述规则将对应数值修改好后保存文件,然后再次加载修复后的so文件,已经不会报错了,按ctrl+s快捷键也可以查看到.init_array段信息了:

IDA 定位到.init_array段,可以看到.init_array段会执行__gnu_armfini_26函数

该函数含义大量花指令,为了便于分析,我们在该函数下断点,对其进行动态调试。通过动态调试,我们会发现该so中花指令与真实指令的关系如下图所示:

__gnu_armfini_26函数清除花指令后的汇编代码如下:

  1. BL __gnu_armfini_30
  2. MOV R4, R0
  3. MOV R0, #0x28 ; '('
  4. BL sysconf ; 获取系统的cpu个数和可用的cpu个数
  5. ADD R1, R4, #0x8A00
  6. BIC R0, R4, #0xFF0
  7. MOV R2, #
  8. ADD R1, R1, #0xEC
  9. BIC R0, R0, #0xF
  10. BL __gnu_arm_fini_06
  11. MOV R1, #0x8A00
  12. MOV R0, R4
  13. ADD R1, R1, #0xEC
  14. BL __gnu_armfini_29
  15. ADD R1, R4, #0x8A00
  16. MOV R0, R4
  17. ADD R1, R1, #0xEC
  18. BL sub_B6E37614

其中调用的__gnu_armfini_29函数比较可疑

_arm_aeabi_6去掉花指令后是

  1. STMFD SP!, {R3-R8,R10,LR}
  2. MOV R4, R0
  3. MOV R5, R1 ;0000000C
  4. MOV R8, R2
  5. MOV R3, #
  6.  
  7. :loc_B6EFDA74
  8. STRB R3, [R8,R3] ;0x00 r8开始的地址依次填充0-0x99
  9. ADD R3, R3, # ;0+1
  10. CMP R3, #0x100
  11. BNE loc_B6EFDA74
  12. MOV R3, #
  13. MOV R6, R3
  14. STRB R3, [R8,#0x100] ;R8+#0x100地址处的内容置0
  15. STRB R3, [R8,#0x101] ;R8+#0x101地址处的内容置0
  16. MOV R7, R8
  17. ADD R10, R8, #0x100 ;R8:BEF9C6F4, R10:BEF9C7F4
  18. MOV R0, R3
  19.  
  20. :loc_B6EFD914
  21. LDRB R2, [R4,R0] ;r2=0x96,0xE6,0x57
  22. LDRB R3, [R7] ;R3=0x00, R3=0xC2,0x00
  23. ADD R0, R0, # ;r0=0x01, 0x02, 0x03
  24. MOV R1, R5 ;R1=R5=0x0C
  25. ADD R2, R2, R3 ;R2 = R2+R3=0xC6+0x00, R2 = R2+R3=0x96+0xC2=0x158, R2 = R2+R3=0xE6+0x00=0xE6
  26. ADD R6, R2, R6 ;R6 = R2+R6=0xC6+0x00, R6 = R2+R6=0x158+0xC6=0x21E, R6 = R2+R6=0xE6+0x01E=0x104
  27. AND R6, R6, #0xFF ;R6 = 0xC6, 0x01E, 0x004
  28. LDRB R2, [R8,R6] ;r2=0x00, 0xEF, 0xE9
  29. STRB R2, [R7],# ;STR R0,[R1], #8 将R0中的字数据写入以R1为地址的存储器 中,并将新地址R1+8写入R1。
  30. STRB R3, [R8,R6] ;r3=0x00, 0xC2, 0x00
  31. BL __aeabi_idivmod ;执行完后r1=r0=0x01,0x02,0x03
  32. CMP R7, R10 ;r7=0xC2,r10=0x00
  33. AND R0, R1, #0xFF ;r0:0x01
  34. BNE loc_B6EFD914
  35. LDMFD SP!, {R3-R8,R10,PC}

这实际上就是RC4算法的第一个步骤(参考:https://www.jianshu.com/p/fcfdcc3ff9d5):

/*
初始化状态向量S和临时向量T,供keyStream方法调用
*/

  1. void initial() {
  2. for(int i=;i<;++i){
  3. S[i]=i;
  4. T[i]=K[i%keylen];
  5. }
  6. }

_gnu_arm_message函数具有rc4算法的典型特征:

_gnu_armfini_29这个函数解密从0x75EEC6DC地址开始,大小为0x8AEC内存区域的数据,实际上是对地址为“基址0x75ED5000+0x176dc”,大小为0x8AEC的内存区域数据进行解密。

解密完毕后将该so dump下来,dump的起始地址及大小可以通过ida的Modules菜单获取:

dump脚本:

  1. static main(void)
  2. {
  3. auto fp, begin, end, ptr;
  4. fp = fopen("d:\\dump.so", "wb");
  5. begin = 0x75ED5000;
  6. end = begin + 0x23000;
  7. for ( ptr = begin; ptr < end; ptr ++ )
  8. fputc(Byte(ptr), fp);
  9. }

按快捷键“shift+F2”打开脚本编写窗口写入上述脚本代码,点击“Run"运行该代码就可以在D盘根目录看到dump下来的so文件dump.so。

用IDA打开dump.so,定位到JNI_OnLoad函数,按“C”键,将数据转换成代码,按”F5“查看反编译的伪代码,按“Y”键修正变量类型,得到如下的代码:


我们知道RegisterNatives的函数原型是:

  1. jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods)

第二个参数是JNINativeMethod结构体

  1. typedef struct {
  2.   const char* name;
  3.   const char* signature;
  4.   void* fnPtr;
  5. } JNINativeMethod;

结构体的第三个参数是函数指针,该结构体的偏移地址为0x22004,定位到该偏移地址处发现没有正常把函数指针解析出来,需要进一步调试。


重新打开libqihoo.so,在 __gnu_armfini_29函数处下断点,进行动态调试。按F9运行到这里后,函数会进行内存区块解密,解密完成之后,在Modules菜单中找到libqihoo.so,点开,我们就可以看到“verify”和“JNI_OnLoad”函数了,分别下断点(注意,一定要在解密完之后,即执行完__gnu_arm_message函数后再下断点),如图所示:

再按F9就来到了JNI_OnLoad函数处

定位到JNINativeMethod结构体地址,从ida中我们可以看到该结构体函数指针指向verify函数:

继续F9,执行到verify函数。
在verify函数中调用了__gnu_Unwind_8和__gnu_Unwind_6函数。
__gnu_Unwind_8函数的作用是将jstring转换成为c/c++中的char*
从java程序中传过去的String对象在本地方法中对应的是jstring类型,jstring类型和c中的char*不同,所以如果你直接当做char*使用的话,就会出错。因此在使用之前需要将jstring转换成为c/c++中的char*,这里使用JNIEnv提供的方法转换。

  1. /* java jstring turn to c/c++ char* */
  2. char* jstringToChar(JNIEnv* env, jstring jstr)
  3. {
  4. char* pStr = NULL;
  5. jclass jstrObj = (*env)->FindClass(env, "java/lang/String");
  6. jstring encode = (*env)->NewStringUTF(env, "utf-8");
  7. jmethodID methodId = (*env)->GetMethodID(env, jstrObj, "getBytes", "(Ljava/lang/String;)[B");
  8. jbyteArray byteArray = (jbyteArray)(*env)->CallObjectMethod(env, jstr, methodId, encode);
  9. jsize strLen = (*env)->GetArrayLength(env, byteArray);
  10. jbyte *jBuf = (*env)->GetByteArrayElements(env, byteArray, JNI_FALSE);
  11. if (jBuf > 0)
  12. {
  13. pStr = (char*)malloc(strLen + 1);
  14. if (!pStr)
  15. {
  16. return NULL;
  17. }
  18. memcpy(pStr, jBuf, strLen);
  19. pStr[strLen] = 0;
  20. }
  21. env->ReleaseByteArrayElements(byteArray, jBuf, 0);
  22. return pStr;
  23. }

实际上上述转换的字符串就是用户输入的用户名、邮箱以及序列号。

在__gnu_Unwind_6函数中会判断用户输入的序列号长度是否为8。

调用__gnu_Unwind_1函数,此函数又有很多花指令

再次调用_gnu_armfini_29函数,通过前面的分析我们知道这是一个RC4加解密函数。

在这里实际上是取“用户名+邮箱”组成的字符串的的前四个字节进行加密,如图所示:

然后就是一个比较关键的函数__gnu_Unwind_4了

这个函数实际上是实现了SHA1加密算法,以下为该算法初始化缓冲区时的特征:

__gnu_Unwind_4将”用户名+邮箱“组成的字符串(包括前四个被RC4算法加密过的字符)进行SHA1加密后,通过__gnu_Unwind_11函数与用户输入的序列号进行对比,从而判断是否破解成功。

附:其它方法
我们重新打开一个ida,加载libqihoo.so,按快捷键“shift+F2”打开脚本编写窗口编写脚本对该内存区域进行解密。如下图所示:

这样得到的就是完全解密的so文件了。
脚本内容:

  1. import idaapi
  2.  
  3. def rc4(data, key):
  4. """RC4 encryption and decryption method."""
  5. S, j, out = list(range(256)), 0, []
  6.  
  7. for i in range(256):
  8. j = (j + S[i] + ord(key[i % len(key)])) % 256
  9. S[i], S[j] = S[j], S[i]
  10.  
  11. i = j = 0
  12. for ch in data:
  13. i = (i + 1) % 256
  14. j = (j + S[i]) % 256
  15. S[i], S[j] = S[j], S[i]
  16. out.append(chr(ord(ch) ^ S[(S[i] + S[j]) % 256]))
  17.  
  18. return "".join(out)
  19.  
  20. key=idaapi.get_many_bytes(0x20434,12)
  21. addr= 0x176dc;
  22. data=idaapi.get_many_bytes(addr,0x8aec)
  23. decode=rc4(data,key)
  24. idaapi.patch_bytes(addr, decode)

样本下载地址:

链接:https://pan.baidu.com/s/1pMXryhP 密码:clun

一道360 crackme的破解的更多相关文章

  1. 看雪论坛 破解exe 看雪CTF2017第一题分析-『CrackMe』-看雪安全论坛

    韩梦飞沙  韩亚飞  313134555@qq.com  yue31313  han_meng_fei_sha 逆向 黑客 破解 学习 论坛 『CrackMe』 http://bbs.pediy.co ...

  2. CrackMe破解系列第一番Acid_burn

    本文作者:gncao 楼主目前是做渗透测试的,在看一些大神的文章时,有时会看到一些关于逆向方面的知识,无奈楼主不懂逆向.但是身为搞技术的嘛,不懂就学,学不懂就再学,所以就在前些日子看了一些基础的汇编视 ...

  3. 程序破解之 API HOOK技术 z

    API HOOK,就是截获API调用的技术,在程序对一个API调用之前先执行你的函数,然后根据你的需要可以执行缺省的API调用或者进行其他处理,假设如果想截获一个进程对网络的访问,一般是几个socke ...

  4. premierepro破解

    1.安装硬解 360云盘 破解教程:mac sky/破解安装说明.rtf 破解软件:mac sky/Smart Adobe CS6 Blocker v1.1.app 安装文件:mac sky/Prem ...

  5. 破解 crackme4(深入底层抓出关键算法)

    系统 : Windows xp 程序 : crackme4 程序下载地址 :http://pan.baidu.com/s/1nu452xN 要求 : 注册机编写 使用工具 : OD & IDA ...

  6. [反汇编练习] 160个CrackMe之027

    [反汇编练习] 160个CrackMe之027. 本系列文章的目的是从一个没有任何经验的新手的角度(其实就是我自己),一步步尝试将160个CrackMe全部破解,如果可以,通过任何方式写出一个类似于注 ...

  7. [反汇编练习] 160个CrackMe之026

    [反汇编练习] 160个CrackMe之026. 本系列文章的目的是从一个没有任何经验的新手的角度(其实就是我自己),一步步尝试将160个CrackMe全部破解,如果可以,通过任何方式写出一个类似于注 ...

  8. [反汇编练习] 160个CrackMe之025

    [反汇编练习] 160个CrackMe之025. 本系列文章的目的是从一个没有任何经验的新手的角度(其实就是我自己),一步步尝试将160个CrackMe全部破解,如果可以,通过任何方式写出一个类似于注 ...

  9. [反汇编练习] 160个CrackMe之024

    [反汇编练习] 160个CrackMe之024. 本系列文章的目的是从一个没有任何经验的新手的角度(其实就是我自己),一步步尝试将160个CrackMe全部破解,如果可以,通过任何方式写出一个类似于注 ...

随机推荐

  1. 用深度学习(DNN)构建推荐系统 - Deep Neural Networks for YouTube Recommendations论文精读

    虽然国内必须FQ才能登录YouTube,但想必大家都知道这个网站.基本上算是世界范围内视频领域的最大的网站了,坐拥10亿量级的用户,网站内的视频推荐自然是一个非常重要的功能.本文就focus在YouT ...

  2. Python进阶内容(五)--- type和object的关系

    面向对象编程(OOP)的两大关系 继承与实现 继承关系: 子类继承自父类(base),可以使用父类的一些方法(method)和属性(attribute) 实现关系: 以类为模板,实例化一个对象,即:对 ...

  3. 【RMQ】洛谷P3379 RMQ求LCA

    题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...

  4. python开发_python中str.format()

    格式化一个字符串的输出结果,我们在很多地方都可以看到,如:c/c++中都有见过 下面看看python中的字符串格式函数str.format(): 1 #使用str.format()函数 2 3 #使用 ...

  5. promise间隔时间添加dom

    <!DOCTYPE html> <html> <head> <title></title> </head> <body&g ...

  6. [转]正则匹配时对象必须为string or bytes-like object

    逛segmentfault时碰到这个问题,发现早就在stackoverflow上被解决了. 报错:Expected string or bytes-like object 只需将传递的对象转成字符串就 ...

  7. 【请您听我说】PHP语法特点的一些看法

    一.基本认识 PHP是干什么的?百度百科上提到说:PHP就是一门脚本语言,开发用的,相信这个你们只要去搜一下,就会有一大堆关于PHP概念的解释. 相信我们对PHP的初步认识是从浏览器开始的吧,当我们每 ...

  8. linux 操作系统/xxx目录下都是什么文件?

    /bin:存放最常用命令: /dev:设备文件: /etc:存放各种配置文件: /home:用户主目录: /lib:系统最基本的动态链接共享库: /mnt:一般是空的,用来临时挂载别的文件系统: /b ...

  9. 向ajaxform和ajaxgrid中添加数据

    --ajaxform function add(){ $.request({ action:"add", success:onaddcomplete }); } function ...

  10. Linux的ls命令在Windows中的应用

    Linux的ls命令在Windows中的应用 注:ls是Linux中的命令.其作用是列出当前目录下的文件与文件夹.效果等同于Wndows中的dir指令. 如下图 下面是详细步骤 步骤一.在桌面新建一个 ...