古黑币17 个
成长值14930 点
金币305 个
精华贴12 个
可遇不可求的事:故乡的云,上古的玉,随手的诗,十九岁的你。
“上课了,上课了!”同学们一边兴高采烈的讨论着一边步入教室。新的一周又开始了。
“嗯,大家经过这两次课的学习,有什么感觉啊?”老师等同学们坐好后问。
“噢,这几次课我们对溢出编写的基本思路有了清晰的了解;掌握了利用缓冲区溢出的两种方式;同时还对大量的实际漏洞进行了成功的编写。”古风认真的说道。
“嗯,让我们的兴趣和技术都得到了很大的提升。”玉波说。
“同时,我们也认识到了真正的黑客精神是钻研和共享!”宇强佩服的说道。“好!”老师赞扬道,“大家有了这些认识就很好!只要有了兴趣和钻研的精神,就可自主的不断深入下去;同时,有了共享的精神,就会得到别人的尊敬,并且一同讨论、一起进步。”
“嗯!”大家认真的点点头。
“但是,老师。”宇强说道:“虽然我们的溢出水平的确有所提高。但在实际编程中,对ShellCode的编写不是特别明白,对复杂漏洞(比如堆溢出漏洞)也还不清楚,希望能继续的学习。”
老师笑道:“呵呵,别着急,在后面的章节中我们会慢慢深入下去的。大家有这个不断学习的想法就很好!再提醒一次,学习更重要的是方法,而不是技术本身。如果你掌握的只是技术,技术会很快过时,那你就没有机会了;如果掌握的是方法,那你就能很轻松的应对技术的变迁。”
大家齐声答道:“嗯,我们一定会注意的!”
老师说道:“那好,首先我们一起来看看ShellCode的编写吧!”
ShellCode是什么
老师说:“缓冲区溢出漏洞的利用。本质上来讲,就是使计算机跳转到我们的ShellCode中去执行,所以,ShellCode是缓冲区溢出利用的关键之一。但ShellCode本身的编写是大有学问的,可易可难,所以这里单独提出来讲。”
大家小心翼翼的问道:“前面多次提到了ShellCode,这里又说它很有学问。但ShellCode究竟是什么呢?我们都还不知道呢……”
“哦,对哈!”老师一拍脑袋:“之前我还没解释过呢!不要紧,现在我们就来一起学习。”“最先的Shell指的是人机交互界面,而这里的ShellCode不仅仅指交互了,而是指可以实现我们想要的功能的代码。ShellCode是一组能完成我们想要的功能的机器代码,通常以十六进制数组的形式存在。比如,第一章中开DOS窗口的ShellCode,这也是通常ShellCode的出现方式。”
“大家看,ShellCode数组里存的是十六进制形式的机器码,其实,本质是对应着可直接执行的汇编程序。”老师说道。
“哇!是程序啊?但一点都看不出来,究竟是干什么的程序呢!”有些同学问道。
“呵呵!功能的分析会在后面会讲到。”老师说。
“ShellCode是程序,那岂不是需要别人点击和编译吗?”玉波晃晃脑袋,不解的说。
“这里的ShellCode其实是计算机能直接执行的机器代码,只要计算机的指令指针EIP指向ShellCode里面,就可以顺利执行,不需要再点击和编译了。”
小知识:计算机指令的执行
计算机每次都只是执行当前EIP指向的指令(单CPU)。在当前指令执行后,EIP会自动加1,从而指向下一条指令。如果有JMP CALL RET—类的指令,EIP就会被强行改变成指定的地址,从而完成流程的跳转。
“再解释清楚一点,我们想尽办法写出ShellCode,是想用ShellCode的数据把内存中原有的数据等动态覆盖掉,之后计算机的指令指针EIP指向ShellCode在内存中的开始位置,就可以执行ShellCode,实现我们想要的功能了。”老师继续说道。
“比如,原来在内存中的数据是这样的,如下图。”
原内存中的数据
“但我们把数据动态覆盖成ShellCode后,就成下图的样子了。”
覆盖后的数据
“这些数据就是那个开DOS窗口的ShellCode代码。当计算机执行到原来的地方时,实际上是在执行我们的ShellCode。这样,计算机在不知不觉的情况下就完成了我们想要的功能,不需要别人再去做什么。“
哦!”同学们恍然大悟。“那有点像在Debug时动态改变寄存器的值一样。”宇强说道。
“对,就是这样!计算机会按照改变后的值继续执行下去,而不管是谁改变了值,是否改变得正确。”老师还赞扬了一句:“看来这位同学的悟性不错嘛!”
“嘿嘿!”宇强发现PLMM看了自己一眼,高兴极了!
“大家继续努力吧!知道了ShellCode的用处,那我们就来看看如何编写出机器码形式的ShellCode。”
简单的例子——编写控制台窗口的ShellCode
“学习任何东西,都是由易到难,从简单到复杂,”老师说道。“这是一种学习的方法。”
“嗯!”台下认真的听着。
“我们这里也一样,”老师环顾了一下教室,说道,“我们从一个简单的程序进入编写ShellCode的殿堂吧!”
打开控制台窗口的C程序
“ShellCode是完成我们想要的功能的代码,那我们想要的功能最好是能开一个DOS窗口,那样就可做很多事情。比如之前在第一章中例举的这个程序。”
[mw_shl_code=c,true]#include<windows.h>
int main()
{
LoadLibrary(“msvcrt.dll”);
system(“command.com”);
return 0;
}[/mw_shl_code]
“在这个程序中,首先使用LoadLibrary(“msvcrt.dll”);装载动态链接库msvcrt.dll,然后用system函数执行command.com,就可获得一个DOS窗口,我们能在窗口中执行dir、copy等命令,效果大家也见过了。”
“喔!看起来很简单啊!”大家说道。
“嗯,是啊丨但在那个程序中,编译器会帮我们完成很多工作,比如函数地址的转换等。下面这个程序功能完全一样,但更能突出本质!大家看!”
[mw_shl_code=applescript,true]#include <windows.h>
#include <winbase.h>
typedef void (*MYPROC)(LPTSTR); //定义函数指针
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = LoadLibrary(“msvcrt.dll”);
ProcAdd = (MYPROC) GetProcAddress(LibHandle, "system"); //查找system函数地址
(ProcAdd) ("command.com"); //其实就是执行system(“command.com”)
return 0;
}[/mw_shl_code]
“哎哟!好复杂啊!头晕……”好多人开始晕‘程’了。
“呵呵,不要怕,这个程序和原来的那个差不多,但多了一些处理。我们一句一句的解释吧!
typedef void (*MYPROC) (LPTSTR)
定义了一个函数指针,其指向函数的参数是字符串,返回值是空。该指针的作用是用于指向system函数,在后面调用它,就相当于调用system函数。
LibHandle = LoadLibrary("msvcrt.dll")
加载msvcrt.dll这个动态链接库,动态链接库的句柄赋给LibHandle。
ProcAdd = (MYPROC) GetProcAddress (LibHandle,"system");
获得动态链接库的句柄后,我们再使用“GetProcAddress(LibHandle,system)”获得system的真实地址。之后再使用这个真实地址来调用system函数。执行该语句后,ProcAdd为指向system函数的指针,即ProcAdd存的是system函数的地址。”
(ProcAdd) ("command.com");
因为此时的ProcAdd为指向system函数的指针,所以“(ProcAdd)("command.com")”就是调用“system("command.com")”,完成我们想要的功能。”
“我们编译执行,仍然会弹出一个DOS窗口来,看下图。
“哦!好像比原来的程序就多了一个找system函数的地址。”宇强仔细观察后说道。
“是啊,干嘛这么麻烦呢!原来的程序不是挺好的吗?”胖胖的玉波又喃喃说道。
老师说:“我们千方百计的找出函数地址来,一定是有用处的。刚才说了这么多,大家也一定想知道system的地址到底是多少吧!”
“嗯,是啊,还真不知道呢!”
查看函数的地址
“好,我们一起来看看system函数的地址吧!在VC下按F10进入调试状态,然后在Debug工具栏中,点最后一个按钮‘Disassemble’和第四个按钮‘Registers’,这样就出现了源程序的汇编代码和寄存器状态窗口,如下图。”
“我们继续按F10,程序就会单步执行,直到‘LibHandle=LoadLibrary("msvcrt.dll")’那句下的‘call dword ptr [[email protected] (0042413c)]’执行完后,我们就可在寄存器窗口中发现EAX变为了780000,说明在我Win2000SP3的机器上,msvcrt.dll的地址为0x780000。如下图。”老师说道。
“等等,为什么这里的EAX的780000就为msvcrt.dll的地址呢?”古风说道。
“呵呵,我当然没那么天才,能自己想出来。”老师说,“那句‘call dword ptr [[email protected](0042413c)]’就是执行‘LoadLibrary("msvcrt.dll")’,返回值就是msvcrt.dll的地址;而函数的返回值,通常都是放在EAX中,这算是计算机系统的约定吧!所以,‘LoadLibrary("msvcrt.dll")’的返回值(msvcrt.dll的地址)就存在EAX中,即780000。”
“哦,这样啊!那system的地址是多少呢?”
“一样的道理,我们继续按F10执行下去,直到‘ProcAdd = (MYPROC) GetProcAddress (LibHandle,"system")’语句下的‘call dword ptr[[email protected] (00424194)]’指令执行后,可以发现得到EAX为7801AFC3,即在我Win2000 SP3系统的机器上,system()函数的地址是0x7801AFC3,如x下图。
“哦,真的是啊!”
“不是‘蒸’的,难道还是煮的吗?{:4_107:}接下来,执行‘(ProcAdd)("command.com")’,就弹出了一个DOS对话框,整个程序就结束了。”老师说道。
“哦,明白了。但还是不太清楚为什么需要知道system函数的地址?”古风问道。
“嗯!这涉及到Windows下函数调用的方式。”
Windows下的函数调用原理
老师说:“在Windows系统中,函数调用方式和Linux系统下函数的调用方式是不同的。”
小知识:
在Linux下,函数的执行是使用系统中断调用。执行一个函数,是把参数赋给寄存器,然后调用中断int 80来执行。比如执行execve (name[0],name,NULL),系统把0xb拷贝到寄存器EAX中(0xb是系统调用execve的代码号);将name[0]的地址拷贝到寄存器EBX中;将name的地址拷贝到寄存器ECX中;将NULL拷贝到寄存器EDX中;然后执行中断指令int $0x80,就完成了execve函数的执行。Linux执行其他函数也类似,系统把函数的系统调用码给EAX(如execve是0xb),函数带的参数给其他寄存器,最后执行int $0x80中断指令,就完成函数的执行。
“在Windows下,函数的调用需要先把函数所在的动态链接库Load进去,这点大家都清楚。而在执行的时候用堆栈传递参数,然后直接CALL该函数的地址就完成了,而不是像Linux使用系统中断。”
老师解释道:“比如,在Windows下执行函数Func (argv1,argv2,argv3),先把参数从右至左压入堆栈,这里就是依次把argv3、argv2、argv1压入堆栈里,然后Call Func函数的地址,这里的Call Func函数地址,其实等于两步,一是保存当前EIP,二是跳到Func函数的地址执行,即Push EIP+Jmp Func。其过程如下图。”
“大家这点明白了吗?”老师画完图后问道。
“Yes!但看看实际的例子更容易理解。”大家说。
“当然可以啦!我们来验证一下,还是调试刚才那个程序。执行ProcAdd=(MYPROC) GetProcAddress(LibHandle,"system");这句时,其过程应该是先压入system字符串地址,再压入LibHandle,最后call GetProcAddress函数的地址。大家看,在汇编代码中,过程是一样的:”
[mw_shl_code=c,true]push offset string “system” 第二个参数system字符串的地址入栈
push [ebp-4] 第一个参数LibHandle入栈
call [__imp__GetProcAddress] Call GetProcAddress函数的地址[/mw_shl_code]
“哦!真的和分析一模一样啊!”玉波撇撇嘴说道。
“呵呵,是的。这次由你们自己来分析执行(ProcAdd) ("command.com");的过程吧!”老师说。
“好哩!(ProcAdd) (〃command.com〃)首先是参数入栈,这里只有一个参数,所以就把command.com的地址压入堆栈;然后call ProcAdd函数的地址,这里ProcAdd函数的地址保存在[ebp-8]中,所以call [ebp-8]就OK了。”大家说道。
“非常好!”老师很满意,“真是名师出高徒啊!”
“这下清楚我们为什么要知道system函数的地址了吧?”
“莫非在执行函数时直接CALL我们知道的函数地址?”宇强想了想,问道。
“对!我们知道了函数的地址,也知道了函数执行的原理,那我们就可自己写出调用函数过程的汇编代码,甚至是机器代码一ShellCode!”老师说道。
汇编和机器码——真正ShellCode的生成
“现在知道了我机器上system函数的地址是0x7801AFC3,也知道了函数的执行原理。那我们就来直接写出system(“command.exe”)的汇编代码!”
“哇!”台下大惊,特别是几个女生,“我们还不懂汇编呢!”
“没事。”老师蛮有信心的说,“你们只要知道PUSH和CALL就行了,你们知道PUSH和CALL吗?知道,所以就行了。”
“……”台下没人敢说话。
“不要怕,学习的关键还是思路,只要把握了整个流程,那些细节东西自然就明白了。这也是种学习的方法,有时太拘泥于细节,不能把握好全局,反而不好。‘亮独览大略’,这就是诸葛亮比其他人强的原因。”
“哦!”
“怎么样?大家一起来考虑,一起来编写?”老师问了问大家。
“好吧!”听到诸葛亮也这样,大家都很有信心了。
“我们先把system(“command.exe”)的汇编代码写出来。”
“根据前面的分析,执行system(“command.exe”)只需先把参数command.exe字符串的地址入栈,再CALL System的地址就行了。”
“但command.exe字符串地址是多少呢?我们把什么压入堆栈呢?”古风问。
“是啊,这是个问题啊!”其他人也附和着。
“问得好!我们不知道command.exe字符串的地址,甚至连内存里有没有command.exe字符串都不知道,但我们可自己构造出来。”老师说。
“啊?自己构造?在哪里构造?怎样构造啊?”大家不解的问。
“呵呵,就在堆栈里构造!我们把‘command.exe’一个字符一个字符的压入堆栈,这样‘command.exe’字符串就有了,而且ESP正好是command.exe字符串的地址,看图。”
“有了字符串和字符串的地址ESP,我们把ESP压入堆栈,就是system(“command.exe”)函数的参数一——command.exe字符串的地址,如下图。”
“哦!”大家恍然大悟。
“最后我们CALL System函数的地址就行了。我的Win2000SP3机器上的system函数地址为0x7801AFC3,所以CALL 0x7801AFC3。”
“哇!按照这样的思路,我们可以得到system(“command.exe”)的汇编代码了!”古风高兴的说,埋头就想写汇编了。
“慢着,”老师阻止了古风,“PUSH是四字节对齐的,就是说PUSH—定会压入四个字节,比如PUSH 0x41,其实是压0x00000041进堆栈。刚才讲的一个字节一个字节的压入,其实是不行的。”
“啊?计算机岂不是耍我们?”
“但我们完全可以利用这个思路,只不过在实现细节上要改动一下。就像你们男生都要让着女生一样,我们也让着点计算机MM哈!”
“哈哈哈哈……”大家都笑了起来。小强暗叹道:“我还没有GF呢!”
“怎么改动呢?”
“方法很多,比如,计算机入栈是压四个字节,那我们就每次PUSH四个字节;或者我们就一个字节一个字节地把值赋入堆栈,不用PUSH,而直接用赋值。直接赋值的方法就像这样,大家看:”
[mw_shl_code=c,true]
mov esp,ebp ;
push ebp ;
mov ebp,esp ; 把当前esp赋给ebp
xor edi,edi ;
push edi ;压入0,esp-4,; 作用是构造字符串的结尾\0字符。
sub esp,08h ;加上上面,一共有12个字节,;用来放"command.com"。
mov byte ptr [ebp-0ch],63h ; c
mov byte ptr [ebp-0bh],6fh ; o
mov byte ptr [ebp-0ah],6dh ; m
mov byte ptr [ebp-09h],6Dh ; m
mov byte ptr [ebp-08h],61h ; a
mov byte ptr [ebp-07h],6eh ; n
mov byte ptr [ebp-06h],64h ; d
mov byte ptr [ebp-05h],2Eh ; .
mov byte ptr [ebp-04h],63h ; c
mov byte ptr [ebp-03h],6fh ; o
mov byte ptr [ebp-02h],6dh ; m一个一个生成串"command.com".
lea eax,[ebp-0ch] ;
push eax ; command.com串地址作为参数入栈
mov eax, 0x7801AFC3 ;
call eax ;[/mw_shl_code]
“给大家解释一下,‘mo vebp,esp’是把esp赋给ebp作为栈底;而‘push edi’和‘sub esp,08h’是把esp减去12字节,这12个字节空间就用来放‘command.com’;然后‘mov byte ptr [ebp-0ch],63h’那些指令,是我们把command.com—个字节一个字节的放进留出的空间中;这下有command.com字符了吧?我们用‘lea eax,[ebp-0ch]’来获得构造的command.com字符串的地址;最后,‘push eax’把地址压入堆栈,call system函数的地址就完成了。“
“下面来验证一下,在VC中可以用__asm关键字插入汇编,我们把system(“command.exe”)用写的汇编替换,LoadLibrary先不动,然后执行,成功!弹出了我们想要的DOS窗口。如下图。”
“哦!果然是一样!”同学们满意的说道。
“嗯,我们把LoadLibrary(“msvcrt.dll”)也仿照上面改成汇编,要注意的是,LoadLibrary在Win2000SP3上的地址为0x77e69f64,把两段汇编合起来得到cmdASM.cpp。我们把cmdASM.cpp编译、链接、执行。也成功了!如下图。”
“哇!想不到汇编也能这么轻松搞定啊!”大家都有点不相信自己的眼睛。
“呵呵,关键在于理解方法,有了方法,学习困难的东西就会变成一件愉快的事。最后我们来生成机器码 ShellCode。”
“也不难吗?”还是些同学有点担心。
“嗯,有了刚才的工作,难的只是动笔的体力活了。我们对刚才的全汇编程序在VC中按F10进入调试状态,接着按下Debug工具栏的‘Disassembly’按钮,然后点鼠标右键,在弹出菜单中选中‘CodeBytes’,就会出现汇编对应的机器码了!大家看图!”
“第一句‘pushebp’对应的机器码是55,第二句‘mov ebp,esp’对应的机器码是8BEC,因为汇编可以完全完成我们的功能,所以我们把汇编对应的机器码原封不动的抄下来,就可得到想要的ShellCode了。
古风一声叫唤提醒了大家,大家奋笔疾书,边看边抄,不一会就写下了shellcode。
[mw_shl_code=c,true]unsigned char shellcode[] =
"\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"
"\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC6"
"\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA"
"\x64\x9f\xE6\x77" //sp3 loadlibrary地址0x77e69f64
"\x52\x8D\x45\xF4\x50"
"\xFF\x55\xF0"
"\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x64\x2E"
"\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4"
"\x50\xB8"
"\xc3\xaf\x01\x78" //sp3 system地址0x7801afc3
"\xFF\xD0";[/mw_shl_code]
“呵呵!很好,大家都很勤奋。”老师笑道,“你们写下来的就是真正的ShellCode!”
“Yeah!”大家都兴奋极了!“终于知道如何写出Shellcode了。”
“是啊,大家都很不错,又有了很大的进步。但和现实中真正用的ShellCode相比,刚才那个ShellCode无论是在功能上还是在实用上都很苍薄,甚至可以说是简陋。所以我们离真正ShellCode的编写还有很长的路要走,我们还是一步一步的来吧!而现在嘛……”老师环顾了一下大家。
大家都紧张的看着老师。
“大家提取ShellCode都辛苦了,先休息十分钟吧!”{:4_102:}
ShellCode通用性的初步分析
老师走后,同学们三三两两地闲聊了起来。
“为什么老师说ShellCode很简陋呢?”
“因为…因为不精致啊……”
“哈哈哈哈,什么啊,那我也知道。”
“我想听听老师回来后会说什么。”
宇强漫不经心的四处看了看,不料一下子看见了那位叫吴小倩的女生。班上的其他女生都坐在一起闲聊,而小倩正在认真的整理笔记,宇强发现她握笔的姿势很美……
“在干什么呢?”玉波走过来对宇强说。
“没什么,活动活动啊……”宇强赶紧说。
“是呀,累啊!大家放学去吃大盘鸡吗?”玉波才来没久,已经吃遍学校周围了,“还有,你觉得那个ShellCode怎样?”
“今天我要回家,下次吧!对于那个ShellCode,”宇强认真的回答道,“我主要觉得功能不强。刚才的ShellCode只是开了个本地DOS窗口,但实际中像.printer漏洞等都是远程的,开个本地的DOS窗口根本没有用处。”
“说的很对!”一个声音传来。大家定睛一看,原来是老师回来了,“我们一起来探讨一下ShellCode吧!”
刚才代码的不足
老师说道,“刚才那个ShellCode不足之处之一是功能不实用,那位同学叫什么?”
“我叫宇强,老师!”宇强回答道。
“对,宇强同学刚才也说了,实际中的溢出漏洞很多都是远程的,那么ShellCode也需要完成远程的功能。”大家都看了看宇强,小倩也瞄了瞄他。
老师接着说:“而实际的ShellCode—般都是开个端口让人远程登陆,或下载文件执行等。除了这一点,刚才的ShellCode还有第二个缺点一不通用。”
“不通用是什么意思?”古风不解的问道。
“就是我们用了固定的函数地址!”老师解释道,“比如刚才CALL LoadLibrary和system函数,用的是Win2000SP3里固定的0x77e69f64和0x7801afc3地址。如果是其他的SP或者是XP(甚至是Windows2003),那函数的地址就不是刚才的那些值,再使用刚才的值就会出问题。”
“会有什么问题呢?”古风继续问道。
“在其他SP系统上,”老师继续说,“0x77e69f64和0x7801afc3可能对应的是其他函数地址,甚至是非法地址,再CALL它们至少不能完成我们想要的功能,甚至会出现报非法操作的错误。”
“哦!这样啊!那这两点都非要解决才能写出通用和实用的ShellCode罗!”
“是啊!但饭要一口一口的吃,事情要一件一件的做。对于Shellcode的功能扩充,我们将在下节课讲到。今天剩下的时间我们先对ShellCode的通用性问题进行初步研究吧!”
通用性的初步探索
“哦!通用性怎么解决呢?”台下有人问道。
“大家先考虑考虑,看看你们能提出什么解决办法不。”老师说。
“嗯,如果我们知道所有不同系统的相关函数地址就好了。”玉波说道。
“我们可以把所有的系统都装上,然后一个个的调试刚才的程序。用刚才读地址的办法,把每个系统的地址一个个的读出来。”古风提出了个狠办法。
“对,这个办法不错啊!”大家异口同声。
“嗯,不错。得到了所有地址之后,大家又想想怎样可以让我们的程序在每种系统上都能使用呢?”老师继续问道,“我们班上的女黑客们还是发一下言啊!”
几位女生都只是抿嘴笑,不说话。
宇强考虑后说道:“我们找到所有系统的地址后,把所有地址都备份在程序里面,以后要针对哪种系统就改成哪种系统的地址。”
“对啊!”大家都赞同的说。
宇强说:“但这种方法好笨啊!每种系统都要找地址,然后改地址,才能成功的执行。好累啊!”说完又往班上的女生们那面看了看,正好和小倩的眼光对视,赶紧转过头,把眼光避开。
“呵呵!是啊,是有点累!但能想出一种解决方案还是很不错。”老师总结道,“现在我们就用这种办法来写ShellCode吧!我们将在ShellCode高级技巧中完美的解决通用性问题。现在这种方法写出的ShellCode应该如下。”
[mw_shl_code=c,true]unsigned char shellcode[] =
"\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"
"\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC6"
"\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA"
//\xFF\xFF\xFF\xFF //sp0 loadlibrary地址
//\xFF\xFF\xFF\xFF //sp1 loadlibrary地址
//\xFF\xFF\xFF\xFF //sp2 loadlibrary地址
//\xFF\xFF\xFF\xFF //sp4 loadlibrary地址
"\x64\x9f\xE6\x77" //sp3 loadlibrary地址0x77e69f64
"\x52\x8D\x45\xF4\x50"
"\xFF\x55\xF0"
"\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x64\x2E"
"\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4"
"\x50\xB8"
//\xFF\xFF\xFF\xFF //sp0 system地址
//\xFF\xFF\xFF\xFF //sp1 system地址
//\xFF\xFF\xFF\xFF //sp2 system地址
//\xFF\xFF\xFF\xFF //sp4 system地址
"\xc3\xaf\x01\x78" //sp3 system地址0x7801afc3
"\xFF\xD0";[/mw_shl_code]
“是啊!到时候选择需要的地址就可以了。”
“嗯,这里再给大家一个礼物:就是自动查找函数地址的程序GetAddr.cpp。我们可以使用它在每种系统中找出任意想要的DLL和函数的地址,程序如下
[mw_shl_code=c,true]#include <windows.h>
#include <stdio.h>
typedef void (*MYPROC)(LPTSTR);
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = LoadLibrary("msvcrt");
printf("msvcrt LibHandle = //x%x\n", LibHandle);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"system");
printf("system = //x%x\n", ProcAdd);
return 0;
}[/mw_shl_code]
“执行效果如下图,它自动找出了我机器上system函数的地址——0x7801afc3。”
“哇!太棒了!”同学们都很高兴,马上就要找各种系统的地址。
“等一下。”老师招呼道,“大家这里分一下工,作为作业拿回家做吧!玉波同学找Win2000SP0的地址;古风找Win2000SP1的地址;宇强找Win2000SP2的地址……女生们就找XP系统下的地址吧!每种系统都找LoadLibrary和system函数的地址,大家找到之后,把它们记下来,作为技术积累,明白了吗?”
大家都点了点头。
“这里提示一下,LoadLibrary函数属于kernel32.dll,所以我们把那个查找程序里的LibHandle=LoadLibrary("msvcrt");改成LibHandle=LoadLibrary("kernel32");
把 ProcAdd=(MYPROC)GetProcAddress(LibHandle,"system");改成ProcAdd=(MYPROC)GetProcAddress(LibHandle,"LoadLibrary")。”
“哦,明白了。”
“以后要查找其他函数的地址时都像这样,把函数名和所在的dll名替换程序的相应地方就可以了。“好咧!
“我们再来看看另一个简单的例子吧,再加深一下大家对ShellCode编写的感觉。”
弹出Windows对话框ShellCode的编写
“刚才ShellCode的功能是弹出DoOS窗口控制台,虽然可以让我们做很多事情;但黑乎乎的,有点不爽!”玉波开玩笑说。
“是啊,爱美之心人皆有之,我也这么认为。”老师说。
“呵呵,是啊!”大家都笑了。
“好。那我们来写一个‘漂亮点’的ShellCode吧!弹出一个Windows图形界面的对话框,如何?
“好啊!
C程序解释
“要弹出一个Windows对话框,user32.dll中的MessageBox函数可以帮助我们完成这个功能。”老师说道。
“简单的说,程序只要一句话,实现如下。”
[mw_shl_code=c,true]#include "windows.h"
int main(int argc, char* argv[])
{
LoadLibrary("user32.dll");
MessageBox(0, "Www.GuHei.Net","提示", 1);
return 0;
}[/mw_shl_code]
“首先,‘LoadLibrary(〃user32.dll")’是加载user32.dll动态链接库,大家都应该清楚吧!”
“然后,‘MessageBox(0, "Www.GuHei.Net","提示", 1);’是弹出Windows对话框。我们执行,可以看到对话框的标题是‘提示’,里面的内容是‘Www.GuHei.Net’。如下图,好看多了吧?”
古风核对了一下说道:“哦!那说明MessageBox函数带的第二个参数‘Www.GuHei.Net’是对话框内容,第三个参数‘提示’是标题内容?”
“恩,是的!”
“那还有第一个和最后一个参数呢?一个用的是0,另一个用的是1,又代表什么意思呢?”古风继续问道。“呵呵!我们看看官方(微软)给的定义吧!第一个参数的帮助信息如下:”
hWnd
[in] Handle to the owner window of the message box to be created. If this parameter is NULL, the message box has no owner window.
“意思是,第一个参数表明对话框所属的窗口句柄。如果第一个参数为NULL(即0),那么对话框不属于任何窗口。这里我们用的就是0,弹出不属于任何窗口的对话框。”
“而最后一个参数,是表明对话框的类型。0代表MB_0K,即只有一个‘0K’按钮;1代表MB+0KCANCEL,对话框会有‘0K’和‘Cancel’两个按钮。这里我们用的就是1,两个按钮的对话框比较好看吧!”
“对话框还有很多类型,比如MB_RETRYCANCEL、MB_YESNO、MB+YESNOCANCEL等,大家可以下去自己看看。”“这里我们接着分析汇编和ShellCode的生成。
生成汇编和ShellCode
“对比前面的分析。执行system(“command.exe”)时,先把参数command.exe字符串的地址入栈,再callsystem的地址就行了。”
“那么,执行MessageBox(0, "Www.GuHei.Net","提示", 1);就是把四个参数从右至左压入堆栈,即先压1,再压‘提示’字符串的地址,然后是‘Www.GuHei.Net’字符串的地址,最后压0;接着CALL MessageBox函数的地址就OK了。”
“1和0多好压啊!只要PUSH0、PUSH1就完成了。”玉波嚷道,“可惜还有两个参数呢!如果参数都是数字就好了。”
宇强想想后,问道,“那‘提示’和‘Www.GuHei.Net’这两个参数串莫非像构造command.exe字符串那样,先在栈里面构造出来,然后把它们的地址作为参数入栈?”
“太对了!”老师表扬道,“第三个参数‘提示’是对话框标题,我们在‘ebp—0Bh’和‘ebp-0A’的地方分别放‘提和示’,而‘ebp-09’放字符串结束标志0x00。那么,‘ebp—0Bh’就是字符串的地址了。示意图如下。”
“我们把‘ebp—0Bh'放在ESI中保存起来,等会儿作为参数入栈,代码如下:”
//标题"提示"->esi
mov byte ptr[ebp-0Bh],77h//w
mov byte ptr[ebp-0Ah],77h//w
mov byte ptr[ebp-09h],0h//0x00
lea esi,[ebp-0Bh]
“然后第二个参数(对话框的内容)‘Www.GuHei.Net’也是类似。我们把它放在‘ebp-07h’开始的地方,并保存在ESI中,代码如下:”
//内容"Www.GuHei.Net"->edi
mov byte ptr[ebp-07h],77h//w
mov byte ptr[ebp-06h],77h//w
mov byte ptr[ebp-05h],30h//0
mov byte ptr[ebp-04h],38h//8
mov byte ptr[ebp-03h],33h//3
mov byte ptr[ebp-02h],30h//0
mov byte ptr[ebp-01h],0h//0x00
lea edi,[ebp-07h]
“参数都构造好了。最后我们合起来执行MessageBox(0,"Www.GuHei.Net","提示",1)吧!”
“第四个参数是1,我们就直接PUSH 1;倒数第二个参数是标题字符串的地址,我们存在ESI中的,所以PUSH ESI;同样,内容字符串的地址是在EDI中,我们PUSH EDI;第一个参数是0,我们PUSH 0。”
“参数都入栈后,我们CALL messagebox函数的地址。在我的机器上,函数的地址是0x77d3add7,我们直接CALL 0x77d3add7就完成执行函数了。
今天早点放学,我给大家再布置一个作业,回去独立完成一个ShellCode,功能是在系统上添加一个用户,并把它加成管理员身份,下节课我会让一位同学上台来给大家讲解他的完成过程。大家都要认真准备啊!不然上台说不出话来,被大家笑话就不好意思了吧!班上还有女生呢!0K,今天到此为止,放学!” |
|