首页
社区
课程
招聘
[原创]看雪 2016 CTF 第三题 Solution
发表于: 2016-11-8 11:53 9845

[原创]看雪 2016 CTF 第三题 Solution

HHHso 活跃值
22
2016-11-8 11:53
9845

还好,赶上了末班车,RSA公钥加密,没有私钥,直观的方向枚举,看着结束时间与电脑的CPU负载,想起了曾经玩过的uplink(应该没拼错)。
密码两位两位地跑出来!!还好,4 对 2,每次跑的时间都不长,都不超过几分钟。

1.第一阶段定制IDAPython去除混淆,还好混淆用的模式不多。
2.第二阶段去掉混淆后,IDA看着就如同源码了,以前对 basic_string 及 CPP 对象内存布局相对熟悉,也就没过不去的坎。
3.第三阶段就是rijndael解密和rsa解密(没有私钥信息,只能尝试枚举)处理。

以下是3的处理过程,1,2和相关文件后续再尽快补上思路的历程。

在 40343C 断下,dump出 加密后的注册码encstr (at 42E628;size:0x10)和解密用的密钥ikey (at 42E610;size:0x20);
用下面的python脚本直接解密出destr,最后用后面的热补丁代码对
destr:0D 48 ED 5E F7 69 E2 AB D6 8B FD 6C 76 DD 79 5D 每两个进行rsa溯源枚举
CE 7F 02 00 >> 6F 93 0D 48
6F 80 01 00 >> 78 E6 ED 5E
05 48 00 00 >> 91 99 F7 69
E7 4E 00 00 >> A2 9F E2 AB
24 64 01 00 >> 9C 7E D6 8B
7C 5E 00 00 >> E2 CE FD 6C
4B 33 00 00 >> 80 2C 76 DD
E4 2B 00 00 >> 27 62 79 5D

得到 其中一个可行注册码;
CE7F02006F80010005480000E74E0000246401007C5E00004B330000E42B0000

ikey:
00199DF8  0F 5F 7B CD C0 66 0D 58 7C 86 D7 26 B1 C6 AA D1   
00199E08  03 B2 12 85 C1 03 A3 51 BB C5 73 3B 3A D4 36 35   

ikey = '\x0F\x5F\x7B\xCD\xC0\x66\x0D\x58\x7C\x86\xD7\x26\xB1\xC6\xAA\xD1\x03\xB2\x12\x85\xC1\x03\xA3\x51\xBB\xC5\x73\x3B\x3A\xD4\x36\x35'
encstr = "\xC8\xE5\xE2\xC3\xC4\x39\xFC\x04\x48\xD8\x0F\x8B\x5F\x73\x8C\xA9"
obj = AES.new(ikey,AES.MODE_CBC,"\0"*16)
destr = obj.decrypt(encstr)
for i in destr:
  print "{:02X}".format(ord(i)),
  
destr:
0D 48 ED 5E F7 69 E2 AB D6 8B FD 6C 76 DD 79 5D

rsa枚举热补丁,OD脚本alloc内存为0x9C0000, cbSize:0x1000
009C0000   B9 31000000      MOV ECX,31
009C0005   BE 482F4300      MOV ESI,432F48
009C000A   BF A0014400      MOV EDI,4401A0
009C000F   F3:A5            REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>
009C0011   68 00089C00      PUSH 9C0800
009C0016   6A 22            PUSH 22
009C0018   68 A09F4300      PUSH 439FA0
009C001D   E8 4EA7A4FF      CALL kanxue-c.0040A770
009C0022   83C4 0C          ADD ESP,0C
009C0025   85C0             TEST EAX,EAX
009C0027   75 58            JNZ SHORT 009C0081
009C0029   90               NOP
009C002A   90               NOP
009C002B   90               NOP
009C002C   90               NOP
009C002D   68 00089C00      PUSH 9C0800
009C0032   6A 00            PUSH 0
009C0034   68 04019C00      PUSH 9C0104
009C0039   68 08019C00      PUSH 9C0108                              ; ASCII "'by]"
009C003E   6A 04            PUSH 4
009C0040   68 00019C00      PUSH 9C0100
009C0045   E8 06BEA4FF      CALL kanxue-c.0040BE50
009C004A   83C4 18          ADD ESP,18
009C004D   8BF0             MOV ESI,EAX
009C004F   68 00089C00      PUSH 9C0800
009C0054   E8 E7A6A4FF      CALL kanxue-c.0040A740
009C0059   83C4 04          ADD ESP,4
009C005C   85F6             TEST ESI,ESI
009C005E   75 22            JNZ SHORT 009C0082
009C0060   90               NOP
009C0061   90               NOP
009C0062   90               NOP
009C0063   90               NOP
009C0064   33C0             XOR EAX,EAX
009C0066   66:813D 0A019C00>CMP WORD PTR DS:[9C010A],5D79
009C006F   74 0F            JE SHORT 009C0080
009C0071   90               NOP
009C0072   90               NOP
009C0073   90               NOP
009C0074   90               NOP
009C0075   8305 00019C00 01 ADD DWORD PTR DS:[9C0100],1
009C007C  ^EB 93            JMP SHORT 009C0011
009C007E   90               NOP
009C007F   90               NOP
009C0080   90               NOP
009C0081   90               NOP
009C0082   90               NOP
009C0083   8305 00019C00 01 ADD DWORD PTR DS:[9C0100],1
009C008A  ^E9 71FFFFFF      JMP 009C0000

----------------------------------------------------------------
完善下1,2,3阶段分析和去混淆方案过程
还是趁热打下铁,把心路历程写写,再等上一等,估计也就懒得写了。
第一阶段:定制IDAPython脚本去掉混淆(Confuse)代码。
第二阶段:istream,ostream,basic_string及其关联函数甄别。
第三阶段:rijndael 及 LibTom家族的rsa攻击。

第一阶段:二话不说,直接上手头的IDA6.6(这几年经历了5.1,6.1到6.6,换掉5.1那是因为的确有点老了,换掉6.1是因为在非常复杂庞大的单个函数体graph处理上会罢工,
换用6.6时,6.8已经出来,选6.6仅仅是因为有6.6的SDK,而6.8的那时还没有,扯远了,不过打开附件的IDA库时,请确保用高版本的IDA)

(1)第一回合:无论是从"********   "等字符的引用溯源,还是直接从start开始完成运行时环境的初始化进入入口函数,
最终还是会碰上不能"Create Function"进而不能"graph流程模式"的函数体,而罪魁祸首就是分支混淆,如下:

.text:00403556 FF 74 24 28                                   push    dword ptr [esp+28h]
.text:0040355A FF 74 24 38                                   push    dword ptr [esp+38h]
.text:0040355E 68 B2 04 3B B9                                push    0B93B04B2h
.text:00403563 83 EC 40                                      sub     esp, 40h
.text:00403566 E8 02 00 00 00                                call    loc_40356D
.text:00403566                               ; ---------------------------------------------------------------------------
.text:0040356B 81                                            db 81h
.text:0040356C 89                                            db 89h
.text:0040356D                               ; ---------------------------------------------------------------------------
.text:0040356D
.text:0040356D                               loc_40356D:                             ; CODE XREF: .text:00403566j
.text:0040356D 87 1C 24                                      xchg    ebx, [esp]
.text:00403570 81 EB A8 3B B7 3E                             sub     ebx, 3EB73BA8h
.text:00403576 81 C3 C5 3B B7 3E                             add     ebx, 3EB73BC5h
.text:0040357C 74 08                                         jz      short loc_403586
.text:0040357E 87 1C 24                                      xchg    ebx, [esp]
.text:00403581 C2 78 00                                      retn    78h
.text:00403581                               ; ---------------------------------------------------------------------------
.text:00403584 81 C9                                         db 81h, 0C9h
.text:00403586                               ; ---------------------------------------------------------------------------
.text:00403586
.text:00403586                               loc_403586:                             ; CODE XREF: .text:0040357Cj
.text:00403586 81 FC 68 00 04 00                             cmp     esp, 40068h
.text:0040358C 00 E8                                         add     al, ch
.text:0040358E DB 09                                         fisttp  dword ptr [ecx]

一个简单的分支跳转指令(马后炮,事后统计才知),变成了:
  压栈,压入retRefAddr(上述模式中用作计算参考地址,以call方式压入),通过xchg,sub,add计算得到真正的目标地址RealTgtAddr
  在jz不成立的情形下(jnz是另一个模式),通过 retn imm16 切换到目标地址,并保证堆栈平衡。

  
混淆直接隐藏了目标地址,并成功通过 "xchg,sub,add"之后的jz或jnz直接诱骗IDA进行错误的反汇编。
(在攻击过程中粗放式统计,可知道jz或jnz后面的两个是固定冗余字节,现在写文档时,才意识到其实call,与retn之后都是固定多两个混淆用的冗余字节)

很直接的想法,把冗余的代码都nop掉,虽然可以将快捷键切换到对当前行行全nop(通过PatchByte()设置),都不知有多少混淆,这样实在是太伤手了。
最终还是乖乖稍稍定制下IDAPython自动化处理下:
   作为正常人类思维模式是:
     遍历当前函数的所有call指令的目标地{
        如果目的地匹配 xchg,sub,add 代表混淆的特征,则进行混淆分析{
          主要获取,真正跳转地址ReadTgtAddr,混淆起始首尾confuse_start,confuse_end
        }
     }
     
没啥惊世骇俗的,仅仅是通过代码实现人工的工作。代码也是多次测试修改得到,不追求华丽,只追求快速。
在清查匹配过程中,(1)有些是基于IDA已反编译的结果进行匹配,(2)有些则是直接基于二进制字节特征,
或混合使用。由于(1)的影响,所以需要重复多次执行清查才能彻底清除干净(因为前后的清除会使IDA识别更多的有效代码出来)

代码设置了开关功能,主要用于在确认清除前,先做些人工审核,

(1.1)hlaSecssionInstrX,IshlaPush,hlafindpX 三个函数是以前在工作中写的通用功能函数。
IshlaPush用于判断push型指令,hlafindpX用于查找返回一个call之前的第一个push,用于搜索call的参数或栈操作。
hlaSecssionInstrX 用于在区间搜索第几次出现的特定指令

(1.2)call3,call2,call6分别用于存放3字节,2字节,6字节 call的指令特征,用于过滤,
至于为何放在外面,只是为了避免放在函数内部时每次添加过滤都需要重复制整个函数倒IDA里,太扯了,还是放在外面清净。

(1.3)确保你的IDA的IDAPython能工作,复制下述片区的代码到IDAPython命令行里回车编译。
然后输入 findConfuseCall_info(0x401000,0x0042DF66) 执行,就会尝试输出混淆统计信息如下
一开始想的只是清除某个函数内的混淆,所以通过下面的方式逐次定位一个函数的头尾,和其尾部之一(函数可能有多个尾部)
之后再findConfuseCall_info(fun_s,fun_e1)清查,自然也没有那么多call3,call2,call6,不过脑门被夹了以下,为何不直接遍历整个代码区。
enterstr = "55 8B EC"
levelstr = "8B E5 5D"
fun_s = FindBinary(startPos,SEARCH_CASE|SEARCH_DOWN|SEARCH_NEXT,enterstr)
fun_e1 = FindBinary(fun_s,SEARCH_CASE|SEARCH_DOWN|SEARCH_NEXT,levelstr)

(1.4)第一此清查后,可以直接点击IDAPython的输出窗口进行代码片区定位,做些人工检测,
应该注意到混淆结束地址跟目标调整地址不一致的情形,这种是前跳,一开始直接将混淆的尾部定义为目标地址,
当遇到前跳时,可想而知,有用无用的全部清了,而后行的代码跳到了nop区(太不科学了,肯定不合理)
所以后来把混淆的尾部定义为jz/jnz或rent后的两字节之后

(1.5)提供第三参数Ture或1确认审查的同时进行清除操作,findConfuseCall_info(0x401000,0x0042DF66,1)
完了之后需要跟进各区进行代码分析,函数创建(能创建即能graph查看,一般算成功了)
若还不能创建函数,就需要手工在text模式下快速浏览下,是否有新的混淆出现,
并手工将其正确反编译出来(混淆特征jz/jnz等之后的指令通常是错误反编译,快捷键U解除定义,快捷键C反编译),
在重复运行findConfuseCall_info(0x401000,0x0042DF66) 和 findConfuseCall_info(0x401000,0x0042DF66,1)

(1.6)重复审查清除,直到 findConfuseCall_info(0x401000,0x0042DF66) 打印的信息尾部是False时,
说明找不混淆了(并不说明干净了,只能是相对干净了),
我就唯一一次在LibTomCrypt的函数了graph流程图朋友一个分支以push结尾,瞬间的不可思议,马上切换为text模式,
发现了新的混淆模式,及步需要retRefAddr做为参考的模式。混淆片区并不多,有兴趣可以定位到各个特征做下统计分析。

(1.7)重复多次审查和清除的结果。

Python>findConfuseCall_info(0x401000,0x0042DF66)
Search Confuse chip in (00401000 ~ 0042DF66) 01  MODE1 ConfuseZone: from 0040147B to 004014B7 jmp 004014B7
Search Confuse chip in (00401000 ~ 0042DF66) 02  MODE1 ConfuseZone: from 004015EA to 00401625 jmp 00401625
Search Confuse chip in (00401000 ~ 0042DF66) 03  MODE1 ConfuseZone: from 00401730 to 0040177E jmp 0040177E
Search Confuse chip in (00401000 ~ 0042DF66) 04  MODE1 ConfuseZone: from 004018AF to 004018D8 jmp 004018D8
Search Confuse chip in (00401000 ~ 0042DF66) 05  MODE1 ConfuseZone: from 00401A4F to 00401A79 jmp 00401A79
Search Confuse chip in (00401000 ~ 0042DF66) 06  MODE1 ConfuseZone: from 00402DDC to 00402E17 jmp 00402E17
Search Confuse chip in (00401000 ~ 0042DF66) 07  MODE1 ConfuseZone: from 00402FC8 to 00403003 jmp 00403003
Search Confuse chip in (00401000 ~ 0042DF66) 08  MODE1 ConfuseZone: from 0040314C to 00403176 jmp 00403176
Search Confuse chip in (00401000 ~ 0042DF66) 09  MODE1 ConfuseZone: from 0040352D to 00403588 jmp 00403588
Search Confuse chip in (00401000 ~ 0042DF66) 0A  MODE1 ConfuseZone: from 0040359A to 004035C6 jmp 004035C6
Search Confuse chip in (00401000 ~ 0042DF66) 0B  MODE1 ConfuseZone: from 004035DB to 004035FF jmp 004035FF
Search Confuse chip in (00401000 ~ 0042DF66) 0C  MODE2 ConfuseZone: from 00404E83 to 00404EAD jmp 00404EAD
Search Confuse chip in (00401000 ~ 0042DF66) 0D  MODE1 ConfuseZone: from 00405055 to 0040507F jmp 0040507F
Search Confuse chip in (00401000 ~ 0042DF66) 0E  MODE1 ConfuseZone: from 004056F5 to 0040573C jmp 0040573C
Search Confuse chip in (00401000 ~ 0042DF66) 0F  MODE1 ConfuseZone: from 0040BE97 to 0040BECC jmp 0040BECC
Search Confuse chip in (00401000 ~ 0042DF66) 10  MODE1 ConfuseZone: from 0040BEFA to 0040BF35 jmp 0040BF35
Search Confuse chip in (00401000 ~ 0042DF66) 11  MODE1 ConfuseZone: from 0040BFA4 to 0040BFF2 jmp 0040BFF2
Search Confuse chip in (00401000 ~ 0042DF66) 12  MODE1 ConfuseZone: from 0040C203 to 0040C238 jmp 0040C238
Search Confuse chip in (00401000 ~ 0042DF66) 13  MODE1 ConfuseZone: from 0040C396 to 0040C3D1 jmp 0040C3D1
Search Confuse chip in (00401000 ~ 0042DF66) 14  MODE1 ConfuseZone: from 0040C7E0 to 0040C815 jmp 0040C815
True

Search Confuse chip in (00401000 ~ 0042DF66) 01  MODE1 ConfuseZone: from 0040314C to 00403176 jmp 00403176
Search Confuse chip in (00401000 ~ 0042DF66) 02  MODE2 ConfuseZone: from 0040319A to 004031C9 jmp 004033A6
Search Confuse chip in (00401000 ~ 0042DF66) 03  MODE1 ConfuseZone: from 00403279 to 004032B4 jmp 004032B4
Search Confuse chip in (00401000 ~ 0042DF66) 04  MODE1 ConfuseZone: from 004032ED to 00403322 jmp 00403322
Search Confuse chip in (00401000 ~ 0042DF66) 05  MODE1 ConfuseZone: from 0040359A to 004035C6 jmp 004035C6
Search Confuse chip in (00401000 ~ 0042DF66) 06  MODE1 ConfuseZone: from 004035DB to 004035FF jmp 004035FF
Search Confuse chip in (00401000 ~ 0042DF66) 07  MODE1 ConfuseZone: from 00403620 to 00403653 jmp 00403653
Search Confuse chip in (00401000 ~ 0042DF66) 08  MODE1 ConfuseZone: from 0040366E to 004036B5 jmp 004036B5
Search Confuse chip in (00401000 ~ 0042DF66) 09  MODE1 ConfuseZone: from 004036C2 to 004036F7 jmp 004036F7
Search Confuse chip in (00401000 ~ 0042DF66) 0A  MODE1 ConfuseZone: from 004039B0 to 004039E5 jmp 004039E5
Search Confuse chip in (00401000 ~ 0042DF66) 0B  MODE1 ConfuseZone: from 00403A00 to 00403A3B jmp 00403A3B
True

Search Confuse chip in (00401000 ~ 0042DF66) 01  MODE1 ConfuseZone: from 0040BEFA to 0040BF35 jmp 0040BF35
Search Confuse chip in (00401000 ~ 0042DF66) 02  MODE1 ConfuseZone: from 0040BFA4 to 0040BFF2 jmp 0040BFF2
True

(2)第二回合,istream 和 ostream的识别由CPP的运行时信息确认。
.text:00401114 mov     ecx, offset Hi_istream_dword_43F0F0
.text:00401119 call    sub_412D36
    00412D73 mov     dword ptr [esi+eax], offset Hi_vft_basic_istream_off_4344FC
vft(4344FC) .18 kanxue-ctf-gxustudent.exe
NOA 03,00,FF,00,40 .?AV?$basic_istream@DU?$char_traits@D@std@@@std@
NOA 02,00,00,04,50 .?AV?$basic_ios@DU?$char_traits@D@std@@@std@
NOA 01,00,00,04,40 .?AVios_base@std@
NOA 00,08,00,04,40 .?AV?$_Iosb@H@std@

.text:0040109D mov     ecx, offset Hi_ostream_unk_43F038
.text:004010A2 call    sub_411EA8
    0411EE6 mov     dword ptr [esi+eax], offset Hi_vft_basic_ostream_off_434480
NOA 03,00,FF,00,40 .?AV?$basic_ostream@DU?$char_traits@D@std@@@std@
NOA 02,00,00,04,50 .?AV?$basic_ios@DU?$char_traits@D@std@@@std@
NOA 01,00,00,04,40 .?AVios_base@std@
NOA 00,08,00,04,40 .?AV?$_Iosb@H@std@

ostream及Cout
.text:00403653 push    offset Hi_maybe_ostream_endl_sub_404830
.text:00403658 mov     edx, offset aPleaseInputYourSerialNumberPressEnte ; "please input your serial number, press "...
.text:0040365D mov     ecx, offset Hi_ostream_unk_43F038
.text:00403662 call    Hi_ostream_out_lpszEDX_sub_404320
.text:00403667 mov     ecx, eax
.text:00403669 call    Hi_fpP1call_withEcxAsParam_sub_403BD0

istream及Cin
.text:004036B5 lea     edx, [ebp+var_28_bastr1]
.text:004036B8 mov     ecx, offset Hi_istream_dword_43F0F0
.text:004036BD call    Hi_istream_readToEdxbast_sub_

可在Local Type视窗中插入以下basic_stirng 结构,并双击同步到结构窗口中,对basic_string 的相关输入输出局部变量重构。
struct _hi_basic_string
{
  DWORD lpsz;
  DWORD lpsz_04;
  DWORD lpsz_08;
  DWORD lpsz_0C;
  DWORD _10_len;
  DWORD _14_cbsize;
};
typedef struct _hi_basic_string Hi_basic_str;

(3)LibTom家族和rijndael的发现与攻击

(3.1)rijndael就不用说了,算法常量直接爆了了。
.text:0040B1EB mov     ecx, ds:Rijndael_Te2[ecx*4]
.text:0040B1F2 xor     ecx, ds:Rijndael_Te1[

(3.2)"LibTomMath"的使用是在吃饭的时候手机搜到的.
因为下面经过 Hi_chg1_sub_401A00 对输入的十六进制字符key截取的部分转换为binary形式
在对binary形式经过 Hi_chg2_rsa_encrypt_sub_401700 转换为作为rijndael加密输入的binaryStr_ch2
函数 Hi_chg2_rsa_encrypt_sub_401700 中有 ltm 的赋值,后面就直接爆了了 "Libtommath"
百度了以下就出来了,直接下载了
484K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9K9h3u0@1L8$3#2Q4x3V1k6D9K9h3u0@1L8$3#2E0j5i4c8Z5
很幸运,里面找不到"LibTommath"字符常量,
后面就downloadLibTom的整个家族,

lcrypt-master.zip
libtomcrypt-develop.zip
libtomfloat-master.zip
libtommath-develop.zip
ibtompoly-develop.zip

最后在libtomcrypt源码中找了了ltm结构

const ltc_math_descriptor ltm_desc = {

   "LibTomMath",
   (int)DIGIT_BIT,
   &init,
   &init_copy,
   &deinit,
   &neg,
   &copy,
...
}

.text:004032B4 lea     ecx, [ebp+var_88_InputKeyPart08h]
.text:004032BA push    ecx
.text:004032BB lea     edx, [ebp+var_58_InputKeyPart08h_binaryStr_chg1]
.text:004032BE push    edx
.text:004032BF call    Hi_chg1_sub_401A00
.text:004032C4 add     esp, 8
.text:004032C7 mov     byte ptr [ebp+var_4], 3
.text:004032CB lea     ecx, [ebp+var_28_InputKeyPart08h_binaryStr_chg2]
.text:004032CE call    Hi_bastr_init_sub_401B40
.text:004032D3 mov     byte ptr [ebp+var_4], 4
.text:004032D7 lea     eax, [ebp+var_28_InputKeyPart08h_binaryStr_chg2]
.text:004032DA push    eax             ; int
.text:004032DB lea     ecx, [ebp+var_58_InputKeyPart08h_binaryStr_chg1]
.text:004032DE push    ecx             ; int
.text:004032DF call    Hi_chg2_rsa_encrypt_sub_401700
.text:004032E4 add     esp, 8

rdata:00432F48 0C 30 43 00                   Hi_ltm_desc_off_432F48 dd offset aLibtommath
.rdata:00432F48                                                                       ; DATA XREF: sub_401700+83o
.rdata:00432F48                                                                       ; "LibTomMath"
.rdata:00432F4C 1C 00 00 00                   Hi_DIGIT_BIT_Dec28_Hex1Ch_bits_per_digit dd 1Ch
.rdata:00432F50 20 D5 40 00                                   dd offset init
.rdata:00432F54 00 D6 40 00                                   dd offset init_copy
.rdata:00432F58 80 D5 40 00                                   dd offset deinit
.rdata:00432F5C A0 D5 40 00                                   dd offset neg
.rdata:00432F60 D0 D5 40 00                                   dd offset copy
.rdata:00432F64 70 D6 40 00                                   dd offset set_int
.rdata:00432F68 B0 D6 40 00                                   dd offset get_int
//此处省略千言万语
.rdata:00432FF4 90 E9 40 00                                   dd offset ecc_ptadd
.rdata:00432FF8 20 E4 40 00                                   dd offset ecc_ptdbl
.rdata:00432FFC 60 FC 40 00                                   dd offset ecc_map
.rdata:00433000 40 F7 40 00                                   dd offset ecc_mul2add
.rdata:00433004 D0 FD 40 00                                   dd offset rsa_keygen
.rdata:00433008 50 BE 40 00                                   dd offset rsa_me
.rdata:0043300C 4C 69 62 54 6F 6D 4D 61 74 68+aLibtommath     db 'LibTomMath',0       ; DATA XREF: .rdata:Hi_ltm_desc_off_432F48o
.rdata:0043300C 00; .data:Hi_LibTo

(3.3)热补丁,实在是逼于无奈,
当我尝试直接用 libtomcrypt 编写代码是,编译不通过,实在是汗颜!
出于时间考虑,还是算了,直接OD里用原软件的加密过程跑

补丁代码实际就是模仿 Hi_chg2_rsa_encrypt_sub_401700 中
rsa_key 构建 >> 40179D call    Hi_rsa_import_sub_40A770
rsa公钥加密  >> 00401807 call    near ptr Hi_rsa_me_sub_40BE50  
rsa_key 析构 >> 040181C call    Hi_rsa_key_dtor_sub_40A740

用OD脚本alloc 0x1000 内存,实例中返回的是 0x9C0000 cbSize:0x1000

(3.3.1)
前面是 ltm_desc 的初始化
将Hi_rsa_import_sub_40A770 输出的rds_key 原来存放在句柄变量中的改到啊 0x9C0800,
  注意到 0040178F lea     eax, [ebp+var_12C_key] 局部变量名称,其大小不会超过 0x12C,
  所以0x9C0800之后还有0x200的可用空间已经足够,当然你可以任性浪费更多空间
(3.3.2)
00401807 call    near ptr Hi_rsa_me_sub_40BE50  的参数实际是两个缓冲区而后0(公钥)及rsa_key
原代码片区是中间夹带的是 basic_string 的结构,太累赘,直接在补丁中简化了
.text:004017DE lea     eax, [ebp+var_12C_key]
.text:004017E4 push    eax
.text:004017E5 push    0
.text:004017E7 lea     ecx, [ebp+var_108_0x100]
.text:004017ED push    ecx
.text:004017EE lea     edx, [ebp+var_104_buf100]
.text:004017F4 push    edx
.text:004017F5 mov     ecx, [ebp+P1_InputKeyPart08h_binaryStr]
.text:004017F8 call    Hi_bastr_getLen_sub_401E00
.text:004017FD push    eax
.text:004017FE mov     ecx, [ebp+P1_InputKeyPart08h_binaryStr]
.text:00401801 call    Hi_bastr_get_lpsz_sub_401DF0 ; Microsoft VisualC 2-11/net runtime
.text:00401806 push    eax
.text:00401807 call    near ptr Hi_rsa_me_sub_40BE50 ; 40BE50
.text:0040180C add     esp,

Hi_rsa_me_sub_40BE50(InBuf:0x9C0100,InBufSize:4,OutBuf:0x9C0108,InOutSize:0x9C0104)
可见,我们把加密输入inkey放在0x9C0100处,输出rsa_ecnrypt(inkey)放在0x9C0108处,原代码区是0x100的土豪区,
实际多次此时中,Hi_rsa_me_sub_40BE50是四对四的加密(程序去加密输出的后两字节)
枚举的基本原理就是:
  for(inkey = 0;inkey < 0x100000000;inkey++)
    if TgtEncryptKey != rsa_ecnrypt(inkey):
      continue
    else:
      //break at 009C0082

所以需在下面两处下断点(另一个是异常,009C0083实际也是异常,不过测试过程中忽略了继续跑)
当枚举到匹配时就会断下。以方便dump出inkey
同时进行修改009C0066处的TgtEncryptKey(如79 5D >> 0x5D79时)
将EIP修改到开头重来
009C0081   90               NOP
009C0082   90               NOP

什么时候进入补丁?
在 004032DF call    Hi_chg2_rsa_encrypt_sub_401700 断下时
就可以通过od脚本alloc,然后到alloc的内存里写汇编代码了。
一开始图省事,补丁只有 Hi_chg2_rsa_encrypt_sub_401700 的调用,
由于异常,所以直接带上了前面的rsa_key构建和析构。

009C0000   B9 31000000      MOV ECX,31
009C0005   BE 482F4300      MOV ESI,432F48
009C000A   BF A0014400      MOV EDI,4401A0
009C000F   F3:A5            REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>
009C0011   68 00089C00      PUSH 9C0800
009C0016   6A 22            PUSH 22
009C0018   68 A09F4300      PUSH 439FA0
009C001D   E8 4EA7A4FF      CALL kanxue-c.0040A770
009C0022   83C4 0C          ADD ESP,0C
009C0025   85C0             TEST EAX,EAX
009C0027   75 58            JNZ SHORT 009C0081
009C0029   90               NOP
009C002A   90               NOP
009C002B   90               NOP
009C002C   90               NOP
009C002D   68 00089C00      PUSH 9C0800
009C0032   6A 00            PUSH 0
009C0034   68 04019C00      PUSH 9C0104
009C0039   68 08019C00      PUSH 9C0108                              ; ASCII "'by]"
009C003E   6A 04            PUSH 4
009C0040   68 00019C00      PUSH 9C0100
009C0045   E8 06BEA4FF      CALL kanxue-c.0040BE50
009C004A   83C4 18          ADD ESP,18
009C004D   8BF0             MOV ESI,EAX
009C004F   68 00089C00      PUSH 9C0800
009C0054   E8 E7A6A4FF      CALL kanxue-c.0040A740
009C0059   83C4 04          ADD ESP,4
009C005C   85F6             TEST ESI,ESI
009C005E   75 22            JNZ SHORT 009C0082
009C0060   90               NOP
009C0061   90               NOP
009C0062   90               NOP
009C0063   90               NOP
009C0064   33C0             XOR EAX,EAX
009C0066   66:813D 0A019C00>CMP WORD PTR DS:[9C010A],5D79
009C006F   74 0F            JE SHORT 009C0080
009C0071   90               NOP
009C0072   90               NOP
009C0073   90               NOP
009C0074   90               NOP
009C0075   8305 00019C00 01 ADD DWORD PTR DS:[9C0100],1
009C007C  ^EB 93            JMP SHORT 009C0011
009C007E   90               NOP
009C007F   90               NOP
009C0080   90               NOP
009C0081   90               NOP
009C0082   90               NOP
009C0083   8305 00019C00 01 ADD DWORD PTR DS:[9C0100],1
009C008A  ^E9 71FFFFFF      JMP 009C0000

   
---------------------------------------------------------------------------------------------------------------
call3 = [0x50FF,0x52FF,0x55FF,0x56FF]
#call [eax|edx|ebp|esi+ im8]
call2 = [0x12FF,0xD0FF,0xD1FF,0xd2ff,0xd3ff,0xd4ff,0xd5ff,0xd6ff,0xd7ff]
#call [edx],call eax
call6 = [0x15FF,0x90FF,0x91ff,0x92ff,0x93ff,0x94ff,0x95FF]

def findConfuseCall_info(ea_s,ea_e,nopit = False):
  global call3,call2,call6
  #print "Search Confuse chip in ({:08X} ~ {:08X})".format(ea_s,ea_e)
  calltgt_offset = c_long(0)
  calltgt_add = c_ulong(0)
  jzoff = c_long(0)
  ConfuseCall_ea = hlaSecssionInstrX(ea_s,ea_e,["call    "])
  bMatchMode = False
  #check all calls
  cnt = 1
  while ConfuseCall_ea!=0:
    #print "check call {:08X}".format(ConfuseCall_ea)
    if Byte(ConfuseCall_ea)==0xE8: # call xxx
      calltgt_offset.value = Dword(ConfuseCall_ea + 1)
      call_tgt_ea = ConfuseCall_ea + 5 + calltgt_offset.value
      pretips = "Search Confuse chip in ({:08X} ~ {:08X}) {:02X} ".format(ea_s,ea_e,cnt)
      if checkConfuseMode1(ConfuseCall_ea,call_tgt_ea,nopit,pretips):
        cnt = cnt + 1
        bMatchMode = True
      elif checkConfuseMode2(ConfuseCall_ea,call_tgt_ea,nopit,pretips):
        cnt = cnt + 1
        bMatchMode = True
      else:
        pass
      ConfuseCall_ea = hlaSecssionInstrX(ConfuseCall_ea+5,ea_e,["call    "])
      continue
    elif Word(ConfuseCall_ea) in call3:#[0x50FF]:
      #call [eax+ im8]
      ConfuseCall_ea = hlaSecssionInstrX(ConfuseCall_ea+3,ea_e,["call    "])
      continue
    elif Word(ConfuseCall_ea) in call2:#[0x12FF]:
      #call [edx]
      ConfuseCall_ea = hlaSecssionInstrX(ConfuseCall_ea+2,ea_e,["call    "])
      continue
    elif Word(ConfuseCall_ea) in call6:
      ConfuseCall_ea = hlaSecssionInstrX(ConfuseCall_ea+6,ea_e,["call    "])
      continue
    else:
      raise Exception( "-----------find new call mode at {:08X}".format(ConfuseCall_ea))
      ConfuseCall_ea = hlaSecssionInstrX(ConfuseCall_ea+1,ea_e,["call    "])
      continue
  return bMatchMode

def checkConfuseMode2(ConfuseCall_ea = BADADDR,call_tgt_ea = BADADDR,nopit=False,pretips = ""):
  bMatchMode = False
  ww = c_ulong(0)
  im8 = c_byte(0)
  #if (Byte(call_tgt_ea) == 0x68) and ((Dword(call_tgt_ea + 5) & 0xFFFFFF) == 0x240481):
  #if (Byte(call_tgt_ea) == 0x68) and ((Dword(call_tgt_ea + 5) & 0xFFFFFF) == 0x242C81):
  bSubMode1 = (Byte(call_tgt_ea) == 0x68) and ((Dword(call_tgt_ea + 5) & 0xFFFFFF) == 0x240481) # push im32,add [esp],im32
  bSubMode2 = (Byte(call_tgt_ea) == 0x68) and ((Dword(call_tgt_ea + 5) & 0xFFFFFF) == 0x242C81) # push im32;sub [esp],im32
  if bSubMode1 or bSubMode2:
    ww.value = Dword(call_tgt_ea + 1)
    if bSubMode1:
      ww.value = ww.value + Dword(call_tgt_ea + 8)
    elif bSubMode2:
      ww.value = ww.value - Dword(call_tgt_ea + 8)
    else:
      pass
    ReadCall_ea = ww.value
    if (Byte(call_tgt_ea + 0x0C) == 0x74) and (Byte(call_tgt_ea + 0x0E) == 0xC2): # jz.0x74 im8;retn.0xC2 im16
      retnesp = Word(call_tgt_ea + 0x0E + 1)
      im8.value = Byte(call_tgt_ea + 0x0C + 1)
      jzea = (call_tgt_ea + 0x0E) + im8.value
      confuse_end = jzea + 2
      #
      subesp = 0
      if Word(ConfuseCall_ea - 3) == 0xEC83: # sub esp,im8
        subesp = Byte(ConfuseCall_ea - 1)
      else:
        pass
      #
      ConfuseStart = hlafindpX(ConfuseCall_ea,(retnesp-subesp-4-4)/4)
      print pretips,"MODE2","ConfuseZone: from {:08X} to {:08X} jmp {:08X}".format(ConfuseStart,confuse_end,ReadCall_ea)
      if nopit:
        ClearConfuse(ConfuseStart,confuse_end,ReadCall_ea)
      bMatchMode = True
    else:
      raise Exception("confuse mode2 found not jz {:08X}".format(call_tgt_ea + 0x0C))
  return bMatchMode

subadd1 = [[0x2d,0x05]]
#subadd eax
def checkConfuseMode1(ConfuseCall_ea = BADADDR,call_tgt_ea = BADADDR,nopit=False,pretips = ""):
  global subadd1
  bMatchMode = False
  #print "Search Confuse chip in ({:08X} ~ {:08X})".format(ea_s,ea_e)
  calltgt_add = c_ulong(0)
  jzoff = c_long(0)
  #check confuseMode1_op1
  if (Byte(call_tgt_ea) == 0x87) and (Byte(call_tgt_ea+2) == 0x24): # xchg reg,[esp]
    subtor = 0
    addtor = 0
    subadd_size = 0
    #check confuseMode1_op2
    if (Byte(call_tgt_ea + 3) == 0x81) and ((Byte(call_tgt_ea + 4) & 0xF8) == 0xE8): # sub reg,imm32
      subtor = Dword(call_tgt_ea + 5)
      if (Byte(call_tgt_ea + 9) == 0x81) and ((Byte(call_tgt_ea + 10) & 0xF8) == 0xC0): # add reg,imm32
        #
        addtor = Dword(call_tgt_ea + 11)
        subadd_size = 6
    #check confusemode1_op2
    elif [Byte(call_tgt_ea+3),Byte(call_tgt_ea+8)] in subadd1: #
      subtor = Dword(call_tgt_ea + 4)
      addtor = Dword(call_tgt_ea + 9)
      subadd_size = 5
    #check confusemode1_op2, find new op2
    else:
      raise Exception( "-----------find new subadd at {:08X}".format(ConfuseCall_ea))
    calltgt_add.value = (addtor - subtor)
    if 0 == calltgt_add.value:
      print "{:08X}".format(call_tgt_ea)
      raise Exception("match jz")
    calltgt_add.value = ConfuseCall_ea + 5 + calltgt_add.value
    ReadCall_ea = calltgt_add.value
    # got ReadCall_ea
    # try to get confuse-push-stack-size
    subesp = 0
    if Word(ConfuseCall_ea - 3) == 0xEC83: # sub esp,im8
      subesp = Byte(ConfuseCall_ea - 1)
    else:
      pass
      # maybe raise bug
      #print "{:08X} {:04X}".format(ConfuseCall_ea - 3,Word(ConfuseCall_ea - 3))
      #raise Exception("pre code is not 83 EC xx -- sub esp,im8")
    # try to get confuse-chip-end
    confuse_end = 0
    # confuse-chip-end for jz
    if Byte(call_tgt_ea + 3 + subadd_size*2) == 0x74: #jz  jmp.im8 0xEB im8
      jzoff.value = Byte(call_tgt_ea + 3 + subadd_size*2 + 1)
      jz_ea  = call_tgt_ea + 3 + subadd_size*2 + 2 + jzoff.value
      confuse_end = jz_ea + 2
    # confuse-chip-end for jnz
    elif Byte(call_tgt_ea + 3 + subadd_size*2) == 0x75: #jnz
      pass
      #stry to set confuse_end at retn_ea.2
    # need more
    else:
      raise Exception("find ohter jz,jnz >> {:08X} ReadCall_ea= {:08X}".format(call_tgt_ea + 3 + subadd_size*2,ReadCall_ea))
    #retn_ea = hlaSecssionInstrX(call_tgt_ea + 15,ReadCall_ea,["retn    "])
    retn_ea = FindBinary(call_tgt_ea + 3 + subadd_size*2,SEARCH_CASE|SEARCH_DOWN|SEARCH_NEXT,"C2")
    if (confuse_end == 0) and (retn_ea != BADADDR):
      confuse_end = retn_ea + 2 + 3 #for jnz
    retnesp = 0
    if retn_ea and (retn_ea < confuse_end) and (Byte(retn_ea) == 0xC2):
      retnesp = Word(retn_ea + 1)
    else:
      print "{:08X}".format(call_tgt_ea)
      raise Exception("retn not found, or foud but not 0xC2: retn_ea={:08X} confuse_end={:08X}".format(retn_ea,confuse_end))
    #
    #print "Search Confuse chip in ({:08X} ~ {:08X})".format(ea_s,ea_e),
    #print "subesp: {:04X} retnesp: {:04X} pushespNum:{}".format(subesp,retnesp,(retnesp-subesp-4)/4)
    ConfuseStart = hlafindpX(ConfuseCall_ea,(retnesp-subesp-4)/4)
    print pretips,"MODE1","ConfuseZone: from {:08X} to {:08X} jmp {:08X}".format(ConfuseStart,confuse_end,ReadCall_ea)
    if nopit:
      ClearConfuse(ConfuseStart,confuse_end,ReadCall_ea)
    bMatchMode = True
  return bMatchMode
  #ConfuseCall_ea = hlaSecssionInstrX(ConfuseCall_ea+5,ea_e,["call    "])

def ClearConfuse(ConfuseStart,confuse_end,ReadCall_ea):
  jzoff = c_long(0)
  for ai in xrange(ConfuseStart,confuse_end):
    PatchByte(ai,0x90)
    MakeCode(ai)
  MakeCode(confuse_end)
  #
  jzoff.value = ReadCall_ea - (ConfuseStart + 2)
  if (jzoff.value <= 128) and (jzoff.value >= -127):
    PatchByte(ConfuseStart,0xEB)
    PatchByte(ConfuseStart+1,c_ulong(jzoff.value).value)
  else:
    jzoff.value = ReadCall_ea - (ConfuseStart + 5)
    PatchByte(ConfuseStart,0xE9)
    PatchDword(ConfuseStart+1,c_ulong(jzoff.value).value)

def hlaSecssionInstrX(ea_start = 0,ea_end = 0, instr = ['push'], instr_index = 0):
  # [ea_start,ea_end) for SEARCH_DOWN zone (ea_start,ea_END] for SEARCH_UP zone
  # instr is the target instr-part
  # instr_index is the x-th time the instr occur, 0 mean the first time
  # update: change instr from single str to [str,str,...]
  if (ea_start < 0) or (ea_end < 0):
     Message('ea_start or ea_end can not be zero\n')
  if ea_start > ea_end:
     cur_code_addr = ea_end
     cnt = 0
     while cur_code_addr > ea_start:
       hlaasm = GetDisasm(cur_code_addr)
       for instr_ in instr:
         if instr_ in hlaasm:
           cnt = cnt + 1
           if (cnt-1) == instr_index:
             return cur_code_addr
       cur_code_addr = FindCode(cur_code_addr,SEARCH_NEXT|SEARCH_UP)
  else:
     cur_code_addr = ea_start
     cnt = 0
     while cur_code_addr < ea_end:
       hlaasm = GetDisasm(cur_code_addr)
       for instr_ in instr:
         if instr_ in hlaasm:
           cnt = cnt + 1
           if (cnt-1) == instr_index:
             return cur_code_addr
       cur_code_addr = FindCode(cur_code_addr,SEARCH_NEXT|SEARCH_DOWN)
  return 0

def hlafindpX(calladdr = 0x0, paramX = 0x0):
    searchflag = SEARCH_NEXT | SEARCH_CASE & ~SEARCH_DOWN
    #paramX is the (paramX+1)th parameter, so-called --index parameters
    pX = 0
    codeaddr = calladdr
    while pX <= paramX:
        codeaddr = FindCode(codeaddr,searchflag)
        if IshlaPush(codeaddr):
            pX = pX + 1
    return codeaddr

def IshlaPush(codeaddr=0x0):
    retbool = 0
    hlacodebyte = Byte(codeaddr)
    for pushflag in xrange(0x50,0x58):
        if hlacodebyte == pushflag:
            retbool = 2
            return retbool
    if hlacodebyte == 0x6A:
        retbool = 2
        return retbool
    if hlacodebyte == 0x68:
        retbool = 3
        return retbool
    if hlacodebyte == 0x0E or hlacodebyte == 0x16:
        retbool = 4
        return retbool
    if hlacodebyte == 0x1E or hlacodebyte == 0x06:
        retbool = 4
        return retbool
    if Word(codeaddr) == 0xA00F or Word(codeaddr) == 0xA80F:
        retbool = 4
        return retbool
    if Word(codeaddr) in [0x77FF,0x73FF,0x75FF,0x76FF,0x70FF,0x71FF,0x72FF,0x74FF,0x76FF]:
        #70 eax,ecx,edx,ebx,esp,ebp,esi,edi
        retbool = 5
        return retbool
---------------------------------------------------------------------------------------------------------------
分析的ida数据库,及去混淆IDAPython脚本
PEDIY_2016CTF_03_ClearConfuseIDAPython.7z

另外,如果修改IDA数据如清除或修改了不该清除修改的地址的数据,
笨点办法推到重来,重新加载IDA,
不过在你想进行此操作前,可以考虑以下挽救方式,即还原 ea 到 ee整个区域的数据。
def huanyuan(ea,ee):
    for a in xrange(es,ee):
        PatchByte(a,GetOriginalByte(a))


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 2
支持
分享
最新回复 (8)
雪    币: 134
活跃值: (11)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
大神,能否分享下去混淆的脚本,我手动去混淆有些函数还是不能f5,想学习下
2016-11-8 13:39
0
雪    币: 13719
活跃值: (2851)
能力值: ( LV15,RANK:2663 )
在线值:
发帖
回帖
粉丝
3
求去混淆具体过程
2016-11-8 14:09
0
雪    币: 17740
活跃值: (4796)
能力值: ( LV15,RANK:1878 )
在线值:
发帖
回帖
粉丝
4
已补充分析和相关脚本。
2016-11-8 22:35
0
雪    币: 112
活跃值: (37)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
5
膜拜楼主,感谢分享
2016-11-10 18:17
0
雪    币: 144
活跃值: (335)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
五体投地。。。
2016-11-13 09:53
0
雪    币: 223
活跃值: (42)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
膜拜神牛!
2016-12-5 11:55
0
雪    币: 6
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
膜拜。。。 去混淆看晕了,。。。。。佩服
2017-2-13 17:46
0
雪    币: 870
活跃值: (2264)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
写的很好
2018-7-24 15:04
0
游客
登录 | 注册 方可回帖
返回