前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >无文件执行:一切皆是shellcode (中)

无文件执行:一切皆是shellcode (中)

作者头像
七夜安全博客
发布2020-03-12 15:49:01
3.1K0
发布2020-03-12 15:49:01
举报
文章被收录于专栏:七夜安全博客七夜安全博客

前言

良好的习惯是人生产生复利的有力助手。

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的地址:

  1. LoadLibraryA 用来加载动态链接库
  2. GetProcAddress 用来获取动态链接库中的API函数地址
  3. 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文件到内存
  1. 使用VirtualAlloc分配内存
  2. 映射map MZ header, NT Header, FileHeader, OptionalHeader, all section headers
  3. 映射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        "\xE8\x00\x00\x00\x00" //call <next_line>        "\x5B" // pop ebx        "\x48\x83\xEB\x09" // sub ebx,9        "\x53" // push ebx (Image Base)        "\x48\x81\xC3" // add ebx,        "\x59\x04\x00\x00" // value        "\xFF\xD3" // 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
复制
"\xE8\x00\x00\x00\x00" //call <next_line>"\x5B" // pop ebx"\x48\x83\xEB\x09" // sub ebx,9

shellcode如何跳转到Stub,必须要知道Stub在内存中的地址。我们可以先知道整个文件加载到内存中的基地址,然后通过偏移找到Stub。但是如何找到基地址呢?我们可以知道自身指令在内存中的地址,然后减去执行的指令字节数就是基地址,常用的是call-pop方式。

代码语言:javascript
复制
"\xE8\x00\x00\x00\x00" //call <next_line>"\x5B" // pop ebx

此时ebx中存储的是就是当前指令的地址。

推荐阅读:

无文件执行:一切皆是shellcode (上)

linux无文件执行— fexecve 揭秘

沙盒syscall监控组件:strace and wtrace

无"命令"反弹shell-逃逸基于execve的命令监控(上)

APT组织武器:MuddyC3泄露代码分析

Python RASP 工程化:一次入侵的思考

教你学木马攻防 | 隧道木马 | 第一课

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-03-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 七夜安全博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • PE to Shellcode原理
    • 好奇心
      • PE loader的实现
        • (1)定位kernel基地址
        • (2) 定位API
        • (3) 映射PE文件到内存
        • (4) 导入dll 并重定向地址
        • (5) 设置入口点并执行
      • 猜想
      • 一文件两用
        • 新模型
          • “MZ”开头的shellcode
            • 跳转的小技巧
            相关产品与服务
            腾讯云代码分析
            腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档