欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]MagicISO的逆向过程及算法破解

[原创]MagicISO的逆向过程及算法破解

1.缘由。
    事情是这样的:一天突然想下载VC++ 2008用,就在本论坛搜索下载地址,找到了这个帖子:http://bbs.pediy.com/showthread.php?t=167366,就用这个帖子里面的下载地址把Visual Studio 2008 Professional下载了,下载后发现文件是虚拟镜像文件,只好再下载工具软件:MagicISO来导出安装文件。下载MagicISO安装文件后发现MagicISO是未注册的,遂产生了要破解意愿,以下为逆向MagicISO的过程。
2.脱壳    
    MagicISO安装好之后,用PEiD检测之后发现是:UPX 0.89.6 - 1.02 / 1.05 - 1.24 -> Markus & Laszlo,之后再下载了两个脱壳工具(http://tools.pediy.com/unpacker.htm):upxfix_by_DiKeN和upxfix。先用upxf.exe修复MagicISO.exe,然后再输入命令行:UPX –d 文件名,就可以把MagicISO.exe脱壳,脱壳之后发现程序使用Borland C++编译的。
3.软件加密解密原理    
    本软件在用户名和序列号的处理上采用了RSA和Base64加密两种手段共同完成,加密和解密分为两条路径,一是软件外部的加密,一是软件程序的内部解密,基本逻辑是这个样子的:

  • 软件外部1:首先用工具对用户名进行RSA加密,N是1024位长度,此时假设输入的用户名为M,加密后得到密文C;
  • 软件外部2:采用Base64方式对用户输入的密文C进行二次加密,不过这个Base64编码方式非普通的编码方式,在处理上有一点特殊之处,此时得到密文:C1
  • 软件内部1:对用户输入的序列号C2进行Base64解码,得到密文C3
  • 软件内部2:利用RSA解密程序解密密文C3,解密后得到明文M1
  • 软件内部3:程序拿M 和 M1进行比对,若M=M1,则视为用户输入序列号为正确的序列号,此时也即可以理解为用户输入的密文C2=C,如果相同,则可视为注册成功!

  • 4.OD逆向    4.1寻找分析断点
        程序运行后的第一个界面,点击“register”按钮后是这个输入用户名和序列号的界面,如图:
        
        我随便输入用户名和序列号(序列号长度应>171个字符,程序内有验证要求)之后,想跟踪点击“Register”按钮事件处理,刚开始想对DialogBoxParamA、MessageBoxA、MessageBoxW、MessageBoxExA、MessageBoxExW下断点,发现都徒劳,经过请教别人发现通过对SendMessageA下断点可以跟踪到“Register”按钮事件处理事件处理函数,即断点地址:004AF030。
        4.2分析断点内容
        经过一段时间对函数:004AF030的分析,发现这个事件处理函数很复杂,一是函数很多,二是函数嵌套很多,跟踪起来相当头疼。随着耐心的分析代码,还是能够找到一些逻辑,大致如下:
        4.2.1获取用户名和序列号    获取用户名的地址为:
        004AF12A CALL <JMP.&KERNEL32.lstrcpyA>     ; \lstrcpyA
        获取序列号的地址为:
        004AF174 CALL <JMP.&KERNEL32.lstrcpyA>    ; \lstrcpyA
        这两处地方都是比较明显的代码。
        4.2.2 创建注册表项
        接着程序的下一步操作是在操作系统的注册表中创建、保存用户名和序列号的注册表项并,保存用户名和序列号的键值(程序的变态之处在于:程序启动时也会再次验证用户名和密码是否正确),代码如下:
    004AF18C PUSH ECX                        ; /Arg2,序列号
    
    004AF18D MOV EAX,DWORD PTR SS:[EBP-8]    ; |
    
    004AF190 PUSH EAX                        ; |Arg1,用户名
    
    004AF191 CALL test.0047B0B0              ; \创建保存用户名和序列号的注册表键值

        F7跟踪进入此函数,基本很容易就能发现创建并保存注册表键值的地方,代码如下:
    0047B2B2 PUSH EAX                                 ; /BufSize,用户名长度
    
    0047B2B3 PUSH EBX                                 ; |Buffer,用户名地址
    
    0047B2B4 PUSH 1                                   ; |ValueType = REG_SZ
    
    0047B2B6 PUSH 0                                   ; |Reserved = 0
    
    0047B2B8 PUSH test.0070063D                       ; |ValueName = "RegUserName"
    
    0047B2BD MOV EAX,DWORD PTR SS:[EBP-40]            ; |
    
    0047B2C0 PUSH EAX                                 ; |hKey
    
    0047B2C1 CALL <JMP.&ADVAPI32.RegSetValueExA>      ; \RegSetValueExA
    
    

        4.2.3 读取注册表项键值    紧接着是读取前面函数已保存过注册表中的用户名和序列号键值,代码为:
         004AF199 PUSH test.00755CF4        ; /Arg2 = 00755CF4
        004AF19E PUSH test.00755CE8        ; |Arg1 = 00755CE8
        004AF1A3 CALL test.0047B61C        ; \test.0047B61C
        F7进入函数,读取键值的地方为:
    0047B697 PUSH EDX                                 ; /读取出来的用户名的长度
    
    0047B698 MOV ECX,DWORD PTR SS:[EBP-8]             ; |
    
    0047B69B LEA EAX,DWORD PTR SS:[EBP-38]            ; |
    
    0047B69E PUSH ECX                                 ; |读取出来的用户名存储在此地址中
    
    0047B69F PUSH EAX                                 ; |pValueType
    
    0047B6A0 PUSH 0                                   ; |Reserved = NULL
    
    0047B6A2 PUSH test.00700693                       ; |ValueName = "RegUserName"
    
    0047B6A7 MOV EDX,DWORD PTR SS:[EBP-34]            ; |
    
    0047B6AA PUSH EDX                                 ; |hKey
    
    0047B6AB CALL <JMP.&ADVAPI32.RegQueryValueExA>    ; \RegQueryValueExA
    
    

        4.2.4 判定程序是否已注册    
        此处是判断程序是否已注册的标识位,在程序内部有很多地方,其中一处为:
        004AF233 MOV BYTE PTR SS:[EBP-6D],0
        004AF237 CMP BYTE PTR DS:[6FE908],1
        004AF23E JNZ test.004AF3C3
        若想爆破此软件,则可以将004AF233  |. C645 93 00     MOV BYTE PTR SS:[EBP-6D],0,修改为:004AF233  |. C645 93 00     MOV BYTE PTR SS:[EBP-6D],1,并且要遍历整个程序,将全部存在类似的地方全部修改完毕即可爆破此软件。
        004AF3C3是个很明显的注册错误程序处理分支,后面会出现很多,因此可以借此判断出此为爆破点!
        4.2.5 黑名单比对    
        紧接着下面是从程序中取出一部分黑名单用户名,拿来跟用户输入的用户名进行比对,以判断程序的流向,第一个比对的代码地址为:
        004AF24A  PUSH ECX                                 ; /String2
        004AF24B  MOV EAX,DWORD PTR DS:[755CEC]            ; |
        004AF250  PUSH EAX                                 ; |String1 => "11ccccccccccc9"
        004AF251  CALL <JMP.&KERNEL32.lstrcmpA>            ; \lstrcmpA
        4.2.6 解Base64编码
        对用户输入的序列号密文首先进行Base64解码,函数地址为:
        004AF37C  PUSH EDX                                 ; /Arg3
        004AF37D  PUSH EDI                                 ; |Arg2  
        004AF37E  PUSH ECX                                 ; |Arg1 => 序列号
        004AF37F  CALL test.0047BA60                       ; \对序列号按照一定的编码规则编码,然后放在Arg3开始的内存区间内
        按F7跟踪进入,可以看到具体的解码代码为以下代码:
    0047BA9E   /TEST EAX,EAX
    
    0047BAA0  |JNZ SHORT test.0047BAC1
    
    0047BAA2  |MOV EAX,DWORD PTR SS:[EBP-4]
    
    0047BAA5  |INC DWORD PTR SS:[EBP-4]
    
    0047BAA8  |MOV EDX,DWORD PTR SS:[EBP+8]
    
    0047BAAB  |MOV CL,BYTE PTR DS:[EDX+EAX]               ;依次取序列号每个字符
    
    0047BAAE  |PUSH ECX
    
    0047BAAF  |CALL test.0047BA00                         ;返回每个字符在base64码表中的位置
    
    0047BAB4  |MOVSX ESI,BYTE PTR DS:[EAX+755D40]         ;取各个字符的对应的码值
    
    0047BABB  |MOV EAX,6                                  ;设定循环6次,每次取最低一位值
    
    0047BAC0  |POP ECX                                      
    
    0047BAC1  |MOV ECX,EBX
    
    0047BAC3  |MOV EDX,ESI
    
    0047BAC5  |AND DL,1                                    ;取码值的最第一位值
    
    0047BAC8  |AND ECX,7                                   ;判断循环是否够8位,够就组下一个8位
    
    0047BACB  |SHL DL,CL                                   ;设定位置
    
    0047BACD  |MOV ECX,EBX
    
    0047BACF  |MOV EDI,DWORD PTR SS:[EBP+10]
    
    0047BAD2  |SHR ECX,3
    
    0047BAD5  |INC EBX                                     
    
    0047BAD6  |SHR ESI,1                                   ;码值向右移动一位,
    
    0047BAD8  |DEC EAX
    
    0047BAD9  |OR BYTE PTR DS:[EDI+ECX],DL                 ;依次向内存中写入处理好的每一位的值
    
    0047BADC  |CMP EBX,DWORD PTR SS:[EBP-8]
    
    0047BADF   \JB SHORT test.0047BA9E
    
    

        假如需用输入的序列号为:012abc……,则会在内存中解码形成数据为:8A BA 80 ……,其处理的大致逻辑如图所示:
        
        4.2.7 RSA解码    
        此处是处理RSA解码的函数:
        004AF393  PUSH EAX                                 ; /Arg5 => 0000008C
        004AF394  PUSH test.006FE87C                       ; |Arg4 = 006FE87C
        004AF399  PUSH EDX                                 ; |Arg3
        004AF39A  PUSH 80                                  ; |Arg2 = 00000080
        004AF39F  MOV ECX,DWORD PTR SS:[EBP-28]            ; |
        004AF3A2  PUSH ECX                                 ; |Arg1
        004AF3A3  CALL test.006B4A10                       ; \test.006B4A10
        RSA解码区程序只给出了N、E,无法获知D,因此我们比较好的处理方法是按照我们给定的N、E、D,配合我们输入的用户名和序列号来完成算法的逆演算,这样所有的数据都是我们编排好的,就可以确保注册是成功的。
        首先我找来RsaKit_V1.0版本,随机产生一个1024位的密钥,然后输入我们需要注册的用户名,点击加密,得到加密密文,如图:
        
        此处我们设定的几个关键参数为:
    N= 6D2E20A939BAB289A52AC6A80BD954FC497EF69369754262513D364DB624D33D902591E443C55448128647E853D4505FDC885788123EEBAB1660066F0E6C4BF520D0D8870F921E79ED63ED049101309635D0E46CCBDE42C67F29DCEA63CF3ABD67AFD81AFFC053AF6CA51D3745B5E97EDAC3AC0B70E48D2CE862AC5823C5F5DD
    E=1001
    D= 2849F5537D321FE01B4B05B3CD6B8DA8EB9626778F5AEDFC49EE44B3B89B2E387190EB9D4844F12D0768F5F00DBF3349AC9FC36D3B0E6CF8DD70E1EEE96B9D3B839C43C9EBB4256E6622D1DA9D1EA7E847470643C8C77F3099D384144B653420813FA366F5E0B458EF994FA0D238CA48A0730606767F7B8854958BE926637921
    M=11ccccccccccc9= 3131636363636363636363636339
    C= 4EE90DA9A2A43376C529DE6EC71B043A07DF321935B1F50CF8DFC0C0E518DCBE9EE6E120A4C19C4358CF416C2C69260AA89EA5BD29F0CE430D060DF1C666E89EE156B8304B2A8C6447DF03DAC83B888C973526358D498951D271A5D6F29624FC62A7A843A3AB8372811FFCB1223461DF4CC2ECEA5D8762B8720A22B46D7B721B
        设计好N、E、D、M、C之后,那么就是要程序按照我们设计好的计划来运转,下面一步就是要替换程序中的N 和E,此时本程序的Base64编码相对复杂,我们也可以省去自己去做Base64编码,而可以在程序做完Base64解码后,直接把我们设计的C替换,替换时我们可以在此处替换:
        
    006B4AE6  PUSH ECX                                 ; /解码后的大小
    
    006B4AE7  MOV EAX,DWORD PTR SS:[EBP-14]            ; |
    
    006B4AEA  PUSH EAX                                 ; |解码后的地址
    
    006B4AEB  MOV EDX,DWORD PTR SS:[EBP+C]             ; |
    
    006B4AEE  PUSH EDX                                 ; |待解码长度
    
    006B4AEF  MOV ECX,DWORD PTR SS:[EBP+8]             ; |
    
    006B4AF2  PUSH ECX                                 ; |待解码地址
    
    006B4AF3  MOV EAX,DWORD PTR SS:[EBP-8]             ; |
    
    006B4AF6  PUSH EAX                                 ; |N 和E存储的地方
    
    006B4AF7  CALL test.0069489C                       ; \test.0069489C
    
    

        对于N 和E 的替换,我们可以用WinHEX打开EXE文件(即附件),通过检索既可以找到N的地址为:002FD488(EXE文件偏移地址),然后把上面我们设计好的N进行倒序排列,即变为:
    N= DDF5C52358AC62E82C8DE4700BACC3DA7EE9B545371DA56CAF53C0FF1AD8AF67BD3ACF63EADC297FC642DECB6CE4D0359630019104ED63ED791E920F87D8D020F54B6C0E6F066016ABEB3E12885788DC5F50D453E84786124854C543E49125903DD324B64D363D516242756993F67E49FC54D90BA8C62AA589B2BA39A9202E6D,然后进行替换。
    E的地址为:002FD488(EXE文件偏移地址),即将:C7406E69,替换为:00010001
        待解码地址我们替换为:4EE90DA9A2A43376C529DE6EC71B043A07DF321935B1F50CF8DFC0C0E518DCBE9EE6E120A4C19C4358CF416C2C69260AA89EA5BD29F0CE430D060DF1C666E89EE156B8304B2A8C6447DF03DAC83B888C973526358D498951D271A5D6F29624FC62A7A843A3AB8372811FFCB1223461DF4CC2ECEA5D8762B8720A22B46D7B721B
        这样,我们就完成了RSA KEY的替换。
        4.2.8 比对用户名    
        进行完以上的KEY替换之后,我们继续运行程序,来到最后的比对处:
    004AF3B0  PUSH EAX                                 ; /String2 => "11ccccccccccc9"
        004AF3B1  MOV EDX,DWORD PTR SS:[EBP-34]            ; |
        004AF3B4  INC EDX
        004AF3B5  PUSH EDX                                 ;  EDX是解码
        004AF3B6  CALL <JMP.&KERNEL32.lstrcmpA>            ; \lstrcmpA
        在此处,EDX处是存储解码后的最终地址,打开后我们看到:
        
        确实解密出来之后的数据还是11ccccccccccc9,但是在这个解密数据之前有很多的空字节,如果按照这个结果去比对,结果还是注册失败!因此我们可以通过再给函数006B4A10打补丁的方式能够解决此问题!但是为什么在11ccccccccccc9之前会有这么多的空字节呢?我一直没有找到正确的答案,也很想知道其中的缘由,恳请高人指点!
       最后感谢MistHill在研究这个软件过程中给予的帮助!
        
        请论坛各位大牛指导!
    test.zip

    上传的附件 register.jpg
    miyue.jpg
    jiemi.jpg
    base64.jpg
    test.zip

    注意:上传附件及图片大小不得大于30M。

    ⚠️ 版权声明:
    本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
    未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

    0 0 0 举报
    复制成功