还好,赶上了末班车,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"
百度了以下就出来了,直接下载了
4abK9s2c8@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,
©,
...
}
.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直播授课
上传的附件: