系统安全及免杀(4) 黑客外传之shellcode传奇(2)

上一集主要说的是杀软如何的检测,以及大致的实现方法,重点是诸多的检测指标,做了一些理论基础的准备

这一集来动手搓一个实际的样本出来喵

鄙人技术浅陋,经网上四处观看浏览总结得此文,如有不足希望师傅们多包容⊙﹏⊙

下面正文开始

准备 – 一只 Shellcode 被赋予的使命

shellcode 的生成在上一集做过了,也没什么可讲的,用现成的 c2 鼠标点点点就行,msf,cs 啥的都能用

生成的 shellcode – 看到的那一大堆的字符其实是有实际代表的功能滴,但是涉及的东西太多,太底层,这里先跳过喵,之后有机会再单独开一集讲讲(

但是如上一集介绍 shellcode 的时候所说的一样,这个东西生成出来就是一串字,写进数组里面 emmm,然后是怎么到创建连接远程上线的呢,下面说一说

shellcode 要想实现诸如创建 TCP 连接,读写文件,执行命令甚至调用工具等功能,都必须要依靠 Windows 系统本身提供的 API 函数进行操作

//说完这句话自然会引出一个问题,为啥是必须这么干啊,这个问题后续会说的喵

而那些自带的关键 API 函数(比如 GetProcAddress ,或者这个系列第一集提到过的 LoadLibrary 函数等)都存放在系统的 dll 里面,比如 kernel32.dll、kernelbase.dll、ntdll.dll 之类的,shellcode 里面是不能写死这些函数的地址的,因为不同的 win 版本,不同补丁环境等等非常繁多的因素影响下,这些函数在内存里面的地址都会不一样,因此 shellcode 必须要先主动的找到这些函数的地址,主要通过 TEB 和 PEB 来实现

TEB(线程环境块)

每个线程在对应的内存中都有一个专属的 TEB 结构体,里边存储了当前线程的信息,比如线程 ID、栈的起始地址之类的东西,但是最关键的是其中存有指向 PEB 的指针

TEB 结构体里有一个字段叫 ProcessEnvironmentBlock,它的作用就是 指向当前进程的 PEB,这个字段在 TEB 里的偏移也是固定的(x86、x64 都是 0x00)只要能找到 TEB 就一定能获得这个指针

那 TEB 在需要用的时候咋找到呢?在 x86 架构,TEB 的地址可以通过寄存器 fs 直接获取,固定偏移 0x30,在 x64 架构下,通过寄存器 gs 获取,固定偏移是 0x60,这个偏移是 Win 系统的 “硬规则”,不会变化的喵

然后通过找到里面的指针抓住 PEB

PEB(进程环境块)

PEB 和 TEB 就好像那个海尔兄弟,舒克和贝塔,都差不多,每个进程在内存里面也都有一个 PEB 结构体,里面也是存着一堆像是进程 ID 之类的东西,最关键的内容是其中有已经加载出来的 DLL 的列表

上文说过,要想找到关键函数地址,这个地址在哪都是不一样的,需要找到对应所属的 dll,那通过 TEB 找到 PEB 之后就可以获取到 dll 加载在哪了

PEB 里边有一个 LDR 字段,指向一个结构体 PEB_LDR_DATA ,其中就存储有一个已加载出来的 dll 的链表,启动进程时,会把所有默认加载的 DLL(比如我们要找的 kernel32.dll)按顺序存在这个链表中

链表的每一个节点都存有三个数据:DLL 的名字(比如 kernel32.dll)、DLL 在内存中的起始地址(基地址)、指向下一个节点的指针

至此答案已经显而易见了喵!

只要遍历这个链表,逐个对比 DLL 名字,直到找到需要的 kernel32.dll(或 kernelbase.dll)—— 因为 LoadLibrary 和 GetProcAddress 这两个函数就在这个 DLL 里,拿到 kernel32.dll 的起始地址后,就可以通过这个 DLL 找到 LoadLibrary 和 GetProcAddress 的具体地址,这个目的是通过 解析 dll 的 PE 结构 来实现的:

  1. 首先每个 DLL 都是一个 PE 文件(Windows 可执行文件格式),PE 结构里有一个导出表(Export Table)
  2. 导出表会列出这个 DLL 对外暴露的所有函数的名字和对应的内存偏移量
  3. 解析导出表,找到 LoadLibrary 和 GetProcAddress 这两个函数对应的偏移,再加上 dll 的基地址,就能得出函数的实际内存地址

拿到这两个函数的地址后,就可以用 LoadLibrary 加载其他的 dll,用 GetProcAddress 查找 dll 里的具体函数(比如 socketconnect,拿来创建 TCP 连接)

终于… shellcode 如愿以偿的可以获得想要的函数,通过这些函数去完成一个 c2 的使命

利用已知的平台(windows)特定机制来执行特定操作,因此有时在一些地方会称呼 shellcode 为引导代码

将 shellcode 代入执行

回到最开始那个问题,shellcode 做的事情已经说明白了,那他是怎么从一个字符串数组开始做那么多的捏

这就说到 C++ 了,执行字符数组的经典写法是将其作为函数指针:

void (*func)();
func = (void (*)()) code;
func();

更简短浓缩一点的单行写法:

(*(void(*)()) code)();

这里就可以回答之前的问题了喵,为什么要使用 shellcode ,导致必须调用系统的 API 函数来实现功能,既然已经用 c++ 写 loader 了,为啥不干脆用 c++ 实现那些功能?

显而易见喵,shellcode 可以在生成之前,通过 c2 平台直接一下子生成出来,还可以选择很多自定义的配置,不用重复改动 C++ 代码,而且直接拿 C++ 写恶意代码去做 c2 太容易被杀软识别,而 Shellcode 的操作空间可就多了喵(其实方便才是最关键的hh

但是话又说回来,有这么两个问题:

  1. windows 系统的数据执行保护机制,特别是栈上的数据受到执行保护(DEP)
  2. 要给 Shellcode 分配可执行的内存空间,并启动线程执行

实际执行 shellcode 的方法有点区别:

首先,使用 VirtualAlloc(或用于远程进程的 VirtualAllocEx)这俩都是 API 函数,可以通过上文说的 TEB/PEB 找到,因为数组存在栈上,而 win 不允许栈上数据进行执行,就是防一手恶意代码注入内存,用 VirtualAlloc 就可以主动申请一块 “可执行” 权限的内存(PAGE_EXECUTE_READWRITE),绕开 DEP 的限制

然后把 shellcode 填充进去(用 RtlCopyMemory 函数)然后分别使用 CreateThread 或 CreateRemoteThread 函数创建新线程

#include <Windows.h> //包含 Windows API 头文件
void main(){
    //准备好Shellcode数组
    const char shellcode[] = " "; 

    //分配可执行的内存空间
    PVOID shellcode_exec = VirtualAlloc(
        0,                  // 让系统自动分配内存地址
        sizeof shellcode,   // 内存大小=Shellcode 的长度
        MEM_COMMIT|MEM_RESERVE,  // 申请内存并提交(必须参数)
        PAGE_EXECUTE_READWRITE  // 内存权限rwx
    );

    //把 Shellcode 复制到分配好的内存中
    RtlCopyMemory(shellcode_exec, shellcode, sizeof shellcode); 
    //RtlCopyMemory复制数据

    //创建新线程,执行内存中的 Shellcode
    DWORD threadID; //存储线程ID
    HANDLE hThread = CreateThread(
        NULL,                  //无安全属性
        0,                     //线程栈大小默认
        (PTHREAD_START_ROUTINE)shellcode_exec,  //线程执行的代码地址(就是 Shellcode 所在内存)
        NULL,                  //给线程传的参数,暂时设为null
        0,                     //线程创建后立即执行
        &threadID              //接收线程 ID 的变量
    );

    //等待线程执行完成
    WaitForSingleObject(hThread, INFINITE);
}

都打了注释,可以看看喵

解释一下为什么要创建新线程,直接执行 shellcode_exec 也可以,但用 CreateThread 能让 Shellcode 在后台运行,程序主线程不会被阻塞,更隐蔽一些

单纯的写出来这么一个很原生的 loader,来测一测免杀效果试试?直接上 vt

19/72,还不错喵,但是还是会被 defender 等主流强力杀软干掉的,下一集搞一搞进阶一点的免杀手法,争取把检出率再压低一点

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇