根据CM023的经验,仍然是下获取编辑框文本的断点(GetWindowText或GetDlgItemText)。
代码的主要逻辑还是比较好懂的。
仍然是获取密码框的int值,压栈保存,获取用户名框的值,
0x58455443循环加用户名DWORD 然后用户名地址+1
弹出密码框int值,和上边循环相加的值相加,先认为这个结果为eax,
eax和0x584554异或保存在全局变量中,然后全局变量减去eax右移0x10的word值(ax)。
注意这个全局变量是在代码段,改变就会改变对应的代码。这是个唯一的去正确提示的突破点。
jmp是个完美的跳转,当然其他也可以,就是得看看当时的标志位满足不满足。
所以用反汇编试了试jmp到成功的提示CALL处(需要调到push那个位置),结果opcode是 EB 26,OK,
这个就是突破点。要让这个全局变量结果是0x26EB,继续分析。
仅仅这样改了跳转还不够,还需要让程序走到我们的跳转处,这就需要达到作者的第二重验证。发现把0x3E给了ecx当做计数器。然后调用lods dword ptr ds:[esi],这作者太坑了,把自己的程序opcode当做加密方法,还一下来了3E次,理论上如果我们写注册机的话,就得保存3E个它的DWORD值.....
程序经过3E次循环异或后,得数与0xAFFCCFFB比较,相等就会跳到我们刚才的jmp处。
经过分析,发现妈的那个esi指向的位置循环3E次,1次4字节,到最后那这一大段代码
中包括了我们要改的代码....这一处代码用两处验证,有点棘手。
程序思路就是代码opcode经过异或后需要等于0xAFFCCFFB才能跳到需要的地方,变动也就4个字节的opcode,逆着把无关紧要的代码异或掉,留下需要改变的那4个字节XXXXXX04 D833ADXX.。看似不连在一起,无从下手,这里就要用到异或的特性:
------------------------------------------------
1、 123456 XOR D8219956 D833AD00
------------------------------------------------
2、 123456 XOR D8219900 D833AD56
D833AD56 XOR 56 D833AD00
------------------------------------------------
想了解异或的特性的朋友可以看看这篇文章:http://www.cnblogs.com/suoloveyou/archive/2012/04/25/2470292.html
上方1和2可以看出(123456 xor D8219956)结果和((123456 xor D8219900) xor 56)一样那么就可以把那4个字节的opcode全填成0让他异或,也就是不会变的数据经过异或后得到的数据A记下来。
004012DD AD lods dword ptr ds:[esi]
004012DE 33D8 xor ebx,eax
004012E0 49 dec ecx
它存在ebx中,让那4个字节用00填充,经过循环异或后得到的ebx记下来。
用00填充后代码就是这样的
004012D7 EB 04 jmp XChafe_2.004012DD
004012D9 0000 add byte ptr ds:[eax],al
004012DB 0000 add byte ptr ds:[eax],al
004012DD AD lods dword ptr ds:[esi]
004012DE 33D8 xor ebx,eax
004012E0 49 dec ecx
004012E1 75 FA jnz XChafe_2.004012DD
==========================此处往下思路有误=======================
在内存中的4字节数据就是00000004和D833AD00。ebx经过循环后得到的结果是
0xDCDA243F,与正确结果0xAFFCCFFB差很多,差的这些数据就需要从4个00处下手了。
因为第一个DWORD中的04和第二个DWORD中的D833AD是死数据,不会改变,并且刚才已经经过了异或运算,所以把他们填0,然后写个程序遍历。
填0后的这两个DWORD是xxxxxx00和000000xx,其中xxx是需要遍历的数据,让他们异或以后的结果等于程序需要的0xAFFCCFFB。
int main(void)
{
for (DWORD i = 0x0; i <= 0xFFFFFF00; i++)//第一个4字节xxxxxx00的范围
{
if ((i & 0xFF)==0)//因为低两位的04已经运算过了,所以
//这里只的低两位必须是00才符合需要的结果
{
for (DWORD j = 0; j <= 0xFF; j++)//第二个4字节的范围
{
if (0xAFFCCFFB == ((0xB2DA24A3 ^ i) ^ j))
printf("\n%08x\t%02x\n\n", i, j);
}
}
}
return 0;
}
经过遍历,只跑出来一个结果 7326EB00 C4,放在内存中是这样的04 EB 26 73 | C4 AD 33 D8,DWORD数据是0xC47326EB
因为之前在OD里试着改过反汇编代码,看见EB 26就比较敏感,没错,他就是jmp到正确提示。
有了这些数据,剩下的工作就是让用户名和序列号经过运算得到这些数据就OK了。现在去看它的用户名&序列号校验
算法。
经过分析以后,它的算法完全明了了
00401269 . C705 D9124000>mov dword ptr ds:[<四字节代码>],0x584554 ; 初始化代码数据
00401273 . 6A 00 push 0x0 ; /IsSigned = FALSE
00401275 . 8D45 FC lea eax,dword ptr ss:[ebp-0x4] ; |
00401278 . 50 push eax ; |pSuccess
00401279 . 6A 64 push 0x64 ; |ControlID = 64 (100.)
0040127B . FF35 50314000 push dword ptr ds:[0x403150] ; |hWnd = 00050612 ('TEXme v2.0',class='CTEX')
00401281 . E8 BC010000 call <jmp.&USER32.GetDlgItemInt> ; \GetDlgItemInt
00401286 . 837D FC 00 cmp dword ptr ss:[ebp-0x4],0x0
0040128A . 74 5F je XChafe_2.004012EB
0040128C . 50 push eax ; 密码框的int值
0040128D . 6A 14 push 0x14 ; /Count = 14 (20.)
0040128F . 68 6C314000 push Chafe_2.0040316C ; |用户名的ASCII
00401294 . FF35 54314000 push dword ptr ds:[0x403154] ; |hWnd = 00050614 (class='Edit',parent=00050612)
0040129A . E8 AF010000 call <jmp.&USER32.GetWindowTextA> ; \GetWindowTextA
0040129F . 85C0 test eax,eax
004012A1 . 74 48 je XChafe_2.004012EB
004012A3 . A1 0B304000 mov eax,dword ptr ds:[<全局变量0>]
004012A8 . BB 6C314000 mov ebx,Chafe_2.0040316C ; 存放用户名地址
004012AD . 0303 add eax,dword ptr ds:[ebx]
004012AF . 43 inc ebx
004012B0 . 81FB 7C314000 cmp ebx,Chafe_2.0040317C
004012B6 . 75 F5 jnz XChafe_2.004012AD ; 58455443循环加用户名DWORD
004012B8 . 5B pop ebx
004012B9 . 03C3 add eax,ebx ; 密码框的int值相加
004012BB . 3105 D9124000 xor dword ptr ds:[<四字节代码>],eax ; 原始代码与运算后的值异或
004012C1 . C1E8 10 shr eax,0x10
004012C4 . 66:2905 D9124>sub word ptr ds:[<四字节代码>],ax ; 减去右移16次的word值
004012CB . BE EC114000 mov esi,Chafe_2.004011EC
004012D0 . B9 3E000000 mov ecx,0x3E ; 算法2的计数器
004012D5 . 33DB xor ebx,ebx
004012D7 . EB 04 jmp XChafe_2.004012DD ; -----jmp算法2-----
004012D9 . 54 push esp ; 这里的代码每次都会初始化为
004012DA 45 db 45 ; 0x00584554
004012DB 58 db 58
004012DC 00 db 00
004012DD . AD lods dword ptr ds:[esi] ; ---算法2---
注释已经写的很明白了,接下来就是下个注册机。
=============================有误解除===========================
想法挺好的,然后注册机没搞出来。这就是为什么说思路有误。因为想法和现实有些出入。
注册机确实写出来了,但是跑出来的密码只能在调试的时候成功一次,然后就不成功了,一直想不通是为什么。看了断点分析才知道,原来软件断点是在opcode第一个字节被替换成了0xCC,这也是单步的原理。这就是为什么调试状态下可以成功,然后取消断点或者不调试运行莫名其妙的就不成功了,因为0xCC断点,所以它的代码opcode异或值在调试的时候一直就不是原原本本的opcede。
如下图:本来的opcode读入寄存器应该是83EC8B55,但图中读入寄存器eax的值却是83ECCC55,原因就是软件断点,第一个字节填充成了0xCC。这就很坑爹了,因为调试过程中,会下N个断点来检测当时数据是否为我们猜想的数据,把这好几个断点带的0xCC参与校验,偏差恐怕要大很多很多。
在此思路有二: 1、手动计算opceode的异或值 2、硬件断点试试。
为了懒省事,先用硬件断点试试。
004012D7 . /EB 04 jmp XChafe_2.004012DD ; 硬件断点
004012D9 > |0000 add byte ptr ds:[eax],al ; 这里填00
004012DB |0000 add byte ptr ds:[eax],al ; 这里填00
004012DD > \AD lods dword ptr ds:[esi]
004012DE . 33D8 xor ebx,eax
004012E0 49 dec ecx
004012E1 ^ 75 FA jnz XChafe_2.004012DD
004012E3 81FB FBCFFCAF cmp ebx,0xAFFCCFFB ; 硬件断点
jmp处的硬件断点断下以后,把后边4字节用00填充,然后F9运行到cmp处的硬件断点,记下ebx的值。这个值就是缺少那4个字节的值,要用到他去穷举那4个字节可能出现的值。
新的穷举算法:int main(void)
{
for (DWORD i = 0x0; i <= 0xFFFFFF00; i++)//第一个4字节xxxxxx00的范围
{
if ((i & 0xFF) == 0)//因为低两位的04已经运算过了,所以
//这里只的低两位必须是00才符合需要的结果
{
for (DWORD j = 0; j <= 0xFF; j++)//第二个4字节的范围
//因为前3个字节已经异或过了,这里只有最后一个字节被我们填00了
//所以范围就是0-0xFF。
{ //正确的值 //ebx的值
if (0xAFFCCFFB == (((0xFBDA24A3 ^ i)) ^ j))
printf("\n%08x\t%02x\n\n", i, j);
}
}
}
return 0;
}
跑出来的结果是 5426eb00 58,在OD里的值是这样的。
那接下来就可以写真•注册机了。int main(void)
{
CHAR szName[20] = { 0 };
cout << "Name:";
cin >> szName;
DWORD dwNum = 0x58455443;//程序实现填入的全局变量
for (int i = 0; i < strlen(szName); i++)//用户名循环相加DWORD
{
void *p = &szName[i];
_asm
{
mov edi, p;
mov eax, dword ptr[edi];
add dwNum, eax;
}
}
//////////////////////////////////////////////////////////////////////////
DWORD dwPass = 0;
while (1)
{
DWORD temp = (dwNum + dwPass) ^ 0x584554;
temp -= (WORD)((dwNum + dwPass) >> 0x10);
if (dwPass % 0x100000 == 0) //这里if语句可以不要只是用来看看它在跑密码还是死机了
cout << dwPass << endl;
if (temp == 0x585426EB)//这就是上一个算法得到的正确的opcode
break;
dwPass++;
}
cout << "Pass:"<<dwPass<<endl;
}
跑出来的结果非调试下也可以提示正确了。
提供一组用于测试:Name:pediy.com
Serial:2920336344
那总结下来原因就是因为CC断点了,经验不够丰富,一直没往这个地方想,不过收获还是挺大了。