该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函数清除花指令后的汇编代码如下:

BL __gnu_armfini_30
MOV R4, R0
MOV R0, #0x28 ; '('
BL sysconf ; 获取系统的cpu个数和可用的cpu个数
ADD R1, R4, #0x8A00
BIC R0, R4, #0xFF0
MOV R2, #
ADD R1, R1, #0xEC
BIC R0, R0, #0xF
BL __gnu_arm_fini_06
MOV R1, #0x8A00
MOV R0, R4
ADD R1, R1, #0xEC
BL __gnu_armfini_29
ADD R1, R4, #0x8A00
MOV R0, R4
ADD R1, R1, #0xEC
BL sub_B6E37614

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

_arm_aeabi_6去掉花指令后是

STMFD SP!, {R3-R8,R10,LR}
MOV R4, R0
MOV R5, R1 ;0000000C
MOV R8, R2
MOV R3, # :loc_B6EFDA74
STRB R3, [R8,R3] ;0x00 将r8开始的地址依次填充0-0x99
ADD R3, R3, # ;0+1
CMP R3, #0x100
BNE loc_B6EFDA74
MOV R3, #
MOV R6, R3
STRB R3, [R8,#0x100] ;R8+#0x100地址处的内容置0
STRB R3, [R8,#0x101] ;R8+#0x101地址处的内容置0
MOV R7, R8
ADD R10, R8, #0x100 ;R8:BEF9C6F4, R10:BEF9C7F4
MOV R0, R3 :loc_B6EFD914
LDRB R2, [R4,R0] ;r2=0x96,0xE6,0x57
LDRB R3, [R7] ;R3=0x00, R3=0xC2,0x00
ADD R0, R0, # ;r0=0x01, 0x02, 0x03
MOV R1, R5 ;R1=R5=0x0C
ADD R2, R2, R3 ;R2 = R2+R3=0xC6+0x00, R2 = R2+R3=0x96+0xC2=0x158, R2 = R2+R3=0xE6+0x00=0xE6
ADD R6, R2, R6 ;R6 = R2+R6=0xC6+0x00, R6 = R2+R6=0x158+0xC6=0x21E, R6 = R2+R6=0xE6+0x01E=0x104
AND R6, R6, #0xFF ;R6 = 0xC6, 0x01E, 0x004
LDRB R2, [R8,R6] ;r2=0x00, 0xEF, 0xE9
STRB R2, [R7],# ;STR R0,[R1], #8 将R0中的字数据写入以R1为地址的存储器 中,并将新地址R1+8写入R1。
STRB R3, [R8,R6] ;r3=0x00, 0xC2, 0x00
BL __aeabi_idivmod ;执行完后r1=r0=0x01,0x02,0x03
CMP R7, R10 ;r7=0xC2,r10=0x00
AND R0, R1, #0xFF ;r0:0x01
BNE loc_B6EFD914
LDMFD SP!, {R3-R8,R10,PC}

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

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

void initial() {
for(int i=;i<;++i){
S[i]=i;
T[i]=K[i%keylen];
}
}

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

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

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

dump脚本:

static main(void)
{
auto fp, begin, end, ptr;
fp = fopen("d:\\dump.so", "wb");
begin = 0x75ED5000;
end = begin + 0x23000;
for ( ptr = begin; ptr < end; ptr ++ )
fputc(Byte(ptr), fp);
}

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

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


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

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

第二个参数是JNINativeMethod结构体

typedef struct {
  const char* name;
  const char* signature;
  void* fnPtr;
} 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提供的方法转换。

/* java jstring turn to c/c++ char* */
char* jstringToChar(JNIEnv* env, jstring jstr)
{
char* pStr = NULL;
jclass jstrObj = (*env)->FindClass(env, "java/lang/String");
jstring encode = (*env)->NewStringUTF(env, "utf-8");
jmethodID methodId = (*env)->GetMethodID(env, jstrObj, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray byteArray = (jbyteArray)(*env)->CallObjectMethod(env, jstr, methodId, encode);
jsize strLen = (*env)->GetArrayLength(env, byteArray);
jbyte *jBuf = (*env)->GetByteArrayElements(env, byteArray, JNI_FALSE);
if (jBuf > 0)
{
pStr = (char*)malloc(strLen + 1);
if (!pStr)
{
return NULL;
}
memcpy(pStr, jBuf, strLen);
pStr[strLen] = 0;
}
env->ReleaseByteArrayElements(byteArray, jBuf, 0);
return pStr;
}

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

在__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文件了。
脚本内容:

import idaapi

def rc4(data, key):
"""RC4 encryption and decryption method."""
S, j, out = list(range(256)), 0, [] for i in range(256):
j = (j + S[i] + ord(key[i % len(key)])) % 256
S[i], S[j] = S[j], S[i] i = j = 0
for ch in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
out.append(chr(ord(ch) ^ S[(S[i] + S[j]) % 256])) return "".join(out) key=idaapi.get_many_bytes(0x20434,12)
addr= 0x176dc;
data=idaapi.get_many_bytes(addr,0x8aec)
decode=rc4(data,key)
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. Spark算子篇 --Spark算子之aggregateByKey详解

    一.基本介绍 rdd.aggregateByKey(3, seqFunc, combFunc) 其中第一个函数是初始值 3代表每次分完组之后的每个组的初始值. seqFunc代表combine的聚合逻 ...

  2. JDK1.8中的线程池

    上面这段代码一直在用,面试的时候也经常被问到,却从未深究过,不知道线程池到底是怎么回事,今天看看源代码,一探其究竟 线程池主要控制的状态是ctl,它是一个原子的整数,其包含两个概念字段: worker ...

  3. cin\cout输入输出控制

    输入输出流的控制符 控制符 作 用 dec 设置数值的基数为10 hex 设置数值的基数为16 oct 设置数值的基数为8 setfill(c) 设置填充字符c,c可以是字符常量或字符变量 setpr ...

  4. 三十分钟学会AWK

    摘要: 本文大部分内容翻译自我开始学习AWK时看到的一篇英文文章 AWK Tutorial ,觉得对AWK入门非常有帮助,所以对其进行了粗略的翻译,并对其中部分内容进行了删减或者补充,希望能为对AWK ...

  5. 使用sysbench对mysql压力测试

    sysbench是一个模块化的.跨平台.多线程基准测试工具,主要用于评估测试各种不同系统参数下的数据库负载情况.关于这个项目的详细介绍请看:https://github.com/akopytov/sy ...

  6. (2环境架设)从零开始的嵌入式图像图像处理(PI+QT+OpenCV)实战演练

    从零开始的嵌入式图像图像处理(PI+QT+OpenCV)实战演练 1综述http://www.cnblogs.com/jsxyhelu/p/7907241.html2环境架设http://www.cn ...

  7. npm package.json文件解读

    每个Nodejs项目的根目录下面,一般都会有一个package.json文件.该文件可以由npm init生成,定义了项目所需要的各种模块,以及项目的配置信息(比如名称.版本.许可证等元数据). pa ...

  8. HDU 2639 Bone Collector II(01背包变形【第K大最优解】)

    Bone Collector II Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others ...

  9. c++(hash表)

    hash表,有时候也被称为散列表.个人认为,hash表是介于链表和二叉树之间的一种中间结构.链表使用十分方便,但是数据查找十分麻烦:二叉树中的数据严格有序,但是这是以多一个指针作为代价的结果.hash ...

  10. c++(单词统计)

    在面试环节中,有一道题目也是考官们中意的一道题目:如果统计一段由字符和和空格组成的字符串中有多少个单词? 其实,之所以问这个题目,考官的目的就是想了解一下你对状态机了解多少. (1) 题目分析 从题目 ...