前言
良好的习惯是人生产生复利的有力助手。
2020年第四篇文章,继续今年的flag:每周至少更新一篇文章。
PE to Shellcode原理
在上一篇文章中,介绍了PE_to_shellcode这个项目,并简单提了一句原理,本节就详细讲解一下PE是如何转化为shellcode的。
好奇心
shellcode是一段可以在内存中直接执行的指令,先将指令载入可读可写可执行的缓冲区,将指令指针指向缓冲区的起始位置,依次往下执行即可。
PE文件本身是无法直接在内存中执行的,windows操作系统需要将PE文件按照规则映射到内存中,并将指令指针指向程序入口就可以执行了,这就是PE loader的工作流程。
PE文件本身并不重要,它的执行依赖于PE loader,因此PE如何转化为shellcode的问题,变成了PE loader 如何转化为shellcode的问题。
PE文件功能千奇百怪,不受我们控制,但是PE loader 功能固定,我们只要通过shellcode实现PE loader,就可以达成目标。通过上述的思考,一个PE 转化为shellcode的结构模型就出来了,如下图所示,Stub是shellcode化的PE loader。
在项目的工程目录中,hldr32和hldr64分别是32位和64位的PE loader shellcode实现。
PE loader的实现
因为项目中有32位和64位的PE loader shellcode实现,我们仅以32位为例进行讲解,由于涉及的知识点过多, 部分的内容一句带过,不进行详细描述,之后会有专题进行讲解,本次只是搭建起整个 PE loader的框架,让大家明白整体流程。
(1)定位kernel基地址
实现PE Loader 需要找到GetProcAddress,LoadLibraryA,VirtualAlloc 三个关键API的地址:
- LoadLibraryA 用来加载动态链接库
- GetProcAddress 用来获取动态链接库中的API函数地址
- VirtualAlloc 用于为PE文件分配内存空间
这三个API位于kernel32.dll中,而每个进程启动都会自动加载kernel32.dll,因此需要先找到进程中的kernel32.dll基址,然后再通过偏移量找到API的入口地址。寻找进程中的kernel32.dll基地址,分为三个步骤:
1.定位TEB与PEB
TEB( 线程环境块)中保存频繁使用的线程相关的数据。进程中的每个线程都有自己的一个TEB。一个进程的所有TEB都以堆栈的方式,PEB(进程环境块)存放进程信息,每个进程都有自己的PEB信息。
通过FS寄存器可以获取TEB的基址:在FS存储的是TEB在GDT 中的序号,通过GDT获取TEB的基址。
PEB结构体在TEB偏移0x30处,即FS:[0x30]。
2.定位Ldr
在PEB偏移0x0c处是Ldr,Ldr的类型为PEBLDRDATA结构体指针。Ldr的作用是存储进程已加载的模块(Module)信息。Module是指PE格式的可执行映像,包括EXE映像和DLL映像。Ldr通过3个队列存储进程加载的Module信息,即InLoadOrderModuleList、InMemoryOrderModuleList、和InInitializationOrderModuleList,我们选择的是InLoadOrderModuleList,加载的模块顺序如下:
代码语言:javascript复制自身.exe -> ntdll.dll -> kernel32.dll ->KERNELBASE.DLL -> NULL
3.定位LDR_DATA_TABLE_ENTRY
每当为本进程装入一个模块时,就要为其分配、创建一个LDRDATATABLEENTRY数据结构,并将其挂入InLoadOrderModuleList和InMemoryOrderModuleList,完成对这个模块的动态链接以后,就把它挂入InInitializationOrderModuleList队列,以便依次调用模块的初始化函数。由此可见进程加载的每个模块都会有一个LDRDATATABLEENTRY,其作用为存储模块的基本信息,DLL基址在其偏移0x18处。
这三步看似复杂,但最终的汇编代码 很简单:
代码语言:javascript复制 push tebProcessEnvironmentBlock pop eax fs mov eax, dword [eax];定位PEB mov eax, dword [eax pebLdr] ;找到ldr mov esi, dword [eax ldrInLoadOrderModuleList] ;找到ldrInLoadOrderModuleList lodsd ;不断的向后寻找kernel xchg eax, esi lodsd mov ebp, dword [eax mlDllBase];kernel dll的基地址 call parse_exports
(2) 定位API
我们找到了kernel32.dll的基地址,接下来通过偏移量找到kernel32.dll的导出表,最后通过导出表找到api的入口地址。导出表的结构如下,最终要找到AddressOfFunctions。
(3) 映射PE文件到内存
- 使用VirtualAlloc分配内存
- 映射map MZ header, NT Header, FileHeader, OptionalHeader, all section headers
- 映射sections data
(4) 导入dll 并重定向地址
使用LoadLibraryA循环加载PE文件导入表中的dll
(5) 设置入口点并执行
略
猜想
通过上文提到的结构模型和PE Loader的实现,基本上可以完成PE转化为shellcode的功能,但是PE_to_shellcode项目生成的shellcode 不仅可以采用shellcode的加载方式,而且可以双击像PE一样独立运行,这是怎么做到的呢?至少上文的结构模型完成不了,因为Stub已经破坏了PE头,操作系统加载的时候是不会将他识别为PE文件的!!!
之前比较PEtoshellcode项目修改前和修改后的程序发现,修改后的程序是有MZ标识,而且Stub是附加在PE文件后面的,这给了我很大的启发。
一文件两用
新模型
通过猜想和比对,对原有的结构模型进行改进,将Stub是附加在PE文件后面,并对PE文件头部进行修改实现跳转,从而实现PE文件一文件两用。
在新模型中,Stub可以不用改变,直接附加在PE文件的最后,对PE文件的头部添加一段跳转shellcode,而且这段shellcode必须以"MZ"开头,这样才能被识别为正常的PE文件。PE文件的头部是DOS头,其结构如下,比较重要的是emagic和elfanew,而其他的位置内容改变可以随意一些:
“MZ”开头的shellcode
由于“MZ”必不可少,那需要看一下M和Z对应 ASCII的汇编指令:
- M对应的值是x4D,汇编指令是 dec ebp
- Z对应的值是x5A,汇编指令是 pop edx
在接下来的shellcode里,首先消除上面两条指令的影响,具体内容如下,仔细看注释:
代码语言:javascript复制bool overwrite_hdr(BYTE *my_exe, size_t exe_size, DWORD raw){ BYTE redir_code[] = "x4D" //dec ebp "x5A" //pop edx "x45" //inc ebp "x52" //push edx "xE8x00x00x00x00" //call <next_line> "x5B" // pop ebx "x48x83xEBx09" // sub ebx,9 "x53" // push ebx (Image Base) "x48x81xC3" // add ebx, "x59x04x00x00" // value "xFFxD3" // call ebx "xc3"; // ret
size_t offset = sizeof(redir_code) - 8;
memcpy(redir_code offset, &raw, sizeof(DWORD)); memcpy(my_exe, redir_code, sizeof(redir_code)); return true;}
跳转的小技巧
在上面的shellcode中,有三行可能大家不明白:
代码语言:javascript复制"xE8x00x00x00x00" //call <next_line>"x5B" // pop ebx"x48x83xEBx09" // sub ebx,9
shellcode如何跳转到Stub,必须要知道Stub在内存中的地址。我们可以先知道整个文件加载到内存中的基地址,然后通过偏移找到Stub。但是如何找到基地址呢?我们可以知道自身指令在内存中的地址,然后减去执行的指令字节数就是基地址,常用的是call-pop方式。
代码语言:javascript复制"xE8x00x00x00x00" //call <next_line>"x5B" // pop ebx
此时ebx中存储的是就是当前指令的地址。
推荐阅读:
无文件执行:一切皆是shellcode (上)
linux无文件执行— fexecve 揭秘
沙盒syscall监控组件:strace and wtrace
无"命令"反弹shell-逃逸基于execve的命令监控(上)
APT组织武器:MuddyC3泄露代码分析
Python RASP 工程化:一次入侵的思考
教你学木马攻防 | 隧道木马 | 第一课