搬到南窑头以后,上网需要运行一个“元和网络”的拨号器,大是不爽;并且我的Ubuntu下面也没法上网了(使用pppoeconfig,输入人家提供的用户名和密码,总是提示错误)。这几天就抽空hack了一下。
需要介绍一下这个拨号过程。在网络连接的详细信息里面看到设备类型为PPPoE,身份验证方法是CHAP。这个对后面的解密过程没有帮助,但是对在Linux下面拨号的设置是有用的。
用Wireshark抓了一下连接过程中的TCP/IP包。如下图:
按照CHAP协议(RFC1994),认证的过程是这样的: (1) 认证端(可以认为是YHWL服务器)向被认证端(可以认为是YHWL客户端)发起一个挑战(上图中的第一行); (2) 客户端根据挑战的值以及本地使用的密码进行一次hash(CHAP协议使用MD5算法),然后将计算出的值(校验值)回应给服务器(用户名是明文传送的,上图中的第二行); (3) 服务器根据用户名去数据库中查询相应的密码,然后进行同样的运算,如果校验值匹配,那么认证成功,客户端和服务端就建立了连接。
然而我计算出的校验值总是和发送的不一致,于是认定这个“元和网络”的客户端肯定做了一些手脚。好,hack开始。
问题的难点在于这个客户端如何建立连接,如何计算校验值。这个客户端是一个.NET程序,用ILSpy反编译了一下,看到里面有一个Ras.RasManager,如下图:
初步确定应该是在这里面进行拨号连接的,继续展开,查看这个类的成员:
public string UserName;
public string Password;
public string EntryName;
public string PhoneNumber;
还有一个Connect()方法,不过这个里面什么也没有:
// Ras.RasManager
[MethodImpl(MethodImplOptions.NoInlining)]
public string Connect()
{
return null;
}
在网络上搜索一下就可以发现,这个已经很接近了,ILSpy可能因为有什么困难不能反编译这个函数的每一条语句,但我们猜测在Connect()方法里肯定调用了RasDial这个函数(http://msdn.microsoft.com/en-us/library/ms897090.aspx):
DWORDRasDial(
LPRASDIALEXTENSIONS dialExtensions,
LPTSTR phoneBookPath,
LPRASDIALPARAMS rasDialParam,
DWORD NotifierType,
LPVOID notifier,
LPHRASCONN pRasConn
);
这里跟我们相关的是这个rasDialParam参数,在Ras.RasManager类里面也有这样一个成员:
private RasManager.RASDIALPARAMS YnfcdQt6s;
继续看这个结构:
[StructLayout(LayoutKind.Sequential, CharSet= CharSet.Auto, Pack = 4)]
public struct RASDIALPARAMS {
public int dwSize;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst= 257)]
public string szEntryName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst= 129)]
public string szPhoneNumber;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst= 129)]
public string szCallbackNumber;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst= 257)]
public string szUserName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst= 257)]
public string szPassword;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst= 16)]
public string szDomain;
public int dwSubEntry;
public int dwCallbackId;
static RASDIALPARAMS()
{
gkmJgD0Q1OPjD7Vp9g.VdUL8hajk();
}
}
用户名、密码、名称、域等一应俱全。为了验证这个想法,我们在Ras.RasManager.Connect()上打一个断点。(果然可以在这里断下,跳出后网络就连通了,这个过程不赘述。)
但在这里看到一个有意思的事情:用户名还是原来的用户名,但是密码已经和原来不同了。把这个密码拿出来,使用这个密码直接拨号(命令行使用rasdial,用法可以/?查看),连网成功!这个是不小的发现。在我的Ubuntu下面也可以上网了,哈哈。
经过一段时间的分析,锁定了SupperRadiusClient.FMain.Ovpt85v4r()这个函数。这个函数就是将输入的密码转换成最终在用MD5计算CHAP回应时使用的密码。这次范围比较小了,专门hack这个函数就可以了。最终对这几行汇编代码产生了兴趣:
00000282 call FAD475BC
00000287 mov esi,eax
00000301 mov eax,dword ptr [ebp-24h]
00000304 mov ecx,dword ptr [eax+000001F4h]
0000030a mov edx,dword ptr [ebp-14h]
0000030d sar edx,2
00000310 call dword ptr ds:[00317638h]
00000316 mov word ptr [esi+4],ax
这里需要逐行解释一下这几行汇编代码。
00000282 call FAD475BC
00000287 mov esi,eax
构造一个空的String,然后将构造的对象的地址放到ESI寄存器中。
00000301 mov eax,dword ptr [ebp-24h]
00000304 mov ecx,dword ptr [eax+000001F4h]
将一个String的地址放到了ECX寄存器,我们监视一下这个字符串,内容是: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ 这个很重要。
0000030a mov edx,dword ptr [ebp-14h]
把当前要加密的字符取了出来,放到EDX寄存器中。
0000030d sar edx,2
除以4.
00000310 call dword ptr ds:[00317638h]
用EDX寄存器中的值作为索引,取ECX寄存器中那个String的一个字符。例如,EDX的值为0,那么这个函数执行之后,就把字母A放到在AX寄存器中。(String是使用Unicode表示的,一个字符占2个字节,所以是AX寄存器,不是EAX,也不是AL)。
00000316 mov word ptr [esi+4],ax
把AX寄存器的值放到ESI+4的地址。ESI是刚才构造的一个新的String对象。这样一个字符就加密好了。剩下的过程类似,在这里我就不分析了。
总结一下加密算法:
1. 密码分成3个一组,例如原始的密码是abcdefg,那么分组后就是 abc def g,对每组中的字符分别加密,每组之间的过程是独立的;
2. 一组的3位密码再次进行加密后,变成4位,每位是这样计算的:
(1): [0]>> 2
(2): (([0]&0x03) << 4) + (([1]&0xf0) >> 4)
(3): (([1]&0x0f) << 2) + (([2]&0xc0) >> 6)
(4): [2]&0x3f
计算出的索引,然后再找那个长字符串(ABCD...789+/)对应的字符。
如果不足3位,那么是这样的:1位的密码计算(1)(2),然后补两个“=”;2位的密码计算(1)(2)(3),然后补一个“=”。如“1”加密后就是“MQ==”,“ab”加密后就是“YWI=”。
2012年12月11日补充:原来这个加密算法就是所谓的Base64.