逆向libbaiduprotect(四)

百度加固libbaiduprotect.so自身对只读字符串进行了加密保护,防止成为破解和逆向的切入口。一般地认为,只要找出这个解密算法就可以对.rodata段的只读字符串进行破解,从而窥探程序的意图。定位解密的位置不难,但是百度使用了多重匹配的手段,有力地加强了难度,因为解密的函数不是一个,而是一组。对于一个字符串只可以被其中一个解密函数解密。如果有M个字符串,N个解密函数,那么就是MxN的组合。首先你要定位出不是一处解密函数,而是N个解密函数,并且N为未知。确定后,有MxN个可能解密的字符串,必须过滤掉Mx(N-1)个不正确的解密字符串。这样还不止,百度加固还使用了另一种手段,使得破解都不在字符串的加密字节流的首字节开始进行的解密失败。

上图的左侧是一段运行中的代码片段,这段代码接踵地调用字符串解密函数,并将解密后的字符串传入下一个函数进行调用。划红线的三处就是解密函数,可以看到是不同的函数地址。在图的中间,是我的gdb调试过程,将左侧的三处函数地址分别存放在$func,$func3和$func4变量中,并类型转换成函数指针放在$f, $f3, $f4变量中。将左侧图的加密字符串地址计算出分别存放在$27, $f3_arg, $f4_arg中。分别执行$f($27), $f3($f3_arg), $f4($f4_arg),得到结果"libc.so", "_exit", "exit"。 这里还有另外两处的解密函数和加密字符串,$f1($50)和$f2($57),它的结果是"libdl.so"和"pwrite"。当我将使用$f2去解密$50时,解密失败了。解密的函数还有许多,就是开篇时说的未知数N。

下面我懒得去逆向分析,直接将其中的一个解密函数dump出来,并且在正在调试libbaiduprotect.so的移植python上映射一个执行区,然后直接去调用。

由于解密函数内部调用了其它库的函数,符号连接没有做处理,访问出错,只要处理好符号连接就可以直接使用里面的解密函数?

还不行,每个解密函数还单独对应一组密钥窗口。

下面摘取其中一个解密函数进行逆向分析:

   0xad9dd430:    push   %ebp
   0xad9dd431:    push   %ebx
   0xad9dd432:    push   %edi
   0xad9dd433:    push   %esi
   0xad9dd434:    sub    $0xc,%esp              ; 距离上一栈帧 0x1c
   0xad9dd437:    call   0xad9dd43c
   0xad9dd43c:    pop    %ebx                   ; %ebx = %eip   // 使用eip定位全局变量
   0xad9dd43d:    add    $0x72a98,%ebx           ; %ebx = 0xad9dd43c + 0x72a98
   0xad9dd443:    mov    0x20(%esp),%esi         ; %esi = arg_1   // 函数输入加密编码字符串
   0xad9dd447:    lea    -0x1(%esi),%edi         ; while(*(((char*)%edi++)+1) != 0);  // 计算加密字节流长度
   0xad9dd450:    cmpb   $0x0,0x1(%edi)
   0xad9dd454:    lea    0x1(%edi),%edi
   0xad9dd457:    jne    0xad9dd450
   0xad9dd459:    sub    %esi,%edi            ; %edi = (int) ((char*)%edi - (char*)%esi)
   0xad9dd45b:    shr    %edi                 ; %edi >>= 1
   0xad9dd45d:    lea    0x1(%edi),%eax         ; %eax = %edi + 1      // 解码字符串长度
   0xad9dd460:    mov    %eax,(%esp)
   0xad9dd463:    call   0xad9c2810 <[email protected]> ; char* var_8 = (char*) malloc(%eax)
   0xad9dd468:    mov    %eax,0x8(%esp)
   0xad9dd46c:    movb   $0x0,(%eax,%edi,1)     ; var_8 [ %edi ] = 0
   0xad9dd470:    mov    (%esi),%cl            ; %cl = *(char*)%esi
   0xad9dd472:    test   %cl,%cl               ; if (%cl != 0) {
   0xad9dd474:    je     0xad9dd4ca
   0xad9dd476:    add    $0x2,%esi               ;  %esi += 2
   0xad9dd479:    xor    %eax,%eax               ;  %eax = 0        // 常量0
   0xad9dd47b:    xor    %edi,%edi               ;  %edi = 0        // 用作计数器,[0, 7],指定解码的key
   0xad9dd47d:    mov    0x8(%esp),%ebp          ;  %ebp = var_8        // 用作遍历 var_8 的指针
   0xad9dd490:    movsbl %cl,%edx                ;  %edx = (int) %cl    // 下一轮循环入口,也就是解码下一对加密字符
   0xad9dd493:    shl    $0x4,%cl                ;  %cl <<= 4
   0xad9dd496:    cmp    $0x3a,%edx              ;  if (%edx >= 0x3a) {
   0xad9dd499:    jl     0xad9dd49e
   0xad9dd49b:    add    $0x90,%cl               ;    %cl += 0x90
   0xad9dd49e:    movsbl -0x1(%esi),%edx         ;  }  %edx = (int) *(char*)(%esi - 1)    // 紧接配对的下一个加密字符
   0xad9dd4a2:    cmp    $0x3a,%edx              ;
   0xad9dd4a5:    mov    $0xd0,%ch               ;  %ch = 0xd0
   0xad9dd4a7:    jl     0xad9dd4ab              ;  if (%edx >= 0x3a) {
   0xad9dd4a9:    mov    $0xc9,%ch               ;     %ch = 0xc9
   0xad9dd4ab:    add    %dl,%cl                 ;  }   %cl += %dl
   0xad9dd4ad:    add    %ch,%cl                 ;  %cl += %ch                // 化简 %cl += %dl + (%edi >= 0x3a) ? 0xc9 : 0xd0
   0xad9dd4af:    cmp    $0x8,%edi               ;
   0xad9dd4b2:    cmove  %eax,%edi               ;  %edi = (0x8 == %edi) ? %eax : %edi    // %eax 这时为0
   0xad9dd4b5:    xor    -0x138df(%ebx,%edi,1),%cl;  %cl ^= 一个全局变量            // 一组解码的key,每个字符的解码相应一个key,滚动使用一个key窗口,防破解
   0xad9dd4bc:    mov    %cl,0x0(%ebp)           ;  *(char*) %ebp = %cl
   0xad9dd4bf:    inc    %ebp                    ;  %ebp++
   0xad9dd4c0:    inc    %edi                    ;  %edi++
   0xad9dd4c1:    mov    (%esi),%cl              ;  %cl = *(char*) %esi     // 下一对加密字符的第一个字符
   0xad9dd4c3:    add    $0x2,%esi               ;  %esi += 2
   0xad9dd4c6:    test   %cl,%cl                 ;  if (%cl != 0)
   0xad9dd4c8:    jne    0xad9dd490              ;     goto 0xad9dd490    // 循环
   0xad9dd4ca:    mov    0x8(%esp),%eax          ; } %eax = var_8
   0xad9dd4ce:    add    $0xc,%esp               ;
   0xad9dd4d1:    pop    %esi
   0xad9dd4d2:    pop    %edi
   0xad9dd4d3:    pop    %ebx
   0xad9dd4d4:    pop    %ebp
   0xad9dd4d5:    ret                            ; return (char*) %eax

上面的反汇编逻辑并不难,完全不借助工具分析也可以手工逆向。

工作原理:

1.字符串每个字符被加密成一个双字节,并以0结束。一个n长度的字符串,它的加密字节串长度为2n,然后以0作为额外结束符。

2.解密一个字符,必须参照一双加密字节,解密出一个字节。然后还要将这个得出的字节,使用密钥字节进行异或才能最后还原出正确的字符。

3.使用一组总数为8的密钥窗口,对解密过程进行滚动使用。每个字符的解密都对应其中的一个密钥。

从上面的工作原理可以有这样的解密失配的结果:

1.当一个偶地址开始的加密字节串,如果在其中中间的某个奇地址开始进行解密的话,解密过程中,由双字节得到单字节都将是错误的,因这过程的双字节失配了。反之亦然。

2.即使双字节没有失配,如果加密字节串的起始地址没有与密钥窗口对上,同样字符的解密失败。比如说我们在加密字节串起始地址偏移了2*n个单位进行解密,则会解密错误。

3.当每个解密函数使用独立的密钥窗口时,加密字节串就必须使用相配的密钥窗口的解密函数才能将字符串解密出来。

4.当你不清楚每个加密字节串的起始地址,而暴力破解时,那不是M个字符串 x (N个解密函数 - 1) 次失配了,而 .rodata长度 x (N个解密函数)- M个字符串 次失配。尽管我们可以假定每个加密字节串的起始位于前一个加密字节串的结束符之后,来减少暴力次数,但也只是假定。

时间: 2024-10-14 09:43:25

逆向libbaiduprotect(四)的相关文章

为了CTF比赛,如何学习逆向和反汇编?

作者:无名侠链接:https://www.zhihu.com/question/23810828/answer/138696052来源:知乎著作权归作者所有,转载请联系作者获得授权. 元旦节马上就要过去,赶紧趁着12点之前写完回答. ====如果觉得本文对你有用,请点个赞并关注一下我吧~==== 我做逆向大概四年左右,虽然我没有参加过CTF,但还是可以写一些关于如何学习逆向方面的内容~ 逆向实际上是很枯燥的工作,我能从枯燥中感到快乐,这就是支撑我学习逆向的动力了. 学习逆向后有什么用?难道就仅仅

汇编语言第四章总结

终于到了自己编写一个完整的汇编语言源程序的时刻,生成可执行文件,但这要经历一个漫长的过程 下面将知识点总结如下: 一.汇编语言源程序中包含两种指令,一种是汇编指令(有对应的机器码,可被编译为机器指令,最终被CPU执行),一种是伪指令(无对应机器码,不能被CPU执行) 二.汇编源程序举例: assume cs:codesgcodesg segment mov ax,2000h mov ss,ax mov sp,0 add sp,10 pop ax pop bx push ax push bx po

【WP】MOCTF逆向题解

moctf 逆向第一题:SOEASY 这个是个 64 位的软件,OD 打不开,只能用 IDA64 打开,直接搜字符串(shift+F12)就可以看到 moctf 逆向第二题:跳跳跳 这个题当初给了初学逆向的我很大的成就感,当时就学了改指令爆破,根本不会分析算法,这就能做出一道题还是很舒服的 打开程序,是个猜数游戏 载入 OD 搜索字符串 双击跟过去看看,很清晰了,那个 jnz 是关键,只要不让这个跳转实现就正确 那就直接把他 NOP 掉吧 保存出来 随便输入,最后还是可以成功出来 flag 这个

自制反汇编逆向分析工具 迭代第四版本

上一个版本,本工具支持的第一个逆向策略,(对反汇编的函数体内的分支控制结构逆向生成cpp代码,)一直都可以正常工作,直到遇上了一个函数.这个使我的逆向策略算法失效的函数,主要的特点是它含有众多无条件跳转指令(,都是在函数体内的跳转). 为什么无条件跳转指令会使得我的第一个逆向算法失效,因为这些无条件跳转指令让函数的执行流变得不易于线性理解. 在一个没有反汇编出来的函数汇编代码中,如果无条件跳转指令很少,特殊地连一条无条件跳转指令也没有时,将汇编代码的执行流当作行文阅读,总可以找到一个特例让所有条

练习四十:数组逆向输出

练习题如下: 已知数组列表a,并且已经是排序过的,要求将a数组的元素逆向排序 排序的方法是很多的,我们这里用:将第一个元素与最后一个元素交换位置方法 1 a = [1,3,4,6,8,12,13,77] 2 for i in range(int(len(a)/2)): 3 a[i],a[len(a)-i-1] = a[len(a)-i-1],a[i] 4 print(a) 执行结果: [77, 13, 12, 8, 6, 4, 3, 1] 原文地址:https://www.cnblogs.com

逆向知识第十四讲,(C语言完结)结构体在汇编中的表现形式

一丶了解什么是结构体,以及计算结构体成员的对其值以及总大小(类也是这样算) 结构体的特性 1.结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合 2.在C语言中,结构体(struct)指的是一种数据结构,是C语言中聚合数据类型(aggregate data type)的一类. 3. 结构体可以被声明为变量.指针或数组等,用以实现较复杂的数据结构.结构体同时也是一些元素的集合,这些元素称为结构体的成员(member),且这些成员可以为不同的类型,成员一般用名字访问. 高级代

逆向课程第四讲逆向中的优化方式,除法原理,以及除法优化上

一丶为什么要熟悉除法的优化,以及除法原理 是这样的,在计算机中,除法运算对应的汇编指令分为 DIV(无符号除法指令) 以及 IDIV(有符号除法指令). 但是,除法指令的执行周期较长效率很低.所以编译器想进办法的用其它指令去代替除法指令. 比如: DIV 指令是100个周期 计算 2 / 2 那么可能在汇编中的表现形式是这样的 CDQ  符号扩展 DIV EDX,2 好,现在100个周期没有了 减法和加法指令,指令周期是4个那么上面的公式可以演化为 mov eax,2 sub eax,2 就算m

hibernate(四)__由表逆向创建Domain对象和对象关系映射文件

之前我们是手写Domain对象和对象关系映射文件->然后生成数据库中的Table. 现在我们反过来先在数据库中建好Table->然后用工具生成Domain对象和对象关系映射文件. 步骤: 1.创建一个web工程项目 2.通过myeclipse 提供的数据库浏览器连接到我们的数据库. ①新建一个数据库连接: ②配置数据库连接(这里借用以oracle一张图,后面都是在sql server的数据库下的配置,可以忽视不影响): ③成功之后输入登录密码进入数据库: 右键表可直接修改删除表,当然这也是相当

ctf.360.cn第二届,逆向部分writeup——第四题

题目:见附件 这题开始有些小复杂了. 运行程序,可以看见界面如下 注意看"隐藏信息完毕!"字符串的位置 直接搜索push 40405c,找到代码位置401a48. 网上翻找到代码块的入口地址4017a0.很显然这是一个非常长的函数,使用IDA进行静态分析.   v2 = CreateFileA(*((LPCSTR *)v1 + 24), 0x80000000u, 1u, 0, 3u, 0x80u, 0);   if ( v2 == (HANDLE)-1 )   {     result