2020-11-25 18:39:44    666    0    1

0x01 什么是 CNG 以及为什么要使用它

本来我在做使用 WIN32 API 加密的时候(比如 AES、RC4) 是通过 CSP 实现的。CSP 是一个独立的软件模块,实际上执行用于身份验证,编码和加密的密码算法。CSP 提供了一组密码学 API,使应用程序开发人员可以向基于 Windows 的应用程序添加身份验证,编码和加密。

但是现在有了新一代密码学 API,也被称为 CNGCryprography API: Next Generation),MSDN 号称:

之前的 Cryptography API 不再推荐使用。

如:

  • CryptAcquireContextA
  • CryptCreateHash
  • CryptDeriveKey
  • ...

新的和现有的软件应开始使用Cryptography Next Generation API。Microsoft可能会在将来的版本中删除之前的 Cryptography API

本文就是使用 CNG(新一代密码学 API)来示范 AES 的使用,因为避免关联,不会给出完整代码,使用的尽量是 MSDN 上的代码或来源于其他合法渠道的公开代码示例。

0x02 实现 AES CBC 模式

CBC 模式是需要使用 IV 的。IV 即初始化向量,相比于 ECB 模式,CBC 模式对每个明文分组要与前一个密文分组进行异或,这样的作用是即时两个明文分组的值是相等的,经过多字节异或,对应的两个密文分组的值也不一定是相等的。而IV 的使用就是为了解决第一个明文分组跟谁异或的问题。

当加密第一个明文分组时,由于不存在“前一个密文分组”,因此需要事先准备一个长度为一个分组的比特序列来代替“前一个密文分组”,这个比特序列称为初始化向量(Initialization Vector),通常缩写为IV,一般来说,每次加密时都会随机产生一个不同的比特序列来作为初始化向量。

这里要注意,IV 的长度与分组的大小是一致的。而 AES 的标准分组为 128 Bits,即为 16 字节。

AES 有三个输入和一个输出:

  • 输入1:IV。IV 不得大于16字节,如果用户输入大于则截断,小于则填充0。
  • 输入
2020-11-20 17:53:41    699    0    0

CS (测试版本 4.0)生成的默认宏样本,如下定义此结构体:

  1. Private Type PROCESS_INFORMATION
  2. hProcess As Long
  3. hThread As Long
  4. dwProcessId As Long
  5. dwThreadId As Long
  6. End Type

但其实:

  1. hProcess As Long
  2. hThread As Long

在64位机器环境下均定义错误。

因为 64 位环境下, VBA 中 Long 仅为4字节,如果按照这个宏样本去运行,取第三个参数的值,就会发现并不是正确的 PID:

  1. Debug.Print pInfo.dwProcessId

但 CS 的默认宏样本却能正确运行,这是因为 hProcess 不过是一个进程句柄,是可以被4字节容纳下的。但是错误的内存大小定义会影响到占位和第三个参数的值,所以应该根据系统选择性的定义:

  1. #If VBA7 And Win64 Then
  2. Private Type PROCESS_INFORMATION
  3. hProcess As LongPtr
  4. hThread As LongPtr
  5. dwProcessId As Long
  6. dwThreadId As Long
  7. End Type
  8. #Else
  9. Private Type PROCESS_INFORMATION
  10. hProcess As Long
  11. hThread As Long
  12. dwProcessId As Long
  13. dwThreadId As Long
  14. End Type
  15. #End If

注: LongPtr 在64位系统中为8字节。

2020-11-10 21:39:48    738    0    0

因为蚂蚁笔记官方图片服务器极其不稳定,请直接转到此链接查看:

《探索 PEB》
https://shimo.im/docs/jRwpvxXYPwPw9Cdd/

我太懒了,有机会的话会换博客服务器。

2020-11-09 15:26:44    712    0    0

什么是纤程Fibers)?

用一个类比:线程是进程内部的实体,那么纤程则又是纤程内部的实体。

在《Windows Internals(第六版)》一书中提到:

因为将 CPU 的执行从一个线程切换到另一个线程,将不可避免地涉及内核调度器,所以,这可能是一个开销昂贵的操作,如果两个线程经常频繁地来回切换则尤其如此。 Windows 实现了两种机制来降低这一开销:纤程(fiber)和用户模式调度( UMS , user-mode scheduling )。

纤程使得一个应用程序可以调度它自己的“线程”的执行过程,而不必依赖于 Windows 内置的基于优先级的调度机制。纤程也常被称为“轻量”线程:从调度的角度来看,它们对于内核是不可见的,因为它们是在用户模式下在 Kemel32.dll 中实现的。为了使用纤程,首先要调用 Windows 的 ConvertThreadToFiber 函数。该函数将当前线程转变成一个正在运行的纤程。之后,在转变得到的纤程中,通过调用 CreateFiber 函数,又可以创建额外的纤程(每个纤程可以有它自己的一组纤程)。然而,与线程不同的是,纤程不会自动执行,它必须由 SwitchToFiber 函数手工选中,然后才能执行。新的纤程会一直运行,直到退出,或者调用SwitchToFiber再次选择运行另一个纤程。

总结一下,纤程是执行单元,其必须由应用程序进行手动调度。纤程在对其进行调度的线程的上下文中运行。

本文中,企图通过利用和纤程相关的 Win32 API,来在本地进程中执行 shellcode。绕过一些防御设备对常见进程注入 API 的拦截。

为了使用纤程执行 Shellcode,调用链为:

  1. 将主线程转换为纤程1(这是必需的,因为只有一个纤程才可以创建另一个纤程)。使用 ConvertThreadToFiber 函数。
  2. 将shellcode写入某个内存位置并使其可执行,获取指向此位置的指针。可使用 VirtualAlloc + memcpy 函数。
  3. 创建一个指向 Shellcode 位置的新纤程2——我们将使用在第一步中通过主线程转的纤程1来创建此纤程2。使用 CreateFiber
2020-11-04 21:00:55    755    0    0
  • 因为蚂蚁笔记官方图片服务器极其不稳定,如果本文出现图片加载不出的问题,请转到此链接查看:
    https://shimo.im/docs/8XJKxH9PxqgKp33c/ 《傀儡进程执行 Shellcode 的小坑》,可复制链接后用石墨文档 App 或小程序打开

三好学生在傀儡进程的实现与检测一文里面提到了傀儡进程。但是在那篇文章中,其实是 process hollowing/RunPE 技术。众所周知,所谓进程,就是pe文件的执行在内存中的映射。那么 process hollowing/RunPE 技术就是把一个挂起进程的内存数据清空,然后将一个 PE 文件映射到内存,再将进程的入口点替换为 PE 文件在内存中的起始地址,最后恢复进程状态,执行 PE 文件。

但这种所谓的 process hollowing/RunPE 技术对于 shellcode 注入来说有点过于重量级,因为镂空一个 PE 文件写入 shellcode 动静较大。所谓的镂空就是使用 NtUnmapViewOfSection 卸载正在执行的PE文件在内存中的映像。所以在进程 shellcode 注入的时候,更好地选择可能是不镂空进程,而直接修改进程的 EIP/RIP ,指向 shellcode 在内存中的起始地址。

所以调用链大概是这样的:

  1. 创建挂起进程(使用 CREATE_SUSPENDED标志调用 CreateProcess API);或者使用 SuspendThread 函数挂起目标线程。
  2. VirtualAllocEx函数申请一个可读、可写、可执行的内存。
  3. 调用WriteProcessMemory将Shellcode数据写入刚申请的内存中。
  4. 调用GetThreadContext,设置获取标志为CONTEXT_FULL,即获取新进程中所有线程的上下文。
  5. 修改线程上下文中EIP/RIP的值为申请的内存的首地址,通过SetThreadContext函数设置回主线程中。
  6. 调用ResumeThread恢复主线程。

总之也就是远线程注入+修改远线程的 EIP/RIP。

其中要注意的是,我们要通过GetThreadContext获得所有寄存器的信

2020-10-27 18:42:02    563    0    0

0x01 API 解析

看到了很多关于钩子注入 Hook inject 的文章,但主要是 DLL 注入。本文将尝试实现通过 Hooking 技术在远程进程中注入 Shellcode。

主要使用的是 SetWindowsHookEx 这个 API。

SetWindowsHookEx 用于将应用程序定义的挂钩过程安装到挂钩链中。您将安装一个挂钩过程来监视系统中的某些类型的事件。这些事件与特定线程或与调用线程在同一桌面上的所有线程相关联。

  1. HHOOK SetWindowsHookEx(
  2. int idHook,
  3. HOOKPROC lpfn,
  4. HINSTANCE hmod,
  5. DWORD dwThreadId
  6. );

调用示例:

  1. HMODULE library = LoadLibraryA("dllhook.dll");
  2. HOOKPROC hookProc = (HOOKPROC)GetProcAddress(library, "spotlessExport");
  3. HHOOK hook = SetWindowsHookEx(WH_KEYBOARD, hookProc, library, 0);

可以看到第二个参数 HOOKPROC lpfn 是指向钩子程序的指针。在这里所谓的钩子程序的具体内容是:

  1. BOOL APIENTRY DllMain( HMODULE hModule,
  2. DWORD ul_reason_for_call,
  3. LPVOID lpReserved
  4. )
  5. {
  6. switch (ul_reason_for_call)
  7. {
  8. case DLL_PROCESS_ATTACH:
  9. case DLL_THREAD_ATTACH:
  10. case DLL_THREAD_DETACH:
  11. case DLL_PROCESS_DETACH:
  12. break;
  13. }
  14. return TRUE;
  15. }
  16. extern "C" __declspec(dlle
2020-10-26 19:31:16    666    0    0
  • 因为蚂蚁笔记官方图片服务器极其不稳定,如果本文出现图片加载不出的问题,请转到此链接查看:
    https://shimo.im/docs/rKXY8V9rrYPt3xKK/ 《Bypass DACL 注入进程(二)》

Bypass MIC & DACL 注入进程 一文中,有几个需要注意的地方。

调用链是:
1. OpenProcessToken
2. LookupPrivilegevalue
3. AdjustTokenPrivileges

目的是使用 OpenProcess 使用写权限去打开远程进程句柄,通过对当前进程启用 SeDebugPrivilege 特权绕过远程进程内存保护的限制。

但是启用此特权的前提是,当前进程具有此特权。

普通用户特权:

Title

Administrator 组用户特权:

Title

可以看到,Administrator 组的用户才具备此特权,只是被禁用了。所以我们需要通过调用 AdjustTokenPrivileges API 来启用此特权。

AdjustTokenPrivileges 函数(securitybaseapi.h)
该 AdjustTokenPrivileges 功能启用或禁用特权在指定的访问令牌。启用或禁用访问令牌中的特权需要 TOKEN_ADJUST_PRIVILEGES 访问。

以普通用户运行的 Visual Studio 中执行以下测试代码,发现不启用 SeDebugPrivilege 时,无法打开 pid 为 5192 的进程句柄。

此进程为 Session 0 中的宿迁进程 svchost.exe

尝试启用 SeDebugPrivilege,发现依然无法打开 pid 为 5192 的进程句柄。

这是符合以上我们所说的 token 特权问题的。也就是普通用户根本就无 SeDebugPrivilege 这一特权,所以启用此特权也是无效的。

所以结论是:

Administrator 组成员的 access token 中会含有一些可以执行系统级操作的特权(privileges) ,如终止任意进程、关闭/重启系

2020-10-19 19:48:28    798    0    0

在远程进程注入之 shellcode 注入的时候,常规方案是:

VirtualAllocExWriteProcessMemory

注意这里必须是 VirtualAllocEx 而非 VirtualAlloc,因为 VirtualAlloc 是给调用进程分配内存;而 VirtualAllocEx 才是给另一个进程的地址空间中分配内存。

现在这两个函数都挺敏感的,在下曾经遇到过天擎拦截 WriteProcessMemory。当然我们可以对 API 作精修,比如替换为内核函数,这样可以绕过 uerland hook。就算是 inline hook,也可以使用 syscall 进行绕过。

但其实要完成[在远程进程中分配内存并将shellcode复制进去]这一任务,还可以使用一套API链,也就是所谓的映射注入 File Mapping

我不是故意想搞名词,名词是为了概括,东西并不难,我只是记录一条链,不喜勿喷。

思路一

CreateFileMappingMapViewOfFileMapViewOfFile2

原理见我的亲兄弟idiotc4t的文章 Mapping Injection

CreateFileMapping
创建或打开指定文件的命名或未命名文件映射对象。

MapViewOfFile
将文件映射的视图映射到调用进程的地址空间。

MapViewOfFile2
将文件或页面文件支持的部分的视图映射到指定进程的地址空间。

概括一下注入流程:

  1. 在注入进程/调用进程创建文件映射对象 mapping(使用 CreateFileMapping API)
  2. 将 mapping 映射到注入进程的虚拟地址(使用 CreateFileMapping API)
  3. 往被映射的虚拟地址写入shellcode(memcpy 库函数)
  4. 打开被注入进程句柄
  5. 将 mapping 映射到被注入进程虚拟地址(使用 MapViewOfFile2 API)

代码实现(我把这个过程自己封成了一个函数 MappingShellcodeFile):

  1. #include <windows.h>
  2. #include <stdio.h>
  3. #
2020-10-19 11:01:48    661    0    0
  • 1、 创建进程时候隐藏窗口
  1. //可以在startinfo里面指定
  2. si.wShowWindow=SW_HIDE;
  3. si.dwFlags=STARTF_USESHOWWINDOW

//也可以在createflags里面指定

title

2020-09-22 21:43:33    651    0    0

0x01 逻辑异或/单位异或

异或(XOR)本身是一个 Boolean binary operator

  • 接收两个参数
  • 输入和输出都只能是 true 或 false
  • 结果为 1(true) 或者 0(false)

异或运算到底做了什么?

programmable inverter:一个输入 bit 决定是否去倒置另一个输入 bit,或者仅仅是不加改变的保留。

倒置/翻转规则:

  • 0 XOR 0 = 0
  • 1 XOR 1 = 0
  • 1 XOR 0 = 1
  • 0 XOR 1 = 1

也就是当参数中的一个为真(但不是 both)时输出为真。

0x02 按位异或

上面的异或是操作的单位 bit 或布尔值。当我们处理包含多位(bit)的值时,大多数编程语言提供了对整数“按位异或”(bitwise XOR)的方法。

  1. 将两个整数表示为二进制形式
  2. 对参数对应位进行异或

如:

title

0x03 编程语言中的按位异或

C/C++:

C或C ++中的^(按位XOR)将两个数字用作操作数,并对两个数字的每一位进行XOR。如果两个位不同,则XOR的结果为1。

  1. // C Program to demonstrate use of bitwise operators
  2. #include <stdio.h>
  3. int main()
  4. {
  5. // a = 5(00000101), b = 9(00001001)
  6. unsigned char a = 5, b = 9;
  7. // The result is 00001100
  8. printf("a^b = %d\n", a ^ b);
  9. return 0;
  10. }
  11. //输出:
  12. a ^ b = 12

c#:

  1. 逻辑异或运算符^
  2. 该^操作符计算它的操作数的逻辑异或,也称为逻辑XOR。对于bool操作数,^运算符计算的结果与不等式运算符(!=)相同。
  3. Console.WriteLine(true ^ true); // output: False
  4. Console.WriteLine(true ^ false); // output: True
  5. Console.Write