编程与调试 C++ -- 一些奇奇怪怪的编程知识

ssdeep、tlsh、vhash、mmdthash

  • 准确率 ACC
  • 召回率 REC
  • 精确率 PRE

mmdthash ssdeep vhash tlsh

quic 协议

Quic 全称 quick udp internet connection,“快速 UDP 互联网连接”,(和英文 quick 谐音,简称“快”)是由 google 提出的使用 udp 进行多路并发传输的协议。 一种基于 UDP 的传输协议,用于提升网络加载速度,在弱网环境下表现较好。 Quic 相比现在广泛应用的 http2+tcp+tls 协议有如下优势:

  • 减少了 TCP 三次握手及 TLS 握手时间。
  • 改进的拥塞控制。
  • 避免队头阻塞的多路复用。
  • 连接迁移。
  • 前向冗余纠错。

进程注入

进程注入的方法非常之多,很多与 DLL 注入有关,比如注册表(Image File Execution Options)、DLL 劫持、输入法、COM、LSP 劫持(LayerService Provider,与 winsock 有关)

除了 DLL 注入,还有 shellcode 注入,因为 shellcode 更小,所以 shellcode 的使用也更加多样。

2017 年,在黑客大会上 Eugene Kogan 和 Tal Liberman 又分享了更加隐蔽和特别的方法,比如 Process Doppelganging。

shellcode

#include <windows.h>
#include <stdio.h>

// 内嵌汇编获取 Kernel32 的地址
__declspec(naked) DWORD getKernel32()
{
    __asm
    {
        mov eax,fs:[30h]
        mov eax,[eax+0ch]
        mov eax,[eax+14h]
        mov eax,[eax]
        mov eax,[eax]
        mov eax,[eax+10h]
        ret
    }
}

// 通过 kernel32 基址获取 GetProcAddress 的地址
FARPROC _GetProcAddress(HMODULE hModuleBase)
{
    PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
    PIMAGE_NT_HEADERS32 lpNtHeader = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + lpDosHeader->e_lfanew);
    if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size){
        return NULL;
    }
    if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress) {
        return NULL;
    }
    PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase +
        (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    PDWORD lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames);
    PWORD lpword = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals);
    PDWORD lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions);

    DWORD dwLoop = 0;
    FARPROC pRet = NULL;
    for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++) {
        char* pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase);

        if (pFunName[0] == 'G' &&
            pFunName[1] == 'e' &&
            pFunName[2] == 't' &&
            pFunName[3] == 'P' &&
            pFunName[4] == 'r' &&
            pFunName[5] == 'o' &&
            pFunName[6] == 'c' &&
            pFunName[7] == 'A' &&
            pFunName[8] == 'd' &&
            pFunName[9] == 'd' &&
            pFunName[10] == 'r' &&
            pFunName[11] == 'e' &&
            pFunName[12] == 's' &&
            pFunName[13] == 's') {

            pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hModuleBase);
            break;
        }
    }
    return pRet;
}

int main()
{
    // kernel32.dll 基址的动态获取
    HMODULE hLoadLibrary = LoadLibraryA("kernel32.dll");
    // 使用内嵌汇编来获取基址
    HMODULE _hLoadLibrary = (HMODULE)getKernel32();
    // 效果是一样的
    printf("LoadLibraryA 动态获取的地址: 0x%x\n", hLoadLibrary);
    printf("内嵌汇编获取的地址: 0x%x\n", _hLoadLibrary);

    // 声明定义,先转到到原函数定义,然后重新定义
    typedef FARPROC(WINAPI *FN_GetProcAddress)(
            _In_ HMODULE hModule,
            _In_ LPCSTR lpProcName
        );

    FN_GetProcAddress fn_GetProcAddress;
    fn_GetProcAddress = (FN_GetProcAddress)_GetProcAddress(_hLoadLibrary);

    printf("动态获取 GetProcAddress 地址: 0x%x\n", fn_GetProcAddress);
    printf("内置函数获取: 0x%x\n", GetProcAddress);
    return 0;
}

基于 LSTM 的二进制代码相似性检测

逆向某平台分析过程指导

note 扔到 ida 看导出函数发现是 jni 动态注册没找到 getServerApi() 这个方法,接下来使用 unidbg 进行分析 call_jni_onload 后找到偏移地址 0x12795。

在 ida 中 G 跳转到偏移地址。

查看 sub_4DF0 函数 F5 生成伪代码。

逆向角度看 C++ 部分特性

note

多项式 MBA 原理及其在代码混淆中的应用

Windows API 调用详解

note sysenter 中断门和调用门比较类似,也是一种用来提权的东西。其核心理念在于 Windows 对 intel CPU 的一种利用。

整个调用方式最关键的就是通过 sysenter 从 User 层到达 Kernel 层,可以说前面的都是在给这一步做铺垫。

sysenter 叫做快速系统调用,叫快速是因为之前的系统调用不快,在 Pentium II(奔腾 2 代 CPU)之后才有的 sysenter,在其之前是采用的 KiIntSystemCall 函数来处理的。

// 1:设置寄存器
CS = IA32_SYSENTER_CS
SS = IA32_SYSENTER_CS+8
eip = IA32_SYSENTER_EIP
esp = IA32_SYSENTER_ESP

// 2:切换特权级
切换到 0 环特权级,(其实设置了寄存器就是切换了)

// 3:切换 CPU 模式
清楚 eflags 寄存器中的虚拟 8086 模式(VM 标志)

// 4:执行
执行系统例程调用

查壳工具 ExeinfoPE

反调试相关: IsDebuggerPresent IsProcessorFeaturePresent // 判断处理器相关信息

Windows 内核-句柄

用 Python 去除图片背景:​Rembg 库

Rembg is a tool to remove images background. https://github.com/danielgatis/rembg

智能电表安全之通讯分析

实现 DebugPort 清零

https://bbs.pediy.com/thread-271460.htm DebugPort 是用于调试的一个端口,如果把这个端口清零,常规手段则无法调试我们的进程。

模拟 windwos 系统调用例子,重写 writeprocessmemory 和 readprocessmemory

分别以 int21 和 syscall 两种调用方式实现 代码如下 https://bbs.pediy.com/thread-271471.htm

#include "stdafx.h"
#include <Windows.h>

// 读进程内存(中断门调用)
BOOL WINAPI HbgReadProcessMemory_INT(
                                HANDLE hProcess,
                                LPCVOID lpBaseAddress,
                                LPVOID lpBuffer,
                                DWORD nSize,
                                LPDWORD lpNumberOfBytesRead)
{
    LONG NtStatus;
    __asm
    {
        // 直接模拟 KiIntSystemCall
        lea edx,hProcess; // 要求 edx 存储最后入栈的参数
        mov eax, 0xBA;
        int 0x2E;
        mov NtStatus, eax;
    }
    if (lpNumberOfBytesRead != NULL)
    {
        *lpNumberOfBytesRead = nSize;
    }
    // 错误检查
    if (NtStatus < 0)
    {
        return FALSE;
    }
    return TRUE;
}

// 读进程内存(快速调用)
BOOL WINAPI HbgReadProcessMemory_FAST(
                                HANDLE hProcess,
                                LPCVOID lpBaseAddress,
                                LPVOID lpBuffer,
                                DWORD nSize,
                                LPDWORD lpNumberOfBytesRead)
{

    LONG NtStatus;
    /*
    __asm
    {
        // 模拟 ReadProcessMemory
        lea eax,nSize;
        push eax;
        push nSize;
        push lpBuffer;
        push lpBaseAddress;
        push hProcess;
        sub esp, 0x04; // 模拟 ReadProcessMemory 里的 CALL NtReadVirtualMemory
        // 模拟 NtReadVirtualMemory
        mov eax, 0xBA;
        push NtReadVirtualMemoryReturn; // 模拟 NtReadVirtualMemory 函数里的 CALL [0x7FFE0300]
        // 模拟 KiFastSystemCall
        mov edx, esp;
        _emit 0x0F; // sysenter
        _emit 0x34;
NtReadVirtualMemoryReturn:
        add esp, 0x18; // 模拟 NtReadVirtualMemory 返回到 ReadProcessMemory 时的 RETN 0x14
        mov NtStatus, eax;
    }
    if (lpNumberOfBytesRead != NULL)
    {
        *lpNumberOfBytesRead = nSize;
    }
    */
    __asm
    {
        lea eax,nSize;
        push eax;
        push nSize;
        push lpBuffer;
        push lpBaseAddress;
        push hProcess;
        sub esp, 0x04; // 模拟 ReadProcessMemory 里的 CALL NtReadVirtualMemory
        // 模拟 NtReadVirtualMemory
        mov eax, 0xBA;
        mov edx, 0X7FFE0300 // 不能直接调用内核,间接 call 函数地址来实现
        CALL DWORD PTR[EDX]
        add esp, 0x18;
        mov NtStatus, eax;
    }
    // 错误检查
    if (NtStatus < 0)
    {
        return FALSE;
    }
    return TRUE;
}

// 写进程内存(中断门调用)
BOOL WINAPI HbgWriteProcessMemory_INT(
                                HANDLE hProcess,
                                LPCVOID lpBaseAddress,
                                LPVOID lpBuffer,
                                DWORD nSize,
                                LPDWORD lpNumberOfBytesWritten)
{
    LONG NtStatus;
    __asm
    {
        lea edx, hProcess;
        mov eax, 0x115;
        int 0x2E;
        mov NtStatus, eax;
    }
    if (lpNumberOfBytesWritten != NULL)
    {
        *lpNumberOfBytesWritten = nSize;
    }
    // 错误检查
    if (NtStatus < 0)
    {
        return FALSE;
    }
    return TRUE;
}

// 写进程内存(快速调用)
BOOL WINAPI HbgWriteProcessMemory_FAST(
                                HANDLE hProcess,
                                LPCVOID lpBaseAddress,
                                LPVOID lpBuffer,
                                DWORD nSize,
                                LPDWORD lpNumberOfBytesWritten)
{
    LONG NtStatus;
    __asm
    {
        // 模拟 WriteProcessMemory
        lea eax,nSize;
        push eax;
        push nSize;
        push lpBuffer;
        push lpBaseAddress;
        push hProcess;
        sub esp, 0x04; // 模拟 WriteProcessMemory 里的 CALL NtWriteVirtualMemory
        // 模拟 NtWriteVirtualMemory
        mov eax, 0x115;
/*
push NtWriteVirtualMemoryReturn; // 模拟 NtWriteVirtualMemory 函数里的 CALL [0x7FFE0300]
        // 模拟 KiFastSystemCall
        mov edx, esp;
        _emit 0x0F; // sysenter
        _emit 0x34;
NtWriteVirtualMemoryReturn:
*/
        mov edx, 0X7FFE0300 // 不能直接调用内核,间接 call 函数地址来实现
            CALL DWORD PTR[EDX]
        add esp, 0x18; // 模拟 NtWriteVirtualMemory 返回到 WriteProcessMemory 时的 RETN 0x14
        mov NtStatus, eax;
    }
    if (lpNumberOfBytesWritten != NULL)
    {
        *lpNumberOfBytesWritten = nSize;
    }
    // 错误检查
    if (NtStatus < 0)
    {
        return FALSE;
    }
    return TRUE;
}

void __declspec(naked) MyReadMem(HANDLE hProcess,
                                LPVOID addr,
                                LPVOID buffer,
                                DWORD len,
                                LPDWORD lpNumberOfBytesWritten)
{
    _asm
    {
        mov   eax, 0BAh
        mov   edx, 7FFE0300h
        call  dword ptr[edx]

        ret 0x14
    }
}

BOOL WINAPI HbgReadProcessMemory_FAST2(
                                HANDLE hProcess,
                                LPCVOID lpBaseAddress,
                                LPVOID lpBuffer,
                                DWORD nSize,
                                LPDWORD lpNumberOfBytesRead)
{

    LONG NtStatus;

    __asm
    {
        lea eax, nSize;
        push eax;
        push nSize;
        push lpBuffer;
        push lpBaseAddress;
        push hProcess;
        call MyReadMem
    }
    // 错误检查
    if (NtStatus < 0)
    {
        return FALSE;
    }
    return TRUE;
}

反射式 DLL 注入实现

反射式 DLL 注入实现(ManualMap,手动解析 PE 并映射到目标进程再运行)

一般而言要注入 DLL 到一个目标进程最简单的方法 就是先获取 DLL 文件路径,然后在目标进程分配内存空间将路径写入到目标进程,写入到目标进程后再调用 CreateRemoteThread()/NtCreateThread()/RtlCreateUserThread() 函数来运行 LoadLibraryA/W 函数调用自己的 DLL,这种方法的缺陷也很明显那就是容易被游戏检测到,很容易被游戏拦截,比如 CSGO 最新版就已经有这个限制了。

想要突破 CSGO 的限制注入 DLL 进去,我们可以采用反射式注入的方法(也可以先恢复 CSGOhook 的 api 进行远程线程注入),那么什么是反射式注入呢?又有什么有点呢? 反射式 dll 注入与常规 dll 注入类似,而不同的地方在于反射式 dll 注入技术自己实现了一个 reflective loader() 函数来代替 LoadLibaryA() 函数去加载 dll,示意图如下图所示。蓝色的线表示与用常规 dll 注入相同的步骤,红框中的是 reflective loader() 函数行为,也是下面重点描述的地方。

Reflective loader 实现思路如下:

  1. 获得被注入进程未解析的 dll 的基地址。
  2. 获得必要的 dll 句柄和函数为修复导入表做准备。
  3. 分配一块新内存去取解析 dll,并把 pe 头复制到新内存中和将各节复制到新内存中。
  4. 修复导入表和重定向表。
  5. 执行 DllMain() 函数。

https://bbs.pediy.com/thread-272569.htm https://github.com/MrXiao7/DllInjector https://github.com/Kerrbty/RemoteLoadDll

API Hook 的几种实现

API Hook 的几种实现

  1. 改写函数的首地址。
  2. 改写导入表 IAT(Import Address Table)
  3. 改写虚函数表。简单的虚表 Hook

VEH + 硬件断点实现无痕 HOOK AddVectoredExceptionHandler 设置 VEH 异常捕获

PVOID WINAPI AddVectoredExceptionHandler(
  _In_ ULONG                       FirstHandler,
  _In_ PVECTORED_EXCEPTION_HANDLER VectoredHandler
);

ULONG WINAPI RemoveVectoredExceptionHandler(
  _In_ PVOID Handler
);

PVOID WINAPI AddVectoredContinueHandler(
  _In_ ULONG                       FirstHandler,
  _In_ PVECTORED_EXCEPTION_HANDLER VectoredHandler
);

ULONG WINAPI RemoveVectoredContinueHandler(
  _In_ PVOID Handler
);

注意:由于 CPU 提供的 Dr 寄存器有限,DrxHook 只能下 4 个断点! 注意:由于程序异常会优先被调试器捕获,所以程序必须编译出来后才能生效!

Typora 解密之跳动的二进制

Typora 是一款由 Abner Lee 开发的轻量级 Markdown 编辑器,与其他 Markdown 编辑器不同的是,Typora 没有采用源代码和预览双栏显示的方式,而是采用所见即所得的编辑方式,实现了即时预览的功能,但也可切换至源代码编辑模式。

  • 使用 FindCrypt3 插件 ,搜索一下算法常量吧。

内存破解 简单说几种思路,由于 main.node 是后加载的模块,所以内存破解有些难度。

  • 调试器加载 :参照上述手段,在模块加载通知中断下,定位到解密函数下断,修改内存中的 JS 代码
  • 导出表 HOOK:参考病毒木马使用的进程替换(傀儡进程)技术,创建进程后挂起,由于 main.node 中的 node api 是使用框架中的导出 api,所以可以替换导出函数为自己的函数,在调用时进行参数判断,如果为 JS 代码,则修改
  • DLL 劫持:替换 main.node,由自己加载真正的 main.node 并调用,调用时,定位到解密函数并 hook,等待 JS 代码并修改
  • PE 代码注入 :修改框架的 PE 文件,并加载自己的 DLL,加载后进行导出表 hook

可能遇到的问题:对 main.node 或者框架进行完整性校验,更加强大的反调试手段。

note

Fuzz 学习记录

note 进行软件漏洞挖掘时,通常有静态分析(staticanalysis)、动态分析(dynamicanalysis)、符号执行(symbolicexecution)、模糊测试(fuzzing)这几种技术手段。

模糊测试不需要人过多的参与,也不像动态分析那样要求分析人员有丰富的知识。简单解释,它就是用大量的输入数据自动去执行程序,从而发现哪些输入能够使程序发生异常,进而分析可能存在的漏洞。当前比较成功的 fuzzer(执行模糊测试的程序)有 AFL、libFuzzer、OSS-Fuzz 等。

用 AFL 来示意一个典型的 Fuzz 过程。

kmeans++

从上面的分析可以看出,k-means 是随机的分配 k 个初始聚类中心。而聚类的结果高度依赖质心的初始化。如果初始聚类中心选的不好,k-means 算法最终会收敛到一个局部最优值,而不是全局最优值。为了解决这个问题,引入了 k-means++ 算法,它的基本思想就是:初始的聚类中心之间的相互距离要尽可能的远。而且在计算过程中,我们通常采取的措施是进行不止一次的聚类,每次都初始化不同的中心,以 inertial 最小的聚类结果作为最终聚类结果。

pyclusring 库下的 kmeans 聚类

某达路由器测试


危险函数表

一文读懂对称加密、非对称加密、哈希值、签名、证书、https 之间的关系

到这里,可能你会有两个疑问:

  1. 最顶部的根证书是谁签发的?
  2. 这些根证书是从哪里来的? 答案是:
  3. 根证书自己签发自己,因为根证书的签发机构站在实力的角度被大家所认可
  4. 这些受信任的根证书一般是操作系统或者浏览器自带的

http/https/ssl/tls 协议

http 协议是一个 4 层协议,wireshark 抓到的对百度这个 ip 地址一个个数据包。

note 其实上一节中的 https 协议依旧是简化版本,真正完整的 https 协议是支持双向认证的,即客户端不仅要认证服务器、服务器也要认证客户端。

只不过是平时大家访问的网站都是开门做生意的状态 —— 来的都是客。因此服务器并不会验证客户端的身份。

一文读懂 PE 文件签名并手工验证签名有效性

AuthentiCode 哈希

根据微软的文档,AuthenticCode 的计算需要跳过 CheckSum 字段、SecurityDirectory 字段以及最后的 PKCS#7 格式的证书数据部分。

2022 腾讯游戏安全决赛 wp

note

RawPDB RawPDB is a C++11 library that directly reads Microsoft Program DataBase PDB files. The code is extracted almost directly from the upcoming 2.0 release of Live++.

通过 Ldr 来遍历自身的 dll 模块,然后通过都 PE 文件的解析寻找函数地址,并添加到全局函数表中。

LDR 链调试的方法,这实际上就是一种利用 PEB 关系链获得各个模块基址进而实现遍历其导出表的技术,通过这种技术我们可以轻易的在程序运行中获取到动态加载的 api 的实际地址进而实现各种各样的功能。

fs 寄存器 -> TEB -> PEB -> PEB_LDR_DATA -> LIST_ENTRY -> LDR_DATA_TABLE_ENTRY -> dll_base

Android 调试与反调试详解

dex 脱壳解决方案

  • dex 整体加固:这种方法往往通过动态加载的形式,交换 Application 的执行,一般我们可以通过 hook 方法, 找到 dex_file 的起始地址或大小,进行脱取,也可以通过定制 Room 方法对关键的函数进行插桩, 代表有 fdex2、Frida_Dump
  • 函数抽取:这种方法往往通过将函数代码抽取放入 so 文件中,执行时再从 so 文件读取还原, 我们一般可以通过被动调用延时 Dump 的方法,或主动调用 ArtMethod 中 invoke 函数,触发每一个函数, 然后进行回填,代表有 youpk 和 fart
  • VMP:通过定制的指令集进行解释,这时往往需要手工分析,找到指令的映射表,然后进行一步步解释

Android Vulnerability Mining

《Android APP 漏洞之战系列》

start76.


参考资料快照
参考资料快照

本文短链接:
If you have any questions or feedback, please reach out .