系统安全二进制安全C/C++ [2022]从微软代码中学习C/C++项目代码规范 ### 1.变量用abcd: ```cpp PCONSOLE_READCONSOLE_MSG a = (PCONSOLE_READCONSOLE_MSG)&Message->u.ApiMessageData; ``` 一个项目里面用abc来命名是大忌,因为这会完全让其他人不知道这个变量的意思,一般情况下一个项目的规范命名有两种: > 驼峰 下划线 而不是微软这种 abcd 阅读全文 2022-01-12 huoji 0 条评论
C/C++ [2022]windows官方提升debug权限代码 ```cpp HRESULT EnableDebugPrivilege(void) { OSVERSIONINFO OsVer; OsVer.dwOSVersionInfoSize = sizeof(OsVer); if (!GetVersionEx(&OsVer)) { return WIN32_LAST_STATUS(); } if (OsVer.dwPlatformId != VER_PLATFORM_WIN32_NT) { return S_OK; } #ifdef _WIN32_WCE return E_NOTIMPL; #else HRESULT Status = S_OK; HANDLE Token; PTOKEN_PRIVILEGES NewPrivileges; LUID LuidPrivilege; static s_PrivilegeEnabled = FALSE; if (s_PrivilegeEnabled) { return S_OK; } // // Make sure we have access to adjust and to get the // old token privileges // if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &Token)) { Status = WIN32_LAST_STATUS(); goto EH_Exit; } // // Initialize the privilege adjustment structure // LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &LuidPrivilege); NewPrivileges = (PTOKEN_PRIVILEGES) calloc(1, sizeof(TOKEN_PRIVILEGES) + (1 - ANYSIZE_ARRAY) * sizeof(LUID_AND_ATTRIBUTES)); if (NewPrivileges == NULL) { Status = E_OUTOFMEMORY; goto EH_Token; } NewPrivileges->PrivilegeCount = 1; NewPrivileges->Privileges[0].Luid = LuidPrivilege; NewPrivileges->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // // Enable the privilege // if (!AdjustTokenPrivileges( Token, FALSE, NewPrivileges, 0, NULL, NULL )) { Status = WIN32_LAST_STATUS(); } free(NewPrivileges); EH_Token: CloseHandle(Token); EH_Exit: if (Status == S_OK) { s_PrivilegeEnabled = TRUE; } return Status; #endif // #ifdef _WIN32_WCE } ``` 阅读全文 2022-01-07 huoji 0 条评论
系统安全二进制安全C/C++ [2021]Windows Services Manager 工作原理 Windows Services Manager这个组件的工作原理比我之前想的还诡异.这里简单说一下"服务"是怎么被管理的 R3下如果你不直接操控服务注册表而是用API如OpenServiceA、CreateServiceA这些API后,会调用到RPC,发往RPC服务端 - services.exe ### handle不是真handle 我们都知道 OpenService、CretaeService这些函数在没有错误的时候都会返回一个句柄.在服务端里面是这样实现的: ![](https://key08.com/usr/uploads/2021/12/452801300.png) 服务端申请一个heap内存->创建一个内核对象,交给内核管理->返回ScServiceObject 在使用其他的API比如ChangeServiceConfig、DeleteService这些API的时候,客户端需要提供一个之前OpenService、CretaeService函数返回的句柄,这没什么.但是到了服务端也就是services.exe的时候,这个"句柄"就是一个heap内存地址. ### 微软判断句柄是否有效的"高级"方法 以RDeleteService为例,微软通过一个叫做ScIsValidServiceHandle的函数判断句柄是否有效: ![](https://key08.com/usr/uploads/2021/12/1937175394.png) 实际上这个判断就是判断内存是否可用,内存前几个字节是否等于特定的签名: ![](https://key08.com/usr/uploads/2021/12/3160170665.png) 其中48726573h是一个签名,char后叫做"Hres" 但是网上的openNt的签名不知道是微软故意的还是怎么的,是跟WIN7的不一样的(也可能是年代太久远改了) 网上的: ![](https://key08.com/usr/uploads/2021/12/4151614666.png) 这波很高级 ### 句柄变成结构 在服务端参数传进来的句柄从SC_HANDLE会变成一个叫做LPSC_HANDLE_STRUCT的结构,你可以简单粗暴的这样理解: ```cpp LPSC_HANDLE_STRUCT serviceHandleStruct = (LPSC_HANDLE_STRUCT)hService; ``` 这个"LPSC_HANDLE_STRUCT"长这样: ```cpp typedef struct _SC_HANDLE_STRUCT { DWORD Signature; // For block identification to detect some app errors DWORD Flags; // See definitions above DWORD AccessGranted; // Access granted to client. union { // Object specific data struct { LPWSTR DatabaseName; // Name of database opened } ScManagerObject; struct { LPSERVICE_RECORD ServiceRecord; // Pointer to service record } ScServiceObject; } Type; } SC_HANDLE_STRUCT, * LPSC_HANDLE_STRUCT; ``` 请注意,网上的是错的,网上的OPEN NT结构长这样: https://github.com/Hengle/windows_nt_3_5_source_code/blob/d2894c9125ff1c14028435ed1b21164f6b2b871a/windows_nt_3_5_source_code/NT-782/PRIVATE/WINDOWS/SCREG/SC/SERVER/SCOPEN.H#L52 ```cpp typedef struct _SC_HANDLE_STRUCT{ DWORD Signature; // For block identification to detect some app errors DWORD AccessGranted; // Access granted to client. union { // Object specific data struct { LPWSTR DatabaseName; // Name of database opened } ScManagerObject; struct { LPSERVICE_RECORD ServiceRecord; // Pointer to service record } ScServiceObject; } Type; } SC_HANDLE_STRUCT, *LPSC_HANDLE_STRUCT; ``` 这个是错的。 之后,服务端会通过这个SC_HANDLE_STRUCT结构,检查其中的权限: ![](https://key08.com/usr/uploads/2021/12/729026050.png) 然后从内核删除 ![](https://key08.com/usr/uploads/2021/12/1276980355.png) 而其他的,比如查询config的,则通过服务名寻找到服务注册表项,然后查询之类的(md,到最后还不是查注册表) 阅读全文 2021-12-29 huoji 0 条评论
二进制安全C/C++ [2021]微软的dll搜索源码 from windows XP ```cpp BOOL IsSvcInjected(DWORD dwPid) { HANDLE hProcess; HMODULE hMods[1024]; BOOL res = FALSE; hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwPid); if (hProcess != NULL) { DWORD dwSize; // search through all loaded modules and see if we are injected if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &dwSize)) { DWORD n; for (n = 0; n < (dwSize / sizeof(HMODULE)); n++) { WCHAR wszModName[MAX_PATH]; if (GetModuleBaseNameW(hProcess, hMods[n], wszModName, sizeof(wszModName) / sizeof(WCHAR))) { if (_wcsicmp(wszModName, SBIEDLL L".dll") == 0) { res = TRUE; break; } } } } // if (EnumProcessModules()) CloseHandle(hProcess); } // if (OpenProcess()) return res; } ``` 阅读全文 2021-12-09 huoji 0 条评论
系统安全C/C++一线开发 [2021]转发并且解析windows日志 转发windows安全日志目前有两种方法 第一种,直接修改windows日志存储目录: ![](https://key08.com/usr/uploads/2021/12/3240987865.png) 比较山寨 第二种,利用windows自己提供的API,分为几步: 首先使用EvtOpenChannelEnum打开EVtx列表: ```cpp EVT_HANDLE channels = EvtOpenChannelEnum(NULL, 0); ``` 此时就可以循环读取,直到发生错误: ```cpp if (EvtNextChannelPath(channels, 0, NULL, (PDWORD)&nameLength) || lastErrCode != ERROR_INSUFFICIENT_BUFFER) { break; } ``` 在循环读取中,检查是否你是你要转发的事件: ```cpp if (wcscmp((wchar_t*)&channelName[0], L"Security") != 0) { continue; } ``` 一旦确定后,就可以增加事件订阅: ```cpp EVT_HANDLE channel = EvtSubscribe(NULL, NULL, &channelName[0], L"*", NULL, NULL, &SysEvtCallback, EvtSubscribeToFutureEvents); ``` 在事件订阅回调中,要创建上下文: ```cpp EVT_HANDLE renderCtx = EvtCreateRenderContext(0, NULL, EvtRenderContextSystem); DWORD bufferSizeRequired = 0; DWORD propertyCount = 0; if (EvtRender(renderCtx, pEvent, EvtRenderEventValues, 0, NULL, &bufferSizeRequired, &propertyCount) == TRUE || LastErrCode != ERROR_INSUFFICIENT_BUFFER) { return NULL; } std::vector buffer(bufferSizeRequired); if (EvtRender(renderCtx, pEvent, EvtRenderEventValues, bufferSizeRequired, &buffer[0], &bufferSizeRequired, &propertyCount) != TRUE) { return NULL; } ``` 此时就可以愉快的解析数据了: ```cpp const EVT_VARIANT* properties = reinterpret_cast(&buffer[0]); const uint32_t pid = properties[EvtSystemProcessID].UInt32Val; const wchar_t* computerName = properties[EvtSystemComputer].StringVal; const uint16_t eventID = properties[EvtSystemEventID].UInt16Val; const wchar_t* providerName = properties[EvtSystemProviderName].StringVal; const wchar_t* userID = properties[EvtSystemUserID].StringVal; const uint16_t OpCode = properties[EvtSystemOpcode].ByteVal; ``` 解析日志的buff提供了API并且可以指定类型,类型有很多,我这边使用XML,因为用message格式化会出现中文 ```cpp bufferSizeRequired = 0; EVT_HANDLE metadata = EvtOpenPublisherMetadata(NULL, providerName, NULL, LOCALE_NEUTRAL, 0); if (EvtFormatMessage(metadata, pEvent, 0, 0, NULL, EvtFormatMessageXml, 0, NULL, &bufferSizeRequired) == TRUE) { return 0; } ``` 效果如下 ![](https://key08.com/usr/uploads/2021/12/3222583833.png) 阅读全文 2021-12-06 huoji 0 条评论
系统安全工具软件二进制安全 [2021]AppDomainManager劫持与powershell hook解码 在红队渗透中,有一种持久化技术经常被使用,那就是AppDomainManager劫持,具体原理本章不再叙述,简单来说,在.net目录新建一个同样文件的.config文件,就可以控制.net的appdomainmanger加载,攻击者往往利用这个特性做后门 ```asp ``` 在安全产品对抗中,安全产品时刻面临着powershell的挑战原因很简单,powershell这玩意,简直是小黑和apt组织狂喜,各种花里胡哨的加密与解密去绕过edr/av,并且微软的amsi接口也各种拉胯,甚至是可以被powershell自己绕过,所以就不能指望微软了,要自己干 -powershell的hook ### 如何获取干净的powershell指令 powershell本质上是基于.net的解释器,在powershell脚本代码执行后,会被编译成il代码继续执行,因此为了解决powershell被混淆的问题,我们需要想办法hook powershell的编译代码,好消息是微软的.net相当于开源,所以很快就能找到这个函数: ```asp System.Management.Automation.CompiledScriptBlockData ``` ![](https://key08.com/usr/uploads/2021/11/1354091260.png) 顺便吐槽一下微软的amsi: ![](https://key08.com/usr/uploads/2021/11/2089727795.png) ### 如何hook 在我们劫持了AppDomainManager启动后,我们需要干的第一件事就是监听程序集加载: ```cpp private const BindingFlags anyType = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly AssemblyLoadEventHandler HookAssemblyLoadEventHandler = new AssemblyLoadEventHandler(Rm_OnAssemblyLoad); private static void Rm_OnAssemblyLoad(object sender, AssemblyLoadEventArgs args) { string assemblyName = args.LoadedAssembly.GetName().Name; string assemblyFullName = args.LoadedAssembly.Location; if (assemblyName == "System.Management.Automation") { ...这里就是程序集加载事件了 ``` 之后,我们要得到此函数的指针 ![](https://key08.com/usr/uploads/2021/11/3051046094.png) ```cpp MethodInfo ReallyCompile = targetMethodClass.GetMethod("ReallyCompile", anyType, null, targetMethodType, null); ... RuntimeHelpers.PrepareMethod(ReallyCompile.MethodHandle); ... IntPtr TargetAddress = ReallyCompile.MethodHandle.GetFunctionPointer(); ``` 然后hook他 ```cpp if (!MinHook.InstallHook(TargetAddress, HookAddress, TrampolineAddress)) { throw new ArgumentNullException("[HUOJI] Min Hook Fail"); //仅供测试,一般要return } ``` 至此,我们就hook了powershell并且拿到了正确信息: ```cpp public void HookReallyCompile(bool optimize) { string Code = this.ToString(); Helper.DbgPrint(Code); .... ``` ![](https://key08.com/usr/uploads/2021/11/1201952192.png) 阅读全文 2021-11-22 huoji 0 条评论