本题考点:代码层面是花指令和进程枚举反调试;算法层面是运用miracl大数库的RSA加密算法和AES加密算法。
花指令分析及清除
这部分直接上代码看,静态分析。
.text:0040126C _main proc near ; CODE XREF: start+E4↓p
.text:0040126C jmp _main_0
.text:0040126C _main endp
start
到main
中间有个main的stub。
.text:00403130 _main_0 proc near ; CODE XREF: _main↑j
.text:00403130 jmp _main_0_0
.text:00403130 _main_0 endp
.text:00403130
这本来应该是真正的main
函数,但这却又是一个跳转,而且跳往的目标代码不正常,有花指令,应该是后来加上的。继续往下看。
.text:0045C106 _main_0_0: ; CODE XREF: _main_0↑j
.text:0045C106 jz short loc_45C10C
.text:0045C108 jnz short loc_45C10C
.text:0045C10A jmp short near ptr unk_45C10E
.text:0045C10C ; ---------------------------------------------------------------------------
.text:0045C10C
.text:0045C10C loc_45C10C: ; CODE XREF: .text:_main_0_0↑j
.text:0045C10C ; .text:0045C108↑j
.text:0045C10C jmp short loc_45C10F
.text:0045C10C ; ---------------------------------------------------------------------------
.text:0045C10E unk_45C10E db 81h ; CODE XREF: .text:0045C10A↑j
.text:0045C10F ; ---------------------------------------------------------------------------
.text:0045C10F
.text:0045C10F loc_45C10F: ; CODE XREF: .text:loc_45C10C↑j
.text:0045C10F push ebp
.text:0045C110 jz short loc_45C116
.text:0045C112 jnz short loc_45C116
.text:0045C112 ; ---------------------------------------------------------------------------
.text:0045C114 db 0E8h
.text:0045C115 db 2
.text:0045C116 ; ---------------------------------------------------------------------------
.text:0045C116
.text:0045C116 loc_45C116: ; CODE XREF: .text:0045C110↑j
.text:0045C116 ; .text:0045C112↑j
.text:0045C116 jmp short loc_45C119
.text:0045C116 ; ---------------------------------------------------------------------------
.text:0045C118 db 81h
.text:0045C119 ; ---------------------------------------------------------------------------
.text:0045C119
.text:0045C119 loc_45C119: ; CODE XREF: .text:loc_45C116↑j
.text:0045C119 mov ebp, esp
.text:0045C11B jo short loc_45C120
.text:0045C11D jno short loc_45C120
.text:0045C11D ; ---------------------------------------------------------------------------
.text:0045C11F db 0E8h
.text:0045C120 ; ---------------------------------------------------------------------------
.text:0045C120
.text:0045C120 loc_45C120: ; CODE XREF: .text:0045C11B↑j
.text:0045C120 ; .text:0045C11D↑j
.text:0045C120 sub esp, 7Ch
.text:0045C123 jmp loc_403136
里面有些花指令,手动调整了下,还并未作任何patch。
其实这里面的指令等价于:
push ebp
mov ebp,esp
sub esp,7Ch
jmp loc_403136
再看这个跳转目标loc_403136:
.text:00403136 push ebx
.text:00403137 push esi
.text:00403138 push edi
.text:00403139 lea edi, [ebp-7Ch]
.text:0040313C mov ecx, 1Fh
.text:00403141 mov eax, 0CCCCCCCCh
.text:00403146 rep stosd
.text:00403148 mov dword ptr [ebp-4], 0
.text:0040314F mov dword ptr [ebp-8], 0
.text:00403156 call loc_403160
.text:00403156 ; ---------------------------------------------------------------------------
.text:0040315B db 0E8h
.text:0040315C ; ---------------------------------------------------------------------------
.text:0040315C
.text:0040315C loc_40315C: ; CODE XREF: .text:loc_403160↓p
.text:0040315C jmp short loc_403165
.text:0040315C ; ---------------------------------------------------------------------------
.text:0040315E db 0
.text:0040315F db 0
.text:00403160 ; ---------------------------------------------------------------------------
.text:00403160
.text:00403160 loc_403160: ; CODE XREF: .text:00403156↑p
.text:00403160 call loc_40315C
.text:00403165
.text:00403165 loc_403165: ; CODE XREF: .text:loc_40315C↑j
.text:00403165 ; .text:0040317A↓j
.text:00403165 add esp, 8
.text:00403168 rdtsc
.text:0040316A push eax
.text:0040316B rdtsc
.text:0040316D sub eax, [esp]
.text:00403170 add esp, 4
.text:00403173 cmp eax, 0FFFh
.text:00403178 jbe short loc_40317C
.text:0040317A jmp short loc_403165
这里是截取了一部分代码,这段代码除了有些花指令,反调试代码,其它都正常,加上后面代码的功能,猜测应该是真正的main
函数部分代码。
再看,403136
紧临403130
,上面就说403130
处的跳转不正常。事出反常必有妖。所以403130
确实是main
函数的入口,只不过被改了指令。先还原这部分指令,去年跳转。
.text:00403130 push ebp .text:00403131 mov ebp, esp
.text:00403133 sub esp, 7Ch
.text:00403136 push ebx
.text:00403137 push esi
403156
处也有花指令,而且发现不止一处。模式为:
call l2
junk
l1:
jmp l3
junk
l2:
call l1
l3:
add esp ,8
code
直接上脚本patch成nop
。
顺便把rdtsc
的代码也patch掉。
main
函数patch后的伪代码如下:
int main_0()
{
int v0; // eax
int result; // eax
char v2; // [esp+50h] [ebp-40h]
__int16 v3; // [esp+51h] [ebp-3Fh]
char input[24]; // [esp+54h] [ebp-3Ch]
char error[8]; // [esp+6Ch] [ebp-24h]
char success[8]; // [esp+78h] [ebp-18h]
__int16 v7; // [esp+80h] [ebp-10h]
BOOL v8; // [esp+84h] [ebp-Ch]
int v9; // [esp+88h] [ebp-8h]
v9 = 0;
v8 = 0;
strcpy(success, "rw`g`ut");
v7 = 0;
strcpy(error, "dpqkw");
*(_DWORD *)&error[6] = 0;
input[0] = 0;
*(_DWORD *)&input[1] = 0;
*(_DWORD *)&input[5] = 0;
*(_DWORD *)&input[9] = 0;
*(_DWORD *)&input[13] = 0;
*(_DWORD *)&input[17] = 0;
*(_WORD *)&input[21] = 0;
input[23] = 0;
v2 = 0;
v3 = 0;
print_banner(); // print
sub_40100A(); // anti
j_xor_with_idx_1((int)success);
j_xor_with_idx_1((int)error);
scanf("%s", input, 24);
if ( strlen(input) > 0x17 )
{
printf(error);
exit(0);
}
v0 = strlen(&input[3]);
unhex((int)&input[3], (int)&str_hex_495660, v0);
v9 = j_check1();
memcpy(&v2, input, 3u);
if ( j_check_isdigit(&v2) )
{
v8 = j_check2((int)&v2);
if ( v8 + v9 == 2 )
printf(success);
else
printf(error);
system("pause");
result = 0;
}
else
{
printf(error);
result = 0;
}
return result;
}
反调试
rdtsc
反调试只有一处。patch花指令时同时处理了。
剩下的还有sub_40100A
函数,这里能不能算是反调试,我说不清楚。我也没明白这里设置的作用。主要代码功能就是进程检查,如果父进程是explorer.exe
,则创建新进程并结束当前进程。因为这里影响AES密钥。我的做法是不用管,事实上对OD没什么作用(其实是如果不patch则后面的调试不了,如果patch注意此对AES的密钥影响),也没有影响AES加密结果。如果直接patch掉,就会影响AES密钥。
流程及算法
main
函数中的主要流程是:
- 打印banner
- 反调试
- 解码结果的正确与否的信息字串
- 输入
- 检查输入长度不大于24
- 输入的第4字节开始至结束转成16进制字符进行check1
- 检查输入的前3字节为数字
- 输入的前3字节作为参数进行check2
check1
check1算法用了miracl大数库。
基本过程是:
- 解码两个16进制字符串n,c
- 将输入转成的16进制串m,
3e9
及n转成大数
- 计算m^0x3e9%n的值 c1
- c1与c比较
很明显这是一个RSA算法计算过程。正好n可以在factordb上直接查到。
p=208096057845685678782766058500526476379
q=273086345401562743300402731618892888991
e=1001
n=0x7da39de66016477b1afc3dc8e309dc429b5de855f0d616d225b570b68b88a585
c=0x208CBB7CD6ECC64516D07D978F5F0681F534EAD235D5C49ADD72D2DB840D5304
>>> d=gmpy2.invert(e,(p-1)*(q-1))
>>> pow(c,d,n)
mpz(601616731606062377067631775469716020478784069937L)
>>> hex(601616731606062377067631775469716020478784069937)
'0x69616d6168616e64736f6d656775796861686131L'
>>> '69616d6168616e64736f6d656775796861686131'.decode('hex')
'iamahandsomeguyhaha1'
现在还差前面的3字节数字了
check2
这部分算法是AES(有改动)。输入数据的前三字节作为了key的前三字节(第3字节与反调试代码有关系)。
AES密钥再引入输入前是0001314000000000
。用输入的前三字节替换密钥的相应位置,并将第三字节加上495728
处的数值(在sub_40100A`函数中设置),其实就是1。
所以密钥也不用枚举了,大家应该都能猜出来5211314000000000
,输入是前三字节520
。
AES加密:
明文pediy 后面补00
密钥 5211314000000000
加密结果:912CA2036A9A0656D17B6B552F157F8E (16进制)
所以最终flag为:520iamahandsomeguyhaha1
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。