好了吃饱了,继续我们的破解之路。
前面已经追到了关键函数,函数中有下面一段代码
004166B7 |. 51 PUSH ECX
004166B8 |. 68 0C914500 PUSH upplican.0045910C ; 这里应该指向一个结构体
004166BD |. 6A 06 PUSH 6
004166BF |. 6A 07 PUSH 7
004166C1 |. 52 PUSH EDX
004166C2 |. C705 C43A5200>MOV DWORD PTR DS:[523AC4],1000000
004166CC |. 894424 1C MOV DWORD PTR SS:[ESP+1C],EAX
004166D0 |. C74424 20 120>MOV DWORD PTR SS:[ESP+20],12
004166D8 |. E8 C3400000 CALL upplican.0041A7A0 ; 获取mac地址
其中第二个push有点意思,直接是一个地址,转到数据窗口
0045910C 00 0B 2F 20 64 91 35 39 2E 36 38 2E 31 2E 32 30 ./ d?9.68.1.20
什么?居然是自己的mac地址,IP地址,网关地址,DNS服务器地址!(后面的数据我没有列出来)
那就看这个函数在做什么吧?
这个函数比较简单,只是几个movs而已,那就需要注意究竟是把什么数据复制到了什么地方。而且
后面对用户名,密码,IP地址等数据的处理也都是这个函数,那么很可能这个函数是用来对某个相同的数据进行赋值
(当然这是我跟踪多遍才发现的),那么是向什么地方赋值呢?直接看edi就可以了,对!直接EDI!
因为在复制的时候编译器编译之后绝大部分都是esi和edi的使用。经过跟踪发现,很大部分的验证信息都被放到了
11df22e地址左右的内存中。
0041680D |. 51 PUSH ECX
0041680E |. 6A 01 PUSH 1
00416810 |. 52 PUSH EDX
00416811 |. E8 0A0F0000 CALL 00417720 ; 这一部分填充的是什么呢?
00416816 |. 8B4424 40 MOV EAX,DWORD PTR SS:[ESP+40]
0041681A |. 8D8C24 C40000>LEA ECX,DWORD PTR SS:[ESP+C4]
00416821 |. 50 PUSH EAX ; 数据长度
00416822 |. 51 PUSH ECX ; 数据指针
00416823 |. E8 180E0000 CALL upplican.00417640 ; 对数据进行加密传送
00416828 |. 8B5424 48 MOV EDX,DWORD PTR SS:[ESP+48]
0041682C |. 8B0D C43C5200 MOV ECX,DWORD PTR DS:[523CC4]
00416832 |. 68 080F0000 PUSH 0F08
00416837 |. 68 7C924500 PUSH upplican.0045927C ; ASCII "192.168.4.2"
0041683C |. 8D8424 D40000>LEA EAX,DWORD PTR SS:[ESP+D4]
00416843 |. 52 PUSH EDX
00416844 |. 50 PUSH EAX
00416845 |. 51 PUSH ECX
00416846 |. E8 C5260000 CALL upplican.00418F10 ; 调用sendto发送数据
跟到后面发现了这两个函数,其中CALL 00417720这个函数也对12df214这个内存进行了填充,但目前还不知道究竟填充了什么。
而CALL 00417640 这个函数对12df214内存中的数据进行了变形,紧跟其后的函数就调用sendto进行发送数据,那么这个函数肯定是
对数据进行加密!!!那就跟进去看看吧
00417663 |> /8A043E /MOV AL,BYTE PTR DS:[ESI+EDI]
00417666 |. |8AC8 |MOV CL,AL
00417668 |. |8AD0 |MOV DL,AL
0041766A |. |C0E9 02 |SHR CL,2
0041766D |. |80E1 20 |AND CL,20
00417670 |. |80E2 40 |AND DL,40
00417673 |. |0ACA |OR CL,DL
00417675 |. |8AD0 |MOV DL,AL
00417677 |. |C0E9 02 |SHR CL,2
0041767A |. |80E2 20 |AND DL,20
0041767D |. |0ACA |OR CL,DL
0041767F |. |8AD0 |MOV DL,AL
00417681 |. |D0E9 |SHR CL,1
00417683 |. |80E2 02 |AND DL,2
00417686 |. |0ACA |OR CL,DL
00417688 |. |8AD0 |MOV DL,AL
0041768A |. |80E2 1C |AND DL,1C
0041768D |. |C0E0 05 |SHL AL,5
00417690 |. |0AD0 |OR DL,AL
00417692 |. |D0E9 |SHR CL,1
00417694 |. |C0E2 02 |SHL DL,2
00417697 |. |0ACA |OR CL,DL
00417699 |. |880C3E |MOV BYTE PTR DS:[ESI+EDI],CL
0041769C |. |46 |INC ESI
0041769D |. |3BF5 |CMP ESI,EBP
0041769F |.^/7C C2 /JL SHORT upplican.00417663
跟进去之后有上面的一段关键代码,它就是对数据进行加密的,先不管他是怎么变换的,不管他是用了某种算法,还是自己写的算法,都不重要了关键是这段代码是对数据进行加密的。那么我们模拟客户端发送数据的话,也一定要实现这个函数,还好这个函数不是太长,很容易就可以写出与之对应的C代码。
//发送之前对数据进行加密
void CCodeHelper::Encrpy(byte code[],int len)
{
for (int i=0;i<len;i++)
{
byte al=code[i];
byte cl=al,dl=al;
cl=cl>>2;
cl=cl&0x20;
dl=dl&0x40;
cl=cl|dl;
dl=al;
cl=cl>>2;
dl=dl&0x20;
cl=cl|dl;
dl=al;
cl=cl>>1;
dl=dl&2;
cl=cl|dl;
dl=al;
dl=dl&0x1c;
al=al<<5;
dl=dl|al;
cl=cl>>1;
dl=dl<<2;
cl=cl|dl;
code[i]=cl;
}
return;
}
那就验证一下我们的想法吧,构造一个和12df214内存数据相同的一个字节数组然后用我们的函数对其进行加密,比对结果,一致!OK
这个函数就搞定了。但是还有一个问题没有解决,就是前面有一个函数填充了另外一些数据,究竟是什么还没有弄明白。
那就跟进CALL 00417720函数一探究竟吧
0040DEFC |. 51 PUSH ECX ; /Arg1
0040DEFD |. 8BCB MOV ECX,EBX ; |
0040DEFF |. E8 7CF4FFFF CALL upplican.0040D380 ; /两次计算生成最终数据
经过层层跟进来到了这个函数处,这是个关键点,为什么说他是关键点呢,因为这是一个MD5函数的实现,为什么这样那个说呢?
0040D3DC |. 8D8C01 78A46A>LEA ECX,DWORD PTR DS:[ECX+EAX+D76AA478]
0040D3E3 |. 8BC1 MOV EAX,ECX
............................
0040D3FD |. 03CD ADD ECX,EBP
0040D3FF |. 8D940A 56B7C7>LEA EDX,DWORD PTR DS:[EDX+ECX+E8C7B756]
0040D406 |. 8BCA MOV ECX,EDX
上面是函数体的一部分代码,怎么样看出端倪了没有?这是md5经常用到的一些常数啊!
好了,CALL 00417720函数的功能就搞明白了。它是用了对某些数据求MD5值,随数据一起发送。其实这也很容易理解,在网络通信中,
为了防止数据被随意改动过都需要对数据进行求MD5。那么他是对谁进行求MD5的呢?
跟踪发现求MD5的函数有一个指针,是12df214!也就是前面的内存块儿。很可能是对所有需要发送的数据进行MD5然后填充。其实这是我跟踪多遍才发现的,在这个函数上我花了比较长的时间。
那就来验证一下吧。在网上找一份MD5的代码,对12df214内存的数据进行求MD5。12df214是起始地址,数据长度是多少呢?75!
为什么这样说呢?因为在前面抓包的过程中已经知道UDP的数据长度是75了。
再次比对成功。那就验证了我们的猜测。
好了,梳理一下思路,客户端发送验证数据的过程如下:
1、构造数据块清零。
2、用相关数据填充数据块,如下:
ABCDEF0000GHIJK
3、对上面的数据(包含其中的0)求MD5值,假设为XXXX填入上面的数据块,填充后如下
ABCDEFXXXXGHIJK
4、用某种算法进行加密。加密后如下:
EFJLEDXFYENXLWP
5、调用sendto发送。
下一步就是如何构造要发送的数据块了,这是一个难点。经过网上查询,知道安腾验证使用的是radius sever方式。
radius协议分为三部分:标志、长度、数据。
这样就很容易看出12df214内存中数据的结构了。但是我们用什么对应的C数据结构实现呢?本想用一个字节数组来实现的
,但是这样太“野蛮”,不利于扩展,根据经验构造数据结构如下:
typedef struct _NODE
{
byte flag;//标志
byte len;//长度
byte data[1];//数据
}NODE,*PNODE;
m_pUserPwd = (PNODE)malloc(sizeof(NODE)+strlen(m_szUserPwd)-1);
怎么样这种数据结构是不是很不错呢?呵呵当然啦,这可是跟着大牛们学的!以后还是多看点书哇。
好了万事具备就欠东风了,开始写我们的C++代码了。这部分并不太难,在此略过。
好了可以发送了,那怎么对接收的数据进行处理呢?我们知道发送的时候对数据进行了加密,那么接收的时候肯定也是要
解密的。那就继续用OD解决吧,有了前面的破解经验,破解解密算法并不困难。
好了到此为止我们就完全解决了发送数据,接收数据的全部问题。下一个问题就是对接收数据意义的解释,也就是什么样的数据表示
验证成功呢?这个比较简单,只要对比成功和不成功时候的数据就可以了。比对后发现有一个字段表示验证结果,是1则表示成功。
恩,所有的问题都解决了,那我们就写一个“暴力破解器”吧。
我们从第一学号(例如00000)开始循环一直到最后一个学号(9999),连续的向服务器发送数据,直到验证成功,找到学号abcde。
好了那就让我们用abcde登录吧。呵呵,是不是已经登录成了呢。
其实我们还可以对程序进行优化,那就是使用多线程,当然的注意,向服务器发送数据的频率不要太高,这样会引起服务器的不响应。
因为学校是严格限制上网的流量的。
好了,到此为止,对安腾校园网客户端的破解就到此为止了,怎么样是不是很有成就感呢。
这次破解用到了很多知识,比如MD5加密算法,比如UDP通信,还有很多其他无法用知识点表现的经验。
在此要非常感谢安腾客户端的漏洞,要不是这样一个漏洞,我哪里还能学到这么多的知识呢^_^
PS:有些地方说的比较含糊,这是故意的,考虑到我们处在天国的时代,说不定哪一天被和谐了,就麻烦了。