西南某比赛的三个简单逆向题分析
比赛还有一个多小时就结束了,大家都没做题了,希望这时候发破文出来对比赛没有影响。
第一题:
这是一个非常简单的CrackMe,找注册码很容易,写注册机也只需简要分析。
首先载入OD,在GetDlgItemTextA函数处下断,运行后来到这里。
代码:
00401241 |. 6A 0A PUSH 0A ; /Count = A (10.)
00401243 |. 52 PUSH EDX ; |Buffer => CrackMe.0040300C
00401244 |. 68 F2030000 PUSH 3F2 ; |ControlID = 3F2 (1010.)
00401249 |. FFB5 FCFEFFFF PUSH DWORD PTR SS:[EBP-104] ; |hWnd
0040124F |. E8 1A010000 CALL <JMP.&user32.GetDlgItemTextA> ; \GetDlgItemTextA
00401254 |. 8D05 0C304000 LEA EAX,DWORD PTR DS:[40300C]
0040125A |. 50 PUSH EAX ; /Arg1 => 0040300C ASCII "Speday"
0040125B |. E8 2DFEFFFF CALL CrackMe.0040108D ; \CrackMe.0040108D
00401260 |. 8D15 0C304000 LEA EDX,DWORD PTR DS:[40300C]
00401266 |. 6A 0A PUSH 0A ; /Count = A (10.)
00401268 |. 52 PUSH EDX ; |Buffer => CrackMe.0040300C
00401269 |. 68 F3030000 PUSH 3F3 ; |ControlID = 3F3 (1011.)
0040126E |. FFB5 FCFEFFFF PUSH DWORD PTR SS:[EBP-104] ; |hWnd
00401274 |. E8 F5000000 CALL <JMP.&user32.GetDlgItemTextA> ; \GetDlgItemTextA
00401279 |. 68 20304000 PUSH CrackMe.00403020 ; /String2 = ""
0040127E |. 68 0C304000 PUSH CrackMe.0040300C ; |String1 = "Speday"
00401283 |. E8 3A010000 CALL <JMP.&kernel32.lstrcmpA> ; \lstrcmpA
00401288 |. 85C0 TEST EAX,EAX
0040128A |. 75 1B JNZ SHORT CrackMe.004012A7
0040128C |. 6A 40 PUSH 40 ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
0040128E |. FF35 A1204000 PUSH DWORD PTR DS:[4020A1] ; |Title = "成功"
00401294 |. FF35 A5204000 PUSH DWORD PTR DS:[4020A5] ; |Text = "注册成功!"
0040129A |. FFB5 FCFEFFFF PUSH DWORD PTR SS:[EBP-104] ; |hOwner
004012A0 |. E8 E7000000 CALL <JMP.&user32.MessageBoxA> ; \MessageBoxA
很明显,0040125B这个CALL是关键CALL,单步进去看一下。
代码:
0040108D /$ 55 PUSH EBP
0040108E |. 8BEC MOV EBP,ESP
00401090 |. 83C4 F4 ADD ESP,-0C
00401093 |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8] ; 计算name字段的长度
00401096 |. 50 PUSH EAX ; /String
00401097 |. E8 2C030000 CALL <JMP.&kernel32.lstrlenA> ; \lstrlenA
0040109C |. 48 DEC EAX ; 长度减1,后面有用
0040109D |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX
004010A0 |. 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8]
004010A3 |. 8D3D 20304000 LEA EDI,DWORD PTR DS:[403020]
004010A9 |. 33DB XOR EBX,EBX
004010AB |. 895D F8 MOV DWORD PTR SS:[EBP-8],EBX
004010AE |. EB 4B JMP SHORT CrackMe.004010FB
004010B0 |> 33C0 /XOR EAX,EAX
004010B2 |. 8A0433 |MOV AL,BYTE PTR DS:[EBX+ESI] ; 取name的第一个字符
004010B5 |. C1F8 04 |SAR EAX,4 ; 右移4位
004010B8 |. 8845 F7 |MOV BYTE PTR SS:[EBP-9],AL ; 存到变量中
004010BB |. 33D2 |XOR EDX,EDX
004010BD |. 8B45 F8 |MOV EAX,DWORD PTR SS:[EBP-8]
004010C0 |. B9 02000000 |MOV ECX,2
004010C5 |. F7F1 |DIV ECX ; eax=eax/2
004010C7 |. 33C0 |XOR EAX,EAX ; edx=eax%ecx
004010C9 |. 33C9 |XOR ECX,ECX
004010CB |. 83FA 01 |CMP EDX,1 ; 判断edx(余数)是否等于1
004010CE |. 74 0F |JE SHORT CrackMe.004010DF
004010D0 |. 8A4433 01 |MOV AL,BYTE PTR DS:[EBX+ESI+1] ; 取后一个字符
004010D4 |. C1E0 1C |SHL EAX,1C ; 左移28位
004010D7 |. C1E8 1C |SHR EAX,1C ; 右移28位
004010DA |. 83C0 41 |ADD EAX,41 ; 加上大写字母A的ascii码
004010DD |. EB 0D |JMP SHORT CrackMe.004010EC
004010DF |> 8A4433 FF |MOV AL,BYTE PTR DS:[EBX+ESI-1] ; 取前一个字符
004010E3 |. C1E0 1C |SHL EAX,1C ; 左移28位
004010E6 |. C1E8 1C |SHR EAX,1C ; 右移28位
004010E9 |. 83C0 61 |ADD EAX,61 ; 加上小写字母a的ascii码
004010EC |> 8A4D F7 |MOV CL,BYTE PTR SS:[EBP-9] ; 取出上面变量中存的值
004010EF |. 03C1 |ADD EAX,ECX ; 加在eax上
004010F1 |. 88043B |MOV BYTE PTR DS:[EBX+EDI],AL ; 存起来
004010F4 |. 8B5D F8 |MOV EBX,DWORD PTR SS:[EBP-8]
004010F7 |. 43 |INC EBX
004010F8 |. 895D F8 |MOV DWORD PTR SS:[EBP-8],EBX
004010FB |> 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
004010FE |. 3BD8 |CMP EBX,EAX ; 判断是不是倒数第二个字符
00401100 |.^ 7C AE \JL SHORT CrackMe.004010B0
00401102 |. 8A0433 MOV AL,BYTE PTR DS:[EBX+ESI] ; 取最后一位字符
00401105 |. C1E0 1C SHL EAX,1C ; 左移28位
00401108 |. C1F8 1C SAR EAX,1C ; 右移28位
0040110B |. 83C0 31 ADD EAX,31 ; 加上数字1的ascii码
0040110E |. 88043B MOV BYTE PTR DS:[EBX+EDI],AL ; 存起来
00401111 |. 33C0 XOR EAX,EAX
00401113 |. 43 INC EBX
00401114 |. 88043B MOV BYTE PTR DS:[EBX+EDI],AL ; 末尾置0
00401117 |. C9 LEAVE
00401118 \. C2 0400 RETN 4
结合上面的分析,自己用C语言写了个注册机,供大家参考。
代码:
#include<stdio.h>
#include<string.h>
#include<windows.h>
main()
{
char name[100];
char key[100];
unsigned int i,j,a,b,c,d;
int m;
printf("Please input a name:\t");
scanf("%s",name);
j=lstrlen(name);
for(i=0;i<j-1;i++)
{
m=name[i];
a=m>>4;
c=a;
d=i%2;
if(d==1)
{
a=name[i-1];
a=a<<0x1c;
a=a>>0x1c;
a=a+0x61;
a=a+c;
key[i]=a;
}
else
{
a=name[i+1];
a=a<<0x1c;
a=a>>0x1c;
a=a+0x41;
a=a+c;
key[i]=a;
}
}
m=name[j-1];
m=m<<0x1c;
m=m>>0x1c;
m=m+0x31;
key[i]=m;
key[i+1]=0;
printf("The key is :\t%s\n",key);
getchar();
getchar();
}
第二个题:
平时几乎没遇到过.net的程序,这次居然遇到了,所以拿出来与大家分享下。
第一次看到.net程序,感觉无从下手。连用什么样的编译器都不知道,于是翻出《加密与解密》看了会,然后到看雪官网上下载了一个反编译器:Reflector。
将程序拉进去,一头雾水,都是抱着试试看的心态逐个找,后来惊讶的发现.net程序居然可以完全反编译成这样。
代码:
Private Sub button1_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim array As Char() = Me.textBox1.Text.Replace("e", "").Replace("E", "e").Replace("3", "E").ToCharArray
Array.Reverse(array)
Dim s As New String(array)
Dim chArray2 As Char() = Convert.ToBase64String(Encoding.GetEncoding("UTF-8").GetBytes(s)).ToCharArray
Array.Reverse(chArray2)
Dim str4 As New String(chArray2)
If (str4 = "==wYjsCZlN2TtBVasVWP0hUa181YIFTYxETRudUR") Then
MessageBox.Show(ChrW(39564) & ChrW(35777) & ChrW(36890) & ChrW(36807) & ChrW(65281) & "KEY" & ChrW(23601) & ChrW(26159) & ChrW(20320) & ChrW(36755) & ChrW(20837) & ChrW(30340) & ChrW(23494) & ChrW(30721), ChrW(25104) & ChrW(21151), MessageBoxButtons.OK, MessageBoxIcon.Asterisk)
Else
MessageBox.Show(ChrW(39564) & ChrW(35777) & ChrW(19981) & ChrW(36890) & ChrW(36807) & ChrW(65281) & ChrW(35831) & ChrW(37325) & ChrW(26032) & ChrW(36755) & ChrW(20837), ChrW(22833) & ChrW(36133), MessageBoxButtons.OK, MessageBoxIcon.Hand)
Me.textBox1.Text = ""
End If
End Sub
虽然反编译出来了,但是没有学过.net程序,还是有点麻烦,那就一个个看吧。
首先从最后一个比较开始,因为这里决定成功与否。
看到这句时,直觉告诉我是将字符串交换顺序Array.Reverse(chArray2),于是我把"==wYjsCZlN2TtBVasVWP0hUa181YIFTYxETRudUR"换了下顺序,得到
RUduRTExYTFIY181aUh0PWVsaVBtT2NlZCsjYw==,继续入往上看,发现是base64加密的,于是拿到百度上去解密,得到EGnE11a1Hc_5iHt=eliPmOced+#c,
继续往上看,又是Array.Reverse(array),交换顺序吧(交换顺序很麻烦,特别是数字0和字母O,小写l和数字1,一不小心就。。)
再往 上看,replace,说明是换字母,换好后如下:c#+dEcOmPilE=tHi5_cH1a113nG3,输入进去OK了,呵呵。
第三个题:
这是一个服务器验证类型的题目,刚拿到时感觉无从下手,因为程序运行后就是一句话:server start!由于很久没写过通信的程序,觉得要写个客户端和它通信简直太麻烦了,
后来上厕所时突然想到DOS下的telnet命令,于是回来就把它拿下了。嘿嘿。。
首先,用recv API函数下断,然后往后跟,来到这里
代码:
00401030 /> \55 PUSH EBP
00401031 |. 8BEC MOV EBP,ESP
00401033 |. 83EC 50 SUB ESP,50
00401036 |. 53 PUSH EBX
00401037 |. 56 PUSH ESI
00401038 |. 57 PUSH EDI
00401039 |. 8D7D B0 LEA EDI,DWORD PTR SS:[EBP-50]
0040103C |. B9 14000000 MOV ECX,14
00401041 |. B8 CCCCCCCC MOV EAX,CCCCCCCC
00401046 |. F3:AB REP STOS DWORD PTR ES:[EDI]
00401048 |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
0040104B |. 50 PUSH EAX
0040104C |. E8 EF090000 CALL Server.00401A40 ; 计算name长度
00401051 |. 83C4 04 ADD ESP,4
00401054 |. 8945 F8 MOV DWORD PTR SS:[EBP-8],EAX
00401057 |. 8B4D 0C MOV ECX,DWORD PTR SS:[EBP+C]
0040105A |. 51 PUSH ECX
0040105B |. E8 E0090000 CALL Server.00401A40 ; 计算serial的长度
00401060 |. 83C4 04 ADD ESP,4
00401063 |. 8B55 F8 MOV EDX,DWORD PTR SS:[EBP-8]
00401066 |. D1E2 SHL EDX,1 ; name长度乘以2
00401068 |. 3BC2 CMP EAX,EDX ; 如果serial长度不等于name长度乘以2,则直接退出
0040106A |. 73 04 JNB SHORT Server.00401070
0040106C |. 32C0 XOR AL,AL
0040106E |. EB 6D JMP SHORT Server.004010DD
00401070 |> C745 FC 00000>MOV DWORD PTR SS:[EBP-4],0
00401077 |. EB 09 JMP SHORT Server.00401082
00401079 |> 8B45 FC /MOV EAX,DWORD PTR SS:[EBP-4]
0040107C |. 83C0 01 |ADD EAX,1
0040107F |. 8945 FC |MOV DWORD PTR SS:[EBP-4],EAX
00401082 |> 8B4D FC MOV ECX,DWORD PTR SS:[EBP-4]
00401085 |. 3B4D F8 |CMP ECX,DWORD PTR SS:[EBP-8] ; 判断是否已经算完
00401088 |. 73 51 |JNB SHORT Server.004010DB
0040108A |. 8B55 08 |MOV EDX,DWORD PTR SS:[EBP+8]
0040108D |. 0355 FC |ADD EDX,DWORD PTR SS:[EBP-4]
00401090 |. 0FBE02 |MOVSX EAX,BYTE PTR DS:[EDX] ; 取name的第一个字符
00401093 |. 35 FF000000 |XOR EAX,0FF ; 将第一个字符取反
00401098 |. 2B45 FC |SUB EAX,DWORD PTR SS:[EBP-4]
0040109B |. 8945 F4 |MOV DWORD PTR SS:[EBP-C],EAX ; 将eax中的值转换为小写字符
0040109E |. 6A 10 |PUSH 10 ; /Arg3 = 00000010
004010A0 |. 8D4D F0 |LEA ECX,DWORD PTR SS:[EBP-10] ; |
004010A3 |. 51 |PUSH ECX ; |Arg2
004010A4 |. 8B55 F4 |MOV EDX,DWORD PTR SS:[EBP-C] ; |
004010A7 |. 52 |PUSH EDX ; |Arg1
004010A8 |. E8 D3830000 |CALL Server.00409480 ; \Server.00409480
004010AD |. 83C4 0C |ADD ESP,0C
004010B0 |. 0FBE45 F0 |MOVSX EAX,BYTE PTR SS:[EBP-10] ; 取得到的第一个字符
004010B4 |. 8B4D FC |MOV ECX,DWORD PTR SS:[EBP-4]
004010B7 |. 8B55 0C |MOV EDX,DWORD PTR SS:[EBP+C]
004010BA |. 0FBE0C4A |MOVSX ECX,BYTE PTR DS:[EDX+ECX*2] ; 取输入的第一个字符
004010BE |. 3BC1 |CMP EAX,ECX ; 对比
004010C0 |. 75 13 |JNZ SHORT Server.004010D5
004010C2 |. 0FBE55 F1 |MOVSX EDX,BYTE PTR SS:[EBP-F] ; 取上面计算 得到的第二个字符
004010C6 |. 8B45 FC |MOV EAX,DWORD PTR SS:[EBP-4]
004010C9 |. 8B4D 0C |MOV ECX,DWORD PTR SS:[EBP+C]
004010CC |. 0FBE4441 01 |MOVSX EAX,BYTE PTR DS:[ECX+EAX*2+1] ; 取输入的第二个字符
004010D1 |. 3BD0 |CMP EDX,EAX ; 对比
004010D3 |. 74 04 |JE SHORT Server.004010D9
004010D5 |> 32C0 |XOR AL,AL
004010D7 |. EB 04 |JMP SHORT Server.004010DD
004010D9 |>^ EB 9E \JMP SHORT Server.00401079
004010DB |> B0 01 MOV AL,1
004010DD |> 5F POP EDI
004010DE |. 5E POP ESI
004010DF |. 5B POP EBX
004010E0 |. 83C4 50 ADD ESP,50
004010E3 |. 3BEC CMP EBP,ESP
004010E5 |. E8 D6090000 CALL Server.00401AC0
004010EA |. 8BE5 MOV ESP,EBP
004010EC |. 5D POP EBP
004010ED \. C3 RETN
这个算法不难,这里给出我写的一个注册机,供初学者参考。
代码:
#include<stdio.h>
#include<string.h>
#include<windows.h>
main()
{
char name[100];
printf("Please input a name :\t");
scanf("%s",name);
int key[100];
int i;
for(i=0;name[i];i++)
{
key[i]=name[i]^0x0FF;
key[i]=key[i]-i;
}
key[i]=0;
for(i=0;key[i];i++)
printf("%x",key[i]);
getchar();
getchar();
}
最后效果如图:
这三个题算比较简单的,供初学者学习吧,后面有时间我再把稍难的题目整理下,写个文档出来,当为这次比赛留个纪念。
附件: http://up.2cto.com/2012/1119/20121119113130873.rar