羽夏逆向指引——破解第一个程序

写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏逆向指引——序 ,方便学习本教程。

小白鼠

  本次示例使用C++,也就是编译型语言,为了方便查看汇编代码和编码故使用VS 2022,其他IDE即可,代码如下:

#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
    int x = 0;

    cout << "请输入密钥:" << endl;
    cin >> x;
    if (x == 1234)
    {
        cout << "成功,By.寂静的羽夏,CNBLOG Only!!!" << endl;
    }
    else
    {
        cout << "失败,By.寂静的羽夏,CNBLOG Only!!!" << endl;
    }

    system("pause");
    return 0;
}

  是不是代码很简单?判断你输入的是不是1234,是的话就是成功,反之失败。这是最简单的校验示例了。

分析

  代码的流程十分简单,我们可以用下面的流程图进行表示:

graph TD; A("程序开始")-->B("显示信息,提示输入")-->C[/"获取输入"/]-->D{"判断是否和 1234 相等"}; D--YES-->E("输出正确信息")-->F("程序结束"); D-.NO.->G("输出错误信息")-->F;

  由于是初次分析编译型程序,我们直接在IDE先看看它的反汇编:

#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
00E42550  push        ebp
00E42551  mov         ebp,esp
00E42553  sub         esp,0D0h
00E42559  push        ebx
00E4255A  push        esi
00E4255B  push        edi
00E4255C  lea         edi,[ebp-10h]
00E4255F  mov         ecx,4
00E42564  mov         eax,0CCCCCCCCh
00E42569  rep stos    dword ptr es:[edi]
00E4256B  mov         eax,dword ptr [__security_cookie (0E4C004h)]
00E42570  xor         eax,ebp
00E42572  mov         dword ptr [ebp-4],eax
00E42575  mov         ecx,offset _3226632D_ConsoleApplication3@cpp (0E4F066h)
00E4257A  call        @__CheckForDebuggerJustMyCode@4 (0E41384h)
    int x = 0;
00E4257F  mov         dword ptr [x],0

    cout << "请输入密钥:" << endl;
00E42586  mov         esi,esp
00E42588  push        offset std::endl<char,std::char_traits<char> > (0E4103Ch)
00E4258D  push        offset string "\xc7\xeb\xca\xe4\xc8\xeb\xc3\xdc\xd4\xbf\xa3\xba" (0E49B30h)
00E42592  mov         eax,dword ptr [__imp_std::cout (0E4D0DCh)]
00E42597  push        eax
00E42598  call        std::operator<<<std::char_traits<char> > (0E411A9h)
00E4259D  add         esp,8
00E425A0  mov         ecx,eax
00E425A2  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E4D0A8h)]
00E425A8  cmp         esi,esp
00E425AA  call        __RTC_CheckEsp (0E4128Fh)
    cin >> x;
00E425AF  mov         esi,esp
00E425B1  lea         eax,[x]
00E425B4  push        eax
00E425B5  mov         ecx,dword ptr [__imp_std::cin (0E4D098h)]
00E425BB  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (0E4D09Ch)]
00E425C1  cmp         esi,esp
00E425C3  call        __RTC_CheckEsp (0E4128Fh)
    if (x == 1234)
00E425C8  cmp         dword ptr [x],4D2h
00E425CF  jne         __$EncStackInitStart+0A0h (0E425FCh)
    {
        cout << "成功,By.寂静的羽夏,CNBLOG Only!!!" << endl;
00E425D1  mov         esi,esp
00E425D3  push        offset std::endl<char,std::char_traits<char> > (0E4103Ch)
00E425D8  push        offset string "\xb3\xc9\xb9\xa6\xa3\xacBy.\xbc\xc5\xbe\xb2\xb5\xc4\xd3\xf0\xcf\xc4\xa3\xacCNBLOG Onl@"... (0E49B64h)
00E425DD  mov         eax,dword ptr [__imp_std::cout (0E4D0DCh)]
00E425E2  push        eax
00E425E3  call        std::operator<<<std::char_traits<char> > (0E411A9h)
00E425E8  add         esp,8
00E425EB  mov         ecx,eax
00E425ED  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E4D0A8h)]
00E425F3  cmp         esi,esp
00E425F5  call        __RTC_CheckEsp (0E4128Fh)
    }
00E425FA  jmp         __$EncStackInitStart+0C9h (0E42625h)
    else
    {
        cout << "失败,By.寂静的羽夏,CNBLOG Only!!!" << endl;
00E425FC  mov         esi,esp
00E425FE  push        offset std::endl<char,std::char_traits<char> > (0E4103Ch)  
00E42603  push        offset string "\xca\xa7\xb0\xdc\xa3\xacBy.\xbc\xc5\xbe\xb2\xb5\xc4\xd3\xf0\xcf\xc4\xa3\xacCNBLOG Onl@"... (0E49D40h)
00E42608  mov         eax,dword ptr [__imp_std::cout (0E4D0DCh)]
00E4260D  push        eax
00E4260E  call        std::operator<<<std::char_traits<char> > (0E411A9h)
00E42613  add         esp,8
00E42616  mov         ecx,eax
00E42618  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E4D0A8h)]
00E4261E  cmp         esi,esp
00E42620  call        __RTC_CheckEsp (0E4128Fh)
    }

    system("pause");
00E42625  mov         esi,esp
00E42627  push        offset string "pause" (0E49B40h)
00E4262C  call        dword ptr [__imp__system (0E4D1D4h)]
00E42632  add         esp,4
00E42635  cmp         esi,esp
00E42637  call        __RTC_CheckEsp (0E4128Fh)
    return 0;
00E4263C  xor         eax,eax
}

  看不懂汇编的意思的,请自学汇编。看不懂整体汇编语言的作用,请学习我的教程 羽夏看C语言 即可看懂。
  输出信息最关键的当然是判断了,也就是下面这个部分:

    if (x == 1234)
00E425C8  cmp         dword ptr [x],4D2h
00E425CF  jne         __$EncStackInitStart+0A0h (0E425FCh)

  如果我把jne给干掉,无论我输入什么,都会输出正确。也就是我们把判断失败的链条给砍断了,只能走这条路。

graph TD; A("程序开始")-->B("显示信息,提示输入")-->C[/"获取输入"/]-->D{"判断是否和 1234 相等"}; D--->E("输出正确信息")-->F("程序结束"); G("输出错误信息")-->F("程序结束");

  上面只改汇编代码的,通过修改汇编以修改代码执行流程实现自己的目的,就是我们所谓的暴力破解,也就是patch,俗称打补丁。如果我逆向分析得到密钥就是1234,直接输入,这东西也就是所谓的key。当然,现实中验证不可能这么简单,它们往往是通过某种算法,通过获取计算机独特的信息生成注册码(如果我们能够知道算法是什么,根据算法来写出算key的软件,俗称注册机),然后根据校验函数进行校验,大大增大了分析难度。这就是说为什么爆破比注册机简单很多。
  但是爆破也不是那么轻松的,如果在校验函数中生成了大量的中间结果,如果它再拿来这东西校验的话,被破解的难度就会提高一个层次。发现被破解,直接退出程序,这往往是付费软件防破解的一个手段。
  当然,我们破解软件的时候不可能是拿到别人的源码按照本篇文章开头那样进行分析。我们来真枪实战一下。假设这个软件是我们未知的,如何分析这个程序。

小试牛刀

  本小节是按照常规套路进行分析,分析不同类型的软件可能流程不太一样,比如分析病毒你肯定不能直接在真机上跑,有些病毒还有反虚拟机的模块,甚至变形多态;编译型代码软件分析和解释型代码软件分析也不太一样;只是分支操作有所不同。接下来是编译型软件的常规操作:

  查壳,看看是啥软件编写的:

  直接拖到IDA瞅一瞅,如果没报错,说明软件可以直接分析。如果报错,说明软件被加壳或者加密。当然这软件没有加壳,所以正常,由于软件短小精悍,我们很快定位到主函数部分,下面是它的流程:

  看到汇编代码后,IDA会有一些注释,看下图:

  这个明明是字符串,但IDA没有识别出来。这个是经常会遇到的情况。有时候IDA识别不出Unicode字符串,甚至识别错误以为是函数偏移,不要仅依赖这个软件。那么我们如何处理呢?把光标放到上面字符串的首地址,按下ALT + A,将会弹出下面的窗体:

  这个就是将数据转化为字符串,由于这个是普通的ASCII字符串,直接选C-style即可。

  转换成功后结果如下:

  听说IDA有一个F5的功能,的确,它是IDA的一个插件。在汇编窗口内按一下会调用Hex-ray这个插件翻译成伪C代码。但是不要过度依赖这个插件,因为翻译出来的东西很多是不准确的,甚至是错误的。比如函数调用的参数个数、多个变量其实就是一个变量、类型不对等等。这些东西都需要依赖自身的开发经验和分析进行调整。经过分析和重命名后,如下所示:

  通过分析,我们既得到了Key,也分析透了函数流程。

暴力破解

  IDA其实可以patch软件的,怎么搞自行学习,比较麻烦,一旦patch就会导致原来的分析流程变化,自认为不太适用,如果用于去除花指令的话另当别论。在动态调试器里修改是最快捷的,最舒服的方式。如下的动图进行演示:

结语

  学习本篇指引,如果IDA不会使用的话,请自行找教程进行练习。本系列教程只是个指引,故不提供IDA使用教程。IDA有如下基本操作:函数和变量的重命名、跳转、类型转化、搜索字符串、查找引用是最常用的操作了,请自行学习。

下一篇

  羽夏逆向指引——补丁

posted @ 2021-11-20 15:14  寂静的羽夏  阅读(1026)  评论(0编辑  收藏  举报