一个关于网络账户认证的小程序的分析,包含两种情况可能分析:一:只有客户端
EXE
而无服务器端的
EXE
。二:客户端和服务器都具有
EXE
文件可供分析。
首先看第一种情况:只有客户端
EXE
的分析步奏:
首先,打开客户端试看情况:
服务器端开启后,随便输入一个用户信息后,单击Register
,显示如下:
使用
OD
打开客户端
EXE
之后,有多种方法可以找到程序的功能主体——以
SOCKET
编程为主体的与服务器的交互部分和账户的验证部分。主要常用的可以使用两种方法较为简单。第一种为查找当前模块中的标签,然后对所有的
GetDlgItemTextA
函数下断点,因为既然客户端有账户的用户名和密码的输入输出,那么可以推测一下程序可能会用该函数以获取输入的文本信息,如果有该函数且确实实现了获取用户输入信息的获取功能则可在断下来之后自然找到发送服务器端验证的函数
Send()
函数以及接收函数
Recv()
函数,以及再接下来的响应分析判断登陆是否成功。第二种方法,直接在中文搜索引擎中搜索
ASCII
码并在反汇编窗口中跟踪显示成功的信息
ASCII
码,则亦自然能找到相应所需的功能函数。(若以上两种皆无法成功,可以尝试下消息断点,亦或者直接找
Socket
编程时所需的几个必要函数的调用位置)。以下分析使用第二种方法:
跟踪后:
00401380 |. /75 17 jnz Xcrackmen.00401399
00401382 |. |50 push eax ; /Style
00401383 |. |A1 FC864000 mov eax,dword ptr ds:[0x4086FC] ; |
00401388 |. |68 6C604000 push crackmen.0040606C ; |Crackne net-1
0040138D |. |68 50604000 push crackmen.00406050 ; |Registration successful !
00401392 |. |50 push eax ; |hOwner => NULL
00401393 |. |FF15 A4504000 call dword ptr ds:[<&USER32.MessageBoxA>>; \MessageBoxA
00401399 |> \5E pop esi
0040139A |. 83C4 58 add esp,0x58
0040139D \. C3 retn
往上看会发现
Socket(),connect(),send(),recv(),GetDlgItemText
()函数的调用,我将断点下在了调用第一个
GetDlgItemText()
函数之前的那一行代码的位置,运行程序,输入账号信息单击
Register
,程序断在断点处,往下单步走分析:
004012B1 |. 8D5424 30 lea edx,dword ptr ss:[esp+0x30]
004012B5 |. 6A 15 push 0x15 ; /Count = 15 (21.)
004012B7 |. 52 push edx ; |Buffer
004012B8 |. 68 E8030000 push 0x3E8 ; |ControlID = 3E8 (1000.)
004012BD |. 50 push eax ; |hWnd => NULL
004012BE |. FFD7 call edi ; \GetDlgItemTextA
004012C0 |. 8B15 00874000 mov edx,dword ptr ds:[0x408700]
004012C6 |. 8D4C24 48 lea ecx,dword ptr ss:[esp+0x48]
004012CA |. 6A 15 push 0x15 ; /Count = 15 (21.)
004012CC |. 51 push ecx ; |Buffer
004012CD |. 68 E9030000 push 0x3E9 ; |ControlID = 3E9 (1001.)
004012D2 |. 52 push edx ; |hWnd => NULL
004012D3 |. FFD7 call edi ; \GetDlgItemTextA
004012D5 |. 6A 00 push 0x0 ; /Flags = 0
004012D7 |. 8D4424 34 lea eax,dword ptr ss:[esp+0x34] ; |
004012DB |. 6A 14 push 0x14 ; |DataSize = 14 (20.)
004012DD |. 50 push eax ; |Data
004012DE |. 56 push esi ; |Socket
004012DF |. E8 CE000000 call ; \send
004012E4 |. 6A 00 push 0x0 ; /Flags = 0
004012E6 |. 8D4C24 4C lea ecx,dword ptr ss:[esp+0x4C] ; |
004012EA |. 6A 14 push 0x14 ; |DataSize = 14 (20.)
004012EC |. 51 push ecx ; |Data
004012ED |. 56 push esi ; |Socket
004012EE |. E8 BF000000 call ; \send
004012F3 |. 68 F4010000 push 0x1F4 ; /Timeout = 500. ms
004012F8 |. FF15 88504000 call dword ptr ds:[<&KERNEL32.Sleep>] ; \Sleep
004012FE |. 6A 00 push 0x0 ; /Flags = 0
00401300 |. 8D5424 1C lea edx,dword ptr ss:[esp+0x1C] ; |
00401304 |. 6A 14 push 0x14 ; |BufSize = 14 (20.)
00401306 |. 52 push edx ; |Buffer
00401307 |. 56 push esi ; |Socket
00401308 |. E8 9F000000 call ; \recv
值得令人注意的地方有获取的用户名和密码的存放地址和接收的服务器回传信息的内容和存放地址。再往下跟,即可发现以下内容:
00401323 |> /0FBE7404 2C /movsx esi,byte ptr ss:[esp+eax+0x2C]
00401328 |. |03D6 |add edx,esi
0040132A |. |40 |inc eax
0040132B |. |3BC1 |cmp eax,ecx
0040132D |.^\72 F4 \jb Xcrackmen.00401323
0040132F |> 33C0 xor eax,eax
00401331 |> 8A4C04 14 /mov cl,byte ptr ss:[esp+eax+0x14]
00401335 |. 32CA |xor cl,dl
00401337 |. 884C04 14 |mov byte ptr ss:[esp+eax+0x14],cl
0040133B |. 40 |inc eax
0040133C |. 83F8 06 |cmp eax,0x6
0040133F |.^ 72 F0 \jb Xcrackmen.00401331
大概跟过分析后可知,功能为将输入1+用户名第一位的ASCII码值+第二位+....+最后一位=》保存至EDX。然后将接收到的服务器回传的信息每次取一个字节与dl相异或,最终得到一个末位为00的字符串CHKFL。而后出现以下代码:
00401347 |. BE 7C604000 mov esi,crackmen.0040607C ; CHKPS
0040134C |. 8D4424 18 lea eax,dword ptr ss:[esp+0x18]
00401350 |> 8A10 /mov dl,byte ptr ds:[eax]
00401352 |. 8A1E |mov bl,byte ptr ds:[esi]
00401354 |. 8ACA |mov cl,dl
00401356 |. 3AD3 |cmp dl,bl
00401358 |. 75 1E |jnz Xcrackmen.00401378
0040135A |. 84C9 |test cl,cl
0040135C |. 74 16 |je Xcrackmen.00401374
0040135E |. 8A50 01 |mov dl,byte ptr ds:[eax+0x1]
00401361 |. 8A5E 01 |mov bl,byte ptr ds:[esi+0x1]
00401364 |. 8ACA |mov cl,dl
00401366 |. 3AD3 |cmp dl,bl
00401368 |. 75 0E |jnz Xcrackmen.00401378
0040136A |. 83C0 02 |add eax,0x2
0040136D |. 83C6 02 |add esi,0x2
00401370 |. 84C9 |test cl,cl
00401372 |.^ 75 DC \jnz Xcrackmen.00401350
00401374 |> 33C0 xor eax,eax
00401376 |. EB 05 jmp Xcrackmen.0040137D
00401378 |> 1BC0 sbb eax,eax
0040137A |. 83D8 FF sbb eax,-0x1
0040137D |> 85C0 test eax,eax
0040137F |. 5B pop ebx
00401380 |. 75 17 jnz Xcrackmen.00401399
00401382 |. 50 push eax ; /Style
00401383 |. A1 FC864000 mov eax,dword ptr ds:[0x4086FC] ; |
00401388 |. 68 6C604000 push crackmen.0040606C ; |Crackne net-1
0040138D |. 68 50604000 push crackmen.00406050 ; |Registration successful !
00401392 |. 50 push eax ; |hOwner => 004306FC ('Riijj - Crackme net-1 20060915',class='myWindowClass')
00401393 |. FF15 A4504000 call dword ptr ds:[<&USER32.MessageBoxA>>; \MessageBoxA
00401399 |> 5E pop esi
0040139A |. 83C4 58 add esp,0x58
0040139D \. C3 retn
可知,是将运算得来的字符串与一给点的字符串CHKPS相比较,若相等则输出成功。
根据以上在若无服务器的EXE文件具体分析时,可得知:以下两点即为重要,一:最终验证的信息与接收的服务器的发送信息有极大的关系。二:重新换一组用户信息后发现计算得到的仍是CHKFL字符串,因此可以推断出服务器端传过来的信息是根据每次输入用户名的而不同的,而确定的是若用户输入正确传过来的信息可以与用户名计算后得到CHKPS,否则永远为CHKFL。根据此,若是无服务器可以自己写一下服务器了。
那么在有服务器时,可进行双端调试,根据以上原理下断点后,可以看到以下代码:
00401133 8D7C24 2C lea edi,dword ptr ss:[esp+0x2C]
00401137 |. 83C9 FF or ecx,0xFFFFFFFF
0040113A |. 33C0 xor eax,eax
0040113C |. F2:AE repne scas byte ptr es:[edi]
0040113E |. F7D1 not ecx
00401140 |. 49 dec ecx
00401141 |. 74 0C je Xserver_c.0040114F
00401143 |> 0FBE5404 2C /movsx edx,byte ptr ss:[esp+eax+0x2C]
00401148 |. 03DA |add ebx,edx
0040114A |. 40 |inc eax
0040114B |. 3BC1 |cmp eax,ecx
0040114D |.^ 72 F4 \jb Xserver_c.00401143
0040114F |> 8D4424 40 lea eax,dword ptr ss:[esp+0x40]
00401153 |. 8D4C24 2C lea ecx,dword ptr ss:[esp+0x2C]
00401157 |. 50 push eax
00401158 |. 51 push ecx
00401159 |. E8 A2000000 call server_c.00401200
0040115E |. 83C4 08 add esp,0x8
00401161 |. 83F8 01 cmp eax,0x1
00401164 |. 75 3B jnz Xserver_c.004011A1
00401166 |. 66:A1 5870400>mov ax,word ptr ds:[0x407058]
0040116C |. 8B15 54704000 mov edx,dword ptr ds:[0x407054]
00401172 |. 66:894424 14 mov word ptr ss:[esp+0x14],ax
00401177 |. 895424 10 mov dword ptr ss:[esp+0x10],edx
0040117B |. 33C0 xor eax,eax
0040117D |> 8A4C04 10 /mov cl,byte ptr ss:[esp+eax+0x10]
00401181 |. 32CB |xor cl,bl
00401183 |. 884C04 10 |mov byte ptr ss:[esp+eax+0x10],cl
00401187 |. 40 |inc eax
00401188 |. 83F8 06 |cmp eax,0x6
0040118B |.^ 72 F0 \jb Xserver_c.0040117D
0040118D |> 6A 00 push 0x0 ; /Flags = 0
0040118F |. 8D4C24 14 lea ecx,dword ptr ss:[esp+0x14] ; |
00401193 |. 6A 06 push 0x6 ; |DataSize = 6
00401195 |. 51 push ecx ; |Data
00401196 |. 56 push esi ; |Socket
00401197 |. E8 04010000 call ; \send
在这些反汇编代码中发现了和客户端中一样的计算1+用户名第一位+第二位+.......+最后一位。并发现了我们最感兴趣的Send函数,那么其上的代码必为计算发送信息的代码,运行至401159处的CALL时,不跳过而跟进函数,可发现以下代码:
00401200 /$ 8B5424 04 mov edx,dword ptr ss:[esp+0x4]
00401204 |. 83EC 18 sub esp,0x18
00401207 |. 83C9 FF or ecx,0xFFFFFFFF
0040120A |. 33C0 xor eax,eax
0040120C |. 56 push esi
0040120D |. 57 push edi
0040120E |. 8BFA mov edi,edx
00401210 |. BE 01000000 mov esi,0x1
00401215 |. F2:AE repne scas byte ptr es:[edi]
00401217 |. F7D1 not ecx
00401219 |. 49 dec ecx
0040121A |. 74 14 je Xserver_c.00401230
0040121C |> 0FBE3C10 /movsx edi,byte ptr ds:[eax+edx]
00401220 |. A8 01 |test al,0x1
00401222 |. 75 05 |jnz Xserver_c.00401229
00401224 |. 0FAFF7 |imul esi,edi
00401227 |. EB 02 |jmp Xserver_c.0040122B
00401229 |> 03F7 |add esi,edi
0040122B |> 40 |inc eax
0040122C |. 3BC1 |cmp eax,ecx
0040122E |.^ 72 EC \jb Xserver_c.0040121C
00401230 |> 33C9 xor ecx,ecx
00401232 |> 69F6 19990100 /imul esi,esi,0x19919
00401238 |. 8BC6 |mov eax,esi
0040123A |. 33D2 |xor edx,edx
0040123C |. BF 1A000000 |mov edi,0x1A
00401241 |. F7F7 |div edi
00401243 |. 80C2 41 |add dl,0x41
00401246 |. 88540C 08 |mov byte ptr ss:[esp+ecx+0x8],dl
0040124A |. 41 |inc ecx
0040124B |. 83F9 14 |cmp ecx,0x14
0040124E |.^ 72 E2 \jb Xserver_c.00401232
00401250 |. 8B7C24 28 mov edi,dword ptr ss:[esp+0x28]
00401254 |. B9 05000000 mov ecx,0x5
00401259 |. 8D7424 08 lea esi,dword ptr ss:[esp+0x8]
0040125D |. 33C0 xor eax,eax
0040125F |. C64424 1C 00 mov byte ptr ss:[esp+0x1C],0x0
00401264 |. F3:A7 repe cmps dword ptr es:[edi],dword ptr d>
00401266 |. 5F pop edi
00401267 |. 5E pop esi
00401268 |. 75 16 jnz Xserver_c.00401280
0040126A |. 68 1C714000 push server_c.0040711C ; Key check correct\n
0040126F |. E8 74000000 call server_c.004012E8
00401274 |. 83C4 04 add esp,0x4
00401277 |. B8 01000000 mov eax,0x1
0040127C |. 83C4 18 add esp,0x18
0040127F |. C3 retn
跟踪运行分析后可知:根据用户名信息以及相关计算得到了一长度为20的字符串,将其与输入密码比较一个双字,若匹配则输出认证成功,并将EAX置为1,否则输出认证失败,并将EAX置为0。跳出函数后,可以看到以下内容:
0040115E |. 83C4 08 add esp,0x8
00401161 |. 83F8 01 cmp eax,0x1
00401164 |. 75 3B jnz Xserver_c.004011A1
00401166 |. 66:A1 5870400>mov ax,word ptr ds:[0x407058]
0040116C |. 8B15 54704000 mov edx,dword ptr ds:[0x407054]
00401172 |. 66:894424 14 mov word ptr ss:[esp+0x14],ax
00401177 |. 895424 10 mov dword ptr ss:[esp+0x10],edx
0040117B |. 33C0 xor eax,eax
0040117D |> 8A4C04 10 /mov cl,byte ptr ss:[esp+eax+0x10]
00401181 |. 32CB |xor cl,bl
00401183 |. 884C04 10 |mov byte ptr ss:[esp+eax+0x10],cl
00401187 |. 40 |inc eax
00401188 |. 83F8 06 |cmp eax,0x6
0040118B |.^ 72 F0 \jb Xserver_c.0040117D
0040118D |> 6A 00 push 0x0 ; /Flags = 0
0040118F |. 8D4C24 14 lea ecx,dword ptr ss:[esp+0x14] ; |
00401193 |. 6A 06 push 0x6 ; |DataSize = 6
00401195 |. 51 push ecx ; |Data
00401196 |. 56 push esi ; |Socket
00401197 |. E8 04010000 call ; \send
0040119C |.^ E9 1AFFFFFF jmp server_c.004010BB
004011A1 |> 66:A1 5070400>mov ax,word ptr ds:[0x407050]
004011A7 |. 8B15 4C704000 mov edx,dword ptr ds:[0x40704C]
004011AD |. 66:894424 14 mov word ptr ss:[esp+0x14],ax
004011B2 |. 895424 10 mov dword ptr ss:[esp+0x10],edx
004011B6 |. 33C0 xor eax,eax
004011B8 |> 8A4C04 10 /mov cl,byte ptr ss:[esp+eax+0x10]
004011BC |. 32CB |xor cl,bl
004011BE |. 884C04 10 |mov byte ptr ss:[esp+eax+0x10],cl
004011C2 |. 40 |inc eax
004011C3 |. 83F8 06 |cmp eax,0x6
004011C6 |.^ 72 F0 \jb Xserver_c.004011B8
004011C8 |.^ EB C3 jmp Xserver_c.0040118D
这就是发送信息的计算代码,其中若前面匹配成功则会根据CHKPS去计算发送信息,否则则根据CHKFL去计算。这也就是为何无论输入是什么,只要错误就一定会在客户端中计算得到CHKFL,而唯一正确的方能算出CHKPS。
附:服务器代码: using System; using System.Net; using System.Net.Sockets; using System.Text; namespace SocketServer { class Class1 { //定义端口号 private const int porNum = 8877; [STAThread] static void Main(string[] args) { bool done = false; TcpListener listener = null; //定义监听主机IP地址 IPAddress localAddr = IPAddress.Parse("127.0.0.1"); listener = new TcpListener(localAddr,porNum); listener.Start(); while (!done) { Console.Write("******虚拟riijj服?器******\n正在侦听8877端口..."); TcpClient client = listener.AcceptTcpClient(); Console.WriteLine("\n接收用户名及密码..."); NetworkStream ns = client.GetStream(); byte[] bytes = new byte[256]; int bytesRead = ns.Read(bytes, 0, bytes.Length); Console.WriteLine("用户名:"); //获取输入用户名 int i=0; String data1 = null; while (bytes[i] != 0) { data1 = null; data1 = System.Text.Encoding.ASCII.GetString(bytes, 0, i+1); i++; } //处理用户名,ASCII码之Sum值+1,然后取低位跟CHKPS做异或运算 //char[] w = new char[20]; char[] b = new char[20]; data1.CopyTo(0,b,0,data1.Length); Int32 data3 = 0; for (int y = 0; y < data1.Length; y++) { data3 += (int)b[y]; } data3 = data3 + 1; string m; m=Convert.ToString(data3, 16).Substring((Convert.ToString(data3, 16).Length - 2), 2); Console.WriteLine(data1); char[] a = new char[5] {'C','H','K','P','S'}; byte[] m1 = new byte[5]; for(int x=0;x<5;x++) { m1[x] = (byte)(int.Parse(m, System.Globalization.NumberStyles.HexNumber) ^ (int)a[x]); Console.WriteLine("打印出与CHKPS分别异或后的值..."); Console.WriteLine(m1[x]); } Console.WriteLine("打印出(SUM+1)的值(AL)..."); Console.WriteLine(m); Console.WriteLine("打印出(SUM+1)的值..."); Console.WriteLine(Convert.ToString(data3,16)); Console.WriteLine("打印出加密字符串(超出ASCII码范围的为问号)..."); Console.WriteLine(System.Text.Encoding.ASCII.GetChars(m1)); Console.WriteLine("密码:"); //获取输入密码 int j = 20; String data2 = null; while (bytes[j] != 0) { data2 = null; data2 = System.Text.Encoding.ASCII.GetString(bytes, 20, i); j++; } Console.WriteLine(data2); byte[] sendData = m1; byte[] sendData1 = new byte[1]; sendData1[0]=(byte)int.Parse(m, System.Globalization.NumberStyles.HexNumber); //向buffer写入异或的值+(SUM值+1) try { ns.Write(sendData,0,sendData.Length); ns.Write(sendData1, 0, 1); ns.Close(); client.Close(); } catch (Exception ex) { Console.WriteLine(ex.ToString()); } } listener.Stop(); } } } |