上一篇從特徵碼開始下手,這次從異動 nuitka 的 compiler 來下手

這篇網站在以前的時候就有嘗試去呼叫ollvm 去加密 binary https://blog.threat.zone/create-more-complicated-binaries-with-nuitka-llvm-obfuscator/

那麼我們來透過 ollvm 去混淆 一些東西看能不能順便透過 Nuitka 進行編譯 目前 msys2 好像都沒看到人去編譯過,來移植一下

code
pacman -S --needed git wget mingw-w64-x86_64-gcc mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake make mingw-w64-x86_64-python3
git config --global user.name "Your Name"
git config --global user.email "your-email@example.com"
git config --global http.postBuffer 524288000
git config --global http.lowSpeedLimit 0
git config --global http.lowSpeedTime 999999
code
nuitka version 2.4.7 基本上編譯出來都會被一些知名防毒軟體偵測
$ gcc -v
Using built-in specs.
COLLECT_GCC=D:\res\msys64\msys64\mingw64\bin\gcc.exe
COLLECT_LTO_WRAPPER=D:/res/msys64/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: ../gcc-13.2.0/configure --prefix=/mingw64 --with-local-prefix=/mingw64/local --build=x86_64-w64-mingw32 --host=x86_64-w64-mingw32 --target=x86_64-w6
4-mingw32 --with-native-system-header-dir=/mingw64/include --libexecdir=/mingw64/lib --enable-bootstrap --enable-checking=release --with-arch=nocona --with-tune=gene
ric --enable-languages=c,lto,c++,fortran,ada,objc,obj-c++,jit --enable-shared --enable-static --enable-libatomic --enable-threads=posix --enable-graphite --enable-fu
lly-dynamic-string --enable-libstdcxx-filesystem-ts --enable-libstdcxx-time --disable-libstdcxx-pch --enable-lto --enable-libgomp --disable-libssp --disable-multilib
 --disable-rpath --disable-win32-registry --disable-nls --disable-werror --disable-symvers --with-libiconv --with-system-zlib --with-gmp=/mingw64 --with-mpfr=/mingw6
4 --with-mpc=/mingw64 --with-isl=/mingw64 --with-pkgversion='Rev3, Built by MSYS2 project' --with-bugurl=https://github.com/msys2/MINGW-packages/issues --with-gnu-as
 --with-gnu-ld --disable-libstdcxx-debug --with-boot-ldflags=-static-libstdc++ --with-stage1-ldflags=-static-libstdc++
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 13.2.0 (Rev3, Built by MSYS2 project)

download llvm

code
export PATH=/mingw64/bin:$PATH
git clone -b llvmorg-17.0.6 https://github.com/llvm/llvm-project.git
cd llvm-project
mkdir build 
cd build 

cmake -G "MinGW Makefiles" \
  -DCMAKE_BUILD_TYPE=Release \
  -DLLVM_ENABLE_RTTI=OFF \
  -DLLVM_ENABLE_EH=OFF \
  -DLLVM_INCLUDE_TESTS=OFF \
  -DLLVM_BUILD_TOOLS=OFF \
  -DLLVM_BUILD_LLVM_DYLIB=OFF \
  -DLLVM_ENABLE_ASSERTIONS=OFF \
  -DLLVM_ENABLE_PROJECTS="clang;lld" \
  -DCMAKE_C_FLAGS="-D__INTRINSIC_SETJMPEX__ /utf-8" \
  -DCMAKE_CXX_FLAGS="-D__INTRINSIC_SETJMPEX__ /utf-8" \
  ../llvm

這邊要編譯過程中可能會遇到一些權限的問題 chmod -R u+w ~/llvm-project/build

ollvm

code
git clone https://github.com/DreamSoule/ollvm17

接下來你要把 LLVM-obfuscate 相關的 pass移動到 llvm-project 下面已經把一些編譯的bug 進行修正,一些新版的compiler 語法請chatgpt 進行修正了

注意過程中有些source code發生異動 \llvm-project\llvm\lib\Passes\Obfuscation\Utils.cpp

code
static bool valueEscapes(const Instruction &I) {
    for (const Use &U : I.uses()) {
        const User *Usr = U.getUser();
        // 检查用户是否是指令,且是否在同一个基本块
        if (const Instruction *Inst = dyn_cast<Instruction>(Usr)) {
            if (Inst->getParent() != I.getParent())
                return true; // 逃逸到其他基本块
        } else {
            return true; // 逃逸到非指令的用户
        }
    }
    return false;
}

/** LLVM\llvm\lib\Transforms\Scalar\Reg2Mem.cpp
 * @brief 修复PHI指令和逃逸变量
 *
 * @param F
 */
void llvm::fixStack(Function &F) {
  // Insert all new allocas into entry block.
  BasicBlock *BBEntry = &F.getEntryBlock();
  assert(pred_empty(BBEntry) &&
         "Entry block to function must not have predecessors!");

  // Find first non-alloca instruction and create insertion point. This is
  // safe if block is well-formed: it always have terminator, otherwise
  // we'll get and assertion.
  BasicBlock::iterator I = BBEntry->begin();
  while (isa<AllocaInst>(I))
    ++I;

  CastInst *AllocaInsertionPoint = new BitCastInst(
      Constant::getNullValue(Type::getInt32Ty(F.getContext())),
      Type::getInt32Ty(F.getContext()), "fix_stack_point", &*I);

  // Find the escaped instructions. But don't create stack slots for
  // allocas in entry block.
  std::list<Instruction *> WorkList;
    for (Instruction &I : instructions(F)) {
    if (!(isa<AllocaInst>(I) && I.getParent() == BBEntry) && valueEscapes(I)) {
        WorkList.push_front(&I);
    }
}
  // Demote escaped instructions
  //NumRegsDemoted += WorkList.size();
  for (Instruction *I : WorkList)
    DemoteRegToStack(*I, false, AllocaInsertionPoint);

  WorkList.clear();

  // Find all phi's
  for (BasicBlock &BB : F)
    for (auto &Phi : BB.phis())
      WorkList.push_front(&Phi);

  // Demote phi nodes
  //NumPhisDemoted += WorkList.size();
  for (Instruction *I : WorkList)
    DemotePHIToStack(cast<PHINode>(I), AllocaInsertionPoint);
}

\llvm-project\llvm\lib\Passes\Obfuscation\IPObfuscationContext.cpp

code
        NF->getBasicBlockList().splice(NF->begin(), F->getBasicBlockList());
換成
        NF->splice(NF->begin(), F);

\llvm-project\llvm\lib\Support\CrashRecoveryContext.cpp

code
#include "llvm/Support/CrashRecoveryContext.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/ExitCodes.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/thread.h"
#include <cassert>
#include <mutex>
#include <setjmp.h>

using namespace llvm;
#if defined(__MINGW32__)
extern "C" int __intrinsic_setjmpex(_JBTYPE* buf, void* ctx) {
    // 提供一个空实现,直接返回 0
    return 0;
}
#endif

build

code
mingw32-make -j$(nproc)

大概就可以順利編譯完成了,接下來要改 nuitka的 source 規避 一些compiler 額外印出來的資訊導致異常拋出錯誤

rewrite nuitka source code

C:\nuitka_cache\SITE_PACKAGE\nuitka\build\SconsCaching.py 這邊我強迫替換成我們剛剛編譯完畢的 clang 位置,不然 Nuitka會動態去抓

code
def checkWindowsCompilerFound(
    env, target_arch, clang_mode, msvc_version, assume_yes_for_downloads
):
    """Remove compiler of wrong arch or too old gcc and replace with downloaded winlibs gcc."""
    # Many cases to deal with, pylint: disable=too-many-branches,too-many-statements

    if os.name == "nt":
        # On Windows, in case MSVC was not found and not previously forced, use the
        # winlibs MinGW64 as a download, and use it as a fallback.
        compiler_path = getExecutablePath(env["CC"], env=env)
        print(compiler_path)


        env["CC"] = f"D:\\res\\msys64\\msys64\\home\\rex603\\llvm-project\\build3\\bin\\clang.exe"
        print(env["CC"])

        #if env["CC"] is None:
        #    raiseNoCompilerFoundErrorExit()

    return env

image

C:\nuitka_cache\SITE_PACKAGE\nuitka\build\SconsSpawn.py,在編譯過成因為 ollvm 有會額外印出用那些pass的資訊 這些資訊會導致 nuitka 誤以為是編譯錯誤,對他而言,沒消息就是好消息

code
      if data is not None and data.rstrip():
            my_print("Unexpected output from this command:", style="scons-unexpected")
            my_print(cmdline, style="scons-unexpected")

            if str is not bytes:
                data = decodeData(data)

            my_print(
                data, style="scons-unexpected", end="" if data.endswith("\n") else "\n"
            )
            print(data)
            # reportSconsUnexpectedOutput(env, cmdline, stdout=data, stderr=None)
#####################註解我

image

ok從這些scons 編譯的 script 來判斷 判斷有三個地方要進行 ollvm 混淆 image C:\nuitka_cache\SITE_PACKAGE\nuitka\build\Backend.scons C:\nuitka_cache\SITE_PACKAGE\nuitka\build\CCompilerVersion.scons C:\nuitka_cache\SITE_PACKAGE\nuitka\build\Onefile.scons 就實驗而言基本上我們只要對 onefile.scons 這個 shellcode 去混淆加密,有嘗試對 Backend source code 加密反而多更多時間,還被檢測到更多的病毒

code
env.Append(
    CCFLAGS=[
        "-mllvm", "-sobf",  # String Obfuscation
        "-mllvm", "-igv",    # Indirect Global Variable Obfuscation
        "-mllvm", "-sub",    # Substitution Obfuscation
        "-mllvm", "-bcf",
    ]
)
code
python -m nuitka test.py --clang --onefile --standalone

基本上一些知名的掃毒應該都可以略過了 https://www.virustotal.com/gui/file/e719c2124e2dadfb081ec6574a2416a556be452601f280469d78c6a96b8902c1

image

收工!