2021-02-05 19:02:33    1286    0    0
  1. MSVCRT.lib(exe_main.obj) : error LNK2001: 无法解析的外部符号 main

遇到这个问题的排错思路是:

  • main 函数是否拼写正确?
  • main 对应 属性配置属性C/C++预处理器预处理器定义中的 _CONSOLE。以及属性配置属性链接器系统子系统中的 控制台(/SUBSYSTEM:CONSOLE),而不要错选成 WINDOWS

但在这两个前提下,依然报这个错误。那么就要考虑到是否真的编译出 main 函数了。

原来是因为最开始在添加包含 main 函数的源代码文件时候,错选成了 .h 头文件,后面虽然改成了 .cpp 文件,但实质上在解决方案中此文件并未参与编译。于是把此文件从项目中排除然后重新添加现有项就可以了。

注意要点
最后稍微列一下C++包含头文件的顺序,同样来源于上一个引用链接。

要注意的是一些头文件也有依赖关 系,这些文件的包含顺序也小心,否则就会出错。ps,头文件的包含顺序应该是从最特殊到一般,比如:我们应该以这样的方式来#include头文件:
从最特殊到最一般,也就是:

  1. #include "本类头文件"
  2. #include "本目录头文件"
  3. #include "自己写的工具头文件"
  4. #include "第三方头文件"
  5. #include "平台相关头文件"
  6. #include "C++库头文件"
  7. #include "C库头文件"
2020-11-25 18:39:44    933    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    761    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    774    0    0

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

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

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

2020-11-09 15:26:44    905    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    870    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    603    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    704    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    895    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    707    0    0
  • 1、 创建进程时候隐藏窗口
  1. //可以在startinfo里面指定
  2. si.wShowWindow=SW_HIDE;
  3. si.dwFlags=STARTF_USESHOWWINDOW

//也可以在createflags里面指定

title