热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

CSShellcode分析入门第二课

 本文作者:Gality本文字数:3700阅读时长:20~30分钟附件链接:点击查看原文下载本文属于【狼组安全社区】原创奖励计划,未经许可禁止转载 本文链接:阅读更佳https:m

 

本文作者:Gality
本文字数:3700

阅读时长:20~30分钟

附件/链接:点击查看原文下载

本文属于【狼组安全社区】原创奖励计划,未经许可禁止转载

 

本文链接:阅读更佳 https://mp.weixin.qq.com/s/uQjkf5fTf8s_qAOwZkcpLA

 

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,狼组安全团队以及文章作者不为此承担任何责任。

狼组安全团队有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经狼组安全团队允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。

图片

 

前言

        本文是 CS-Shellcode分析系列 第一课 第二篇文章,该系列文章旨在帮助具有一定二进制基础的朋友看懂cs的shellcode的生成方式,进而可以达到对shellcode进行二进制层面的改变与混淆,用于免杀相关的研究

免杀加载器:https://github.com/wgpsec/CS-Avoid-killing

SehllCode分析

        接上文,我们接着说,具体怎么比较的. 其实在前面有段代码的一个细节我是直接略过没有提的, 不知道师傅萌是否有疑问,就是这个

图片

最开始的跳转中其实传递了一个726774C的16进制值, 这个值有什么含义呢, 这个会在这一篇中说到

我们直接来看后续的操作:

图片

        这个部分的操作比较诡异, 上文说到了这里有一步将小写字母变大写字母的操作, rcx中存的是当前程序名的长度,然后后续ror r9d, 0Dh以及add r9d, eax这两步其实是在做一个类似于求特征值的这么一个操作, lodsb从rsi指向的地方取字符(也就是程序名字符串), 然后通过ror的循环右移以及加法, 对应求出了该字符串的一个特征值, 这里我想了很多天也没有想清楚为什么一定是右移0xD次, 整个shellcode中求字符串特征值的操作都是通过这种循环右移取特征值的方式来计算的, 是否有什么理论基础如果有师傅懂的话请务必评论赐教Orz(只查到说是这是一套用于将当前函数名称转化为DWORD的hash数据值的算法,目的是方便比对)

在将自己程序字符全部大写取特征字符串后压栈, 然后就是如下操作

图片

        这里rdx还是指向之前的_LDR_DATA_TABLE_ENTRY这个结构, 同样是查表, 可以发现取到了其0x20h对应的InMemoryOrderLinks这一项对应的值,为什么是对应InMemoryOrderLinks, 是因为其实在_PEB_LDR_DATA中 InMemoryOrderModuleList成员的指向的是_LDR_DATA_TABLE_ENTRYInMemoryOrderLinks成员的地址,也就是说此时通过InMemoryOrderModuleList找到的_LDR_DATA_TABLE_ENTRY结构的地址并不是该结构的起始地址,真实的起始地址还需要减0x10, 所以说该_LDR_DATA_TABLE_ENTRY的真实起始地址其实是rdx-10,所以[rdx+20]减去偏移后其实是该_LDR_DATA_TABLE_ENTRY结构的0x30处的偏移,也就是DllBase这个成员, 该成员存储了该dll的基地址,此时,rdx指向Dll的基地址


PEB里的ldr域中的那三个值(Ldr.InInitializationOrderModuleList Ldr.InLoadOrderModuleList Ldr.InMemoryOrderModuleList)并不是直接就是链表中_ldr_data_table_entry结构体的基址

Ldr.InLoadOrderModuleList的值指向的是_ldr_data_table_entry中InLoadOrderLinks成员的地址(恰恰是_ldr_data_table_entry的基址,因为InLoadOrderLinks正是该结构体的第一个成员), Ldr.InMemoryOrderModuleList的值指向的是_ldr_data_table_entry中InMemoryOrderLinks成员的地址,同样,Ldr.InInitializationOrderModuleList 的值指向的是_ldr_data_table_entry中InInitializationOrderLinks成员的地址.因此,使用Ldr.InMemoryOrderModuleList 和Ldr.InInitializationOrderModuleList进行链表遍历查看的时候,应该将其值相应的减去0x10和0x20才对, 通过windbg也可以证实这一点:

0:000> dt _PEB_LDR_DATA 0x00007fff`0119b4c0
ntdll!_PEB_LDR_DATA
+0x000 Length : 0x58
+0x004 Initialized : 0x1 ''
+0x008 SsHandle : (null)
+0x010 InLoadOrderModuleList : _LIST_ENTRY [ 0x00000295`d1802ec0 - 0x00000295`d1804e80 ]
+0x020 InMemoryOrderModuleList : _LIST_ENTRY [ 0x00000295`d1802ed0 - 0x00000295`d1804e90 ]
+0x030 InInitializationOrderModuleList : _LIST_ENTRY [ 0x00000295`d1802d10 - 0x00000295`d1804ea0 ]
+0x040 EntryInProgress : (null)
+0x048 ShutdownInProgress : 0 ''
+0x050 ShutdownThreadId : (null)

可以看到InLoadOrderModuleListInMemoryOrderModuleList的Flink与Blink都差了0x10的长度,InInitializationOrderModuleList同理


这里再次放一个_LDR_DATA_TABLE_ENTRY结构, 方便对照:

1:001> dt _LDR_DATA_TABLE_ENTRYntdll!_LDR_DATA_TABLE_ENTRY +0x000 InLoadOrderLinks : _LIST_ENTRY +0x010 InMemoryOrderLinks : _LIST_ENTRY +0x020 InInitializationOrderLinks : _LIST_ENTRY +0x030 DllBase : Ptr64 Void +0x038 EntryPoint : Ptr64 Void +0x040 SizeOfImage : Uint4B +0x048 FullDllName : _UNICODE_STRING +0x058 BaseDllName : _UNICODE_STRING +0x068 FlagGroup : [4] UChar +0x068 Flags : Uint4B +0x068 PackagedBinary : Pos 0, 1 Bit +0x068 MarkedForRemoval : Pos 1, 1 Bit +0x068 ImageDll : Pos 2, 1 Bit +0x068 LoadNotificationsSent : Pos 3, 1 Bit +0x068 TelemetryEntryProcessed : Pos 4, 1 Bit +0x068 ProcessStaticImport : Pos 5, 1 Bit +0x068 InLegacyLists : Pos 6, 1 Bit +0x068 InIndexes : Pos 7, 1 Bit +0x068 ShimDll : Pos 8, 1 Bit +0x068 InExceptionTable : Pos 9, 1 Bit +0x068 ReservedFlags1 : Pos 10, 2 Bits +0x068 LoadInProgress : Pos 12, 1 Bit +0x068 LoadConfigProcessed : Pos 13, 1 Bit +0x068 EntryProcessed : Pos 14, 1 Bit +0x068 ProtectDelayLoad : Pos 15, 1 Bit +0x068 ReservedFlags3 : Pos 16, 2 Bits +0x068 DontCallForThreads : Pos 18, 1 Bit +0x068 ProcessAttachCalled : Pos 19, 1 Bit +0x068 ProcessAttachFailed : Pos 20, 1 Bit +0x068 CorDeferredValidate : Pos 21, 1 Bit +0x068 CorImage : Pos 22, 1 Bit +0x068 DontRelocate : Pos 23, 1 Bit +0x068 CorILOnly : Pos 24, 1 Bit +0x068 ChpeImage : Pos 25, 1 Bit +0x068 ReservedFlags5 : Pos 26, 2 Bits +0x068 Redirected : Pos 28, 1 Bit +0x068 ReservedFlags6 : Pos 29, 2 Bits +0x068 CompatDatabaseProcessed : Pos 31, 1 Bit +0x06c ObsoleteLoadCount : Uint2B +0x06e TlsIndex : Uint2B +0x070 HashLinks : _LIST_ENTRY +0x080 TimeDateStamp : Uint4B +0x088 EntryPointActivationContext : Ptr64 _ACTIVATION_CONTEXT +0x090 Lock : Ptr64 Void +0x098 DdagNode : Ptr64 _LDR_DDAG_NODE +0x0a0 NodeModuleLink : _LIST_ENTRY +0x0b0 LoadContext : Ptr64 _LDRP_LOAD_CONTEXT +0x0b8 ParentDllBase : Ptr64 Void +0x0c0 SwitchBackContext : Ptr64 Void +0x0c8 BaseAddressIndexNode : _RTL_BALANCED_NODE +0x0e0 MappingInfoIndexNode : _RTL_BALANCED_NODE +0x0f8 OriginalBase : Uint8B +0x100 LoadTime : _LARGE_INTEGER +0x108 BaseNameHashValue : Uint4B +0x10c LoadReason : _LDR_DLL_LOAD_REASON +0x110 ImplicitPathOptions : Uint4B +0x114 ReferenceCount : Uint4B +0x118 DependentLoadFlags : Uint4B +0x11c SigningLevel : UChar

而我们知道dll的结构其实跟PE的文件结构基本是一致的,dll的头也是由DOS头,PE头等组成的


PE结构可以大致分为:


  • DOS部分

  • PE文件头

  • 节表(块表)

  • 节数据(块数据)

  • 调试信息


此时rdx指向的就是dos头的起始,这里提供一个dos头的定义:

IMAGE_DOS_HEADER { WORD e_magic; // +0000h - EXE标志,“MZ” WORD e_cblp; // +0002h - 最后(部分)页中的字节数 WORD e_cp; // +0004h - 文件中的全部和部分页数 WORD e_crlc; // +0006h - 重定位表中的指针数 WORD e_cparhdr; // +0008h - 头部尺寸,以段落为单位 WORD e_minalloc; // +000ah - 所需的最小附加段 WORD e_maxalloc; // +000ch - 所需的最大附加段 WORD e_ss; // +000eh - 初始的SS值(相对偏移量) WORD e_sp; // +0010h - 初始的SP值 WORD e_csum; // +0012h - 补码校验值 WORD e_ip; // +0014h - 初始的IP值 WORD e_cs; // +0016h - 初始的CS值 WORD e_lfarlc; // +0018h - 重定位表的字节偏移量 WORD e_ovno; // +001ah - 覆盖号 WORD e_res[4]; // +001ch - 保留字00 WORD e_oemid; // +0024h - OEM标识符 WORD e_oeminfo; // +0026h - OEM信息 WORD e_res2[10]; // +0028h - 保留字 LONG e_lfanew; // +003ch - PE头相对于文件的偏移地址 }

我们可以看到0x3c处储存了e_lfanew,该变量指明了PE头相对于文件的偏移地址, 所以rdx+3c处得到的偏移值加上基地址就是该dll的PE头的地址。

而PE头的结构如下:

IMAGE_NT_HEADERS { DWORD Signature; // +0000h - PE文件标识,“PE00” IMAGE_FILE_HEADER FileHeader; // +0004h - PE标准头 IMAGE_OPTIONAL_HEADER32 OptionalHeader; // +0018h - PE扩展头}

这里PE标准头的内容我们暂且不关注,看0x18处,也就是PE扩展头的结构:

该位置是一个魔数,用于标明类型:

typedef struct _IMAGE_OPTIONAL_HEADER64 { WORD Magic; // +0018h - 标志字, ROM 映像(0107h),32位普通可执行文件(010Bh),64位可执行文件(0x20B)。 BYTE MajorLinkerVersion; BYTE MinorLinkerVersion;
//以下3个字段都是FileAlignment的整数倍,已弃用。 DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint; //RVA address!!!!
DWORD BaseOfCode;
ULONGLONG ImageBase; DWORD SectionAlignment; //内存中区块的对齐大小 0x1000==4kB DWORD FileAlignment; //文件中区块的对齐大小 0x0200==512B
WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; //how to build the initial gui WORD DllCharacteristics; ULONGLONG SizeOfStackReserve; ULONGLONG SizeOfStackCommit; ULONGLONG SizeOfHeapReserve; ULONGLONG SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //0x88} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

所以,这里我们就明白了cmp word ptr [rax+18h], 20Bh这一步其实就是判断是否是64位可行文件,如果是,则将PE头0x88处的值放入eax中,同样去找该偏移对应的是什么,可以看到在0x78后是16个IMAGE_DATA_DIRECTORY结构


数据目录项 IMAGE_DATA_DIRECTORY

IMAGE_OPTIONAL_HEADER结构的最后一个字段为DataDirectory。

该字段定义了PE文件中出现的所有不同类型的数据的目录信息,从Windows NT 3.1操作系统开始到现在,该数据目录中定义的数据类型一直是16种。

PE中使用了一种称作“数据目录项IMAGE_DATA_DIRECTORY”的数据结构来定义每种数据。

typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; /**指向某个数据的相对虚拟地址 RAV 偏移0x00**/
DWORD Size; /**某个数据块的大小 偏移0x04**/
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

总的数据目录一共由16个相同的IMAGE_DATA_DIRECTORY结构连续排列在一起组成。

如果想在PE文件中寻找特定类型的数据,就需要从该结构开始。

这16个元组的数组每一项均代表PE中的某一个类型的数据,各数据类型为:


所以其实这里获取到了第一项也就是导出表的RVA, 而我们知道dll是一定有导出表的,而一般exe没有导出表,所以这里为0,表示不存在导出表

图片 所以这里就相当于判断了是不是dll,如果是,则rax不为0,流程接着往后走,而如果不是,rax会为0,则跳转loc_c7

图片

这里pop出了r9和rdx, 将rdx指向的值放入rdx中并跳转到loc_21处, 这里弹出的两个值就是之前压栈的r9和rdx的值, 对应着本程序名的特征字符串和_LDR_DATA_TABLE_ENTRY的地址, 然后mov rdx, [rdx]就是取InMemoryOrderLinks中的Flink的值存入了rdx, 对应着模块加载顺序中的前一个_LDR_DATA_TABLE_ENTRY的地址,而后就是一样的操作开始循环InMemoryOrderLinks这个列表中的_LDR_DATA_TABLE_ENTRY结构了

图片

为了便于理解,梳理整个流程, 我做了如下的图表:

图片

那么, 当遍历的模块是个dll时,会执行如下操作:

add rax,rdxpush rax, 我们之前说了Export Directoy RVA储存的是相对虚拟地址,可以理解为文件被装载到虚拟内存(拉伸)后先对于基址的偏移地址,所以基址加RVA得到其导出表(IMAGE_EXPORT_DIRECTORY)的真实地址,而该表的定义又如下:

typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; // +0000h DWORD TimeDateStamp; // +0004h WORD MajorVersion; // +0008h WORD MinorVersion; // +000Ah DWORD Name; // +000Ch DWORD Base; // +0010h DWORD NumberOfFunctions; // +0014h DWORD NumberOfNames; // +0018h 以函数名字导出的函数个数 DWORD AddressOfFunctions; // +001Ch 导出函数地址表RVA:存储所有导出函数地址(表元素宽度为4,总大小NumberOfFunctions * 4) DWORD AddressOfNames; // +0020h 存储函数名字符串所在的地址RVA(表元素宽度为4,总大小为NumberOfNames * 4) DWORD AddressOfNameOrdinals; // +0024h 存储函数序号RVA(表元素宽度为2,总大小为NumberOfNames * 2)} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

所以又分别将NumberOfNames放入ecx,AddressOfNames放入r8d, 为后面遍历所有导出函数做准备, add r8, rdx取到了导出函数名字符串储存地址表

接着其实就是对遍历所有函数名,求函数名的特征字符值,然后与726774C进行比较(726774C存在r10d中)如果不同,则代表不是想要的函数,如果是则进行后面的步骤。而该值,其实就是LoadLibraryA函数的特征字符值,由于需要自己加载dll,而又不想在程序中直接出现调用LoadLibraryA的特征,所以只能使用这种方法来找到该函数的地址来调用他,这种调用方式在shellcode中非常常见,是一种通用的shellcode调用LoadLibraryA的方式,同时也比较隐蔽

        这一章就先到这里,通过到目前为止的分析,我们已经可以找到LoadLibraryA函数的地址用于调用,那么后续如何,请听下回分解

有想一起研究免杀技术或者二进制技术的师傅萌,简历请砸Gality@wgpsec.org,欢迎各位师傅一起交流学习



推荐阅读
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 电话号码的字母组合解题思路和代码示例
    本文介绍了力扣题目《电话号码的字母组合》的解题思路和代码示例。通过使用哈希表和递归求解的方法,可以将给定的电话号码转换为对应的字母组合。详细的解题思路和代码示例可以帮助读者更好地理解和实现该题目。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 多维数组的使用
    本文介绍了多维数组的概念和使用方法,以及二维数组的特点和操作方式。同时还介绍了如何获取数组的长度。 ... [详细]
author-avatar
永远在等一个人--嘉儿
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有