Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
zyrox — Zyrox: LLVM 기반의 컴파일 타임 난독화 플러그인. | Kitploit
도구/GitHubGitHub/peterhackz/zyrox
Static AnalysisCode AnalysisReverse EngineeringBinary AnalysisPapers & ResearchLearning & EducationLearning Paths & Courses
GitHubpeterhackz/zyrox

zyrox

Zyrox: LLVM 기반의 컴파일 타임 난독화 플러그인.

저장소 보기
94116개월 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
웹사이트

Zyrox LLVM 난독화 도구

네이티브 코드를 난독화하는 llvm 컴파일 및 링크 타임 플러그인

왜

why not ¯\_(ツ)_/¯

제 가장 큰 프로젝트 중 하나로, LLVM 내부, 바이너리 포맷, 어셈블리, 난독화 기법에 대해 많이 배운 프로젝트입니다.

무언가를 만들면서 배우는 것이 가장 좋은 학습 방법이라고 믿기 때문에, 이 프로젝트를 통해 이 주제들을 더 깊게 배우고자 만들었습니다.

연구

저는 Zyrox의 개념을 설명하는 블로그 4개를 작성했습니다:

  • Part I: Zyrox 빌드하기: 커스텀 LLVM 난독화 도구
  • Part II: 제어 흐름 평탄화
  • Part III: 암호화된 점프 테이블
  • Part IV: 피날레

이 글들은 이 README보다 더 깊이 다루며, 주제에 관심이 있다면 꼭 읽어볼 가치가 있습니다.

빌드

템플릿 사용 (빠른 시작, 권장)

Zyrox를 _빠르게 테스트_하거나 CMake 프로젝트에 통합하는 방법을 배우려는 사람을 위한 것입니다.

Zyrox Template 저장소의 단계를 따르세요.

소스에서 빌드

llvm 설치:

root@kitploit:~
sudo apt update
sudo apt install llvm-18 llvm-18-dev clang-18

zyrox 클론 및 컴파일:

root@kitploit:~
git clone --recurse-submodules https://github.com/PeterHackz/zyrox.git
cd zyrox
cmake -S . -B build -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++
cmake --build build --parallel 4

Python (컴파일 후) 플러그인 설정

python3와 pip가 설치되어 있는지 확인하세요.

환경 설정 (권장)

root@kitploit:~
# Create a virtual environment
python3 -m venv .venv

# Activate the env
source .venv/bin/activate

pip install -r requirements.txt

전역 설치

root@kitploit:~
pip install -r requirements.txt

사용법

빠른 사용법

root@kitploit:~
clang -O0 -flto=full -c main.c -o out/main.o
clang -flto=full -fuse-ld=lld -Wl,--load-pass-plugin=./build/libzyrox.so out/main.o -o out/main

난독화 후 PyPlugin.py를 실행하여 점프 테이블을 암호화하세요:

root@kitploit:~
# if you installed dependencies in a virtual environment, activate it first:
source .venv/bin/activate
#  then run with:
python PyPlugin.py --in=<input_file> [--out=<output_file>] [--tables=<zyrox_tables_file>] [--android]

CMake와 함께 사용하기

CMake 통합 예제는 Zyrox Template 저장소를 확인하세요.

연락처

이 주제가 복잡하다는 점을 알고 있습니다. 이 프로젝트는 주로 교육 목적으로 만들어졌으며, BSD Brawl을 지원하기 위해서도 만들어졌습니다.

질문이 있거나 그냥 대화하고 싶다면 언제든지 연락하세요:

  • Discord: @s.b
  • Email: [email protected] 또는 [email protected]
  • Discord 서버

풀 리퀘스트나 이슈를 통한 모든 도움에 감사드립니다!

동작 방식

ZyroxPlugin.cpp는 패스를 등록한 다음 siphash를 링크하고(자세한 내용은 나중에) StringEncryption을 호출하여 문자열을 암호화합니다.

문자열을 초기에 암호화하는 이유는 나중에 복호화 로직도 함께 난독화되도록 하기 위해서입니다.

그런 다음 ModuleUtils::ExpandCustomAnnotations와 QuickConfig::RegisterPasses를 호출하여 모든 __attribute__((annotate("..."))) 표현식을 파싱하고 QuickJs 구성을 실행합니다 (ZyroxConfig.js에 위치).

모든 함수는 ZyroxCore.cpp에 있는 Zyrox::RunOnFunction을 호출하여 난독화됩니다. 이에 대한 자세한 문서는 추후 제공될 예정입니다.

추가 유틸

스위치는 점프 테이블을 만들고 PHI 노드는 다루기 까다롭기 때문에 FunctionUtils와 BasicBlockUtils를 사용하여 각각 (if 문으로) 평탄화하고 강등합니다.

패스

아이고, 어디서부터 시작해야 할지

  • 기본 블록 분할기
  • 제어 흐름 평탄화
  • 간접 분기
  • 단순 간접 분기
  • 혼합 부울 산술

모든 js 플러그인 인자는 index.d.ts에 있으므로 이 문서에서는 다루지 않습니다.

어노테이션 문서는 여기를 클릭하세요.

기본 블록 분할기

이 패스는 기본 블록을 더 작은 블록들로 분할하고 섞습니다. 다음과 같은 코드가 있다고 가정해 봅시다:

root@kitploit:~
int __test_fn(int x)
{
    if (x == 2) {
        printf("x is 2\n");
    } else {
        printf("x is not 2!, x is: %d\n", x);
    }
    return x + 4 * x - 2 / 4;
}

다음과 같이 컴파일됩니다:

root@kitploit:~
define internal i32 @__test_fn(i32 noundef %0) #0 !zyrox !8 !obfuscated !11 {
  %2 = alloca i32, align 4
  store i32 %0, ptr %2, align 4
  %3 = load i32, ptr %2, align 4
  %4 = icmp eq i32 %3, 2
  br i1 %4, label %5, label %7

5:                                                ; preds = %1
  %6 = call i32 (ptr, ...) @printf(ptr noundef @.str.1)
  br label %10

7:                                                ; preds = %1
  %8 = load i32, ptr %2, align 4
  %9 = call i32 (ptr, ...) @printf(ptr noundef @.str.2, i32 noundef %8)
  br label %10

10:                                               ; preds = %7, %5
  %11 = load i32, ptr %2, align 4
  %12 = load i32, ptr %2, align 4
  %13 = mul nsw i32 4, %12
  %14 = add nsw i32 %11, %13
  %15 = sub nsw i32 %14, 0
  ret i32 %15
}

Basic Block Splitter를 다음 구성으로 사용하면:

root@kitploit:~
z.RegisterPass(ObfuscationType.BasicBlockSplitter, {
    PassIterations: 1,
    "BasicBlockSplitter.SplitBlockChance": 100,
    "BasicBlockSplitter.SplitBlockMinSize": 2,
    "BasicBlockSplitter.SplitBlockMaxSize": 5,
});

다음과 같이 변합니다:

root@kitploit:~
define internal i32 @__test_fn(i32 noundef %0) #0 !zyrox !8 !obfuscated !11 {
  %2 = alloca i32, align 4
  store i32 %0, ptr %2, align 4
  %3 = load i32, ptr %2, align 4
  %4 = icmp eq i32 %3, 2
  br i1 %4, label %5, label %14

5:                                                ; preds = %1
  %6 = call i32 (ptr, ...) @printf(ptr noundef @.str.1)
  br label %7

7:                                                ; preds = %14, %5
  %8 = load i32, ptr %2, align 4
  %9 = load i32, ptr %2, align 4
  %10 = mul nsw i32 4, %9
  %11 = add nsw i32 %8, %10
  br label %12

12:                                               ; preds = %7
  %13 = sub nsw i32 %11, 0
  ret i32 %13

14:                                               ; preds = %1
  %15 = load i32, ptr %2, align 4
  %16 = call i32 (ptr, ...) @printf(ptr noundef @.str.2, i32 noundef %15)
  br label %7
}

이런 작은 함수의 경우 크게 달라 보이지 않지만, 기본 블록을 분할하는 것을 볼 수 있습니다. 이는 제어 흐름 평탄화 같은 다른 패스와 결합할 때 유용합니다.

제어 흐름 평탄화

아이고, 이 패스는 모든 패스 중 기능이 제일 많습니다 ㅋㅋ.

먼저 동작 방식을 설명한 다음 구성을 설명하겠습니다. 다음과 같은 코드가 있다고 가정해 봅시다:

root@kitploit:~
LABEL_A: bool b = x == 2;
         IF EQ: goto LABEL_B
         goto LABEL_C
LABEL_B  do_stuff()
LABEL_C  do_other_stuff()
         goto LABEL_A

각 기본 블록(A, B, C)에는 고유한 디스패처 상태가 할당됩니다. 예: (단순화됨)

root@kitploit:~
states = {
    1: LABEL_A,
    2: LABEL_B,
    3: LABEL_C,
};

그런 다음 모든 것을 제어하는 디스패처 블록을 주입하여 코드가 다음과 같이 됩니다:

root@kitploit:~
         int state = 0;
LABEL_D  goto LABEL_CA // dispatcher label jumps to first condition block, label condition A
LABEL_CA if state == 1: goto LABEL_A
         // if not 1, go to check if it is label B (fallback)
LABEL_CB if state == 2: goto LABEL_B
LABEL_CC if state == 3: goto LABEL_CC
         // unreachable
         goto LABEL_D
LABEL_A: bool b = x == 2;
         // IF EQ: goto LABEL_B
         // goto LABEL_C
         state = 2 if b else 3 // update state for the block we want and back to dispatcher
         goto LABEL_D
LABEL_B  do_stuff()
LABEL_C  do_other_stuff()
         state = 1
         goto LABEL_D

이 방식에는 난독화 도구가 해결하는 몇 가지 결함이 있습니다. 보다시피 디스패처 변수가 하나뿐이므로 블록이 상태를 설정한 후 어디로 가는지 알 수 있어 쉽게 비난독화할 수 있습니다. 고치기 쉽습니다!

root@kitploit:~
z.RegisterPass(ObfuscationType.ControlFlowFlattening, {
    PassIterations: 1,
    "ControlFlowFlattening.UseFunctionResolverChance": 60,
    "ControlFlowFlattening.UseGlobalStateVariablesChance": 60,
    "ControlFlowFlattening.UseOpaqueTransformationChance": 40,
    "ControlFlowFlattening.UseGlobalVariableOpaquesChance": 80,
    "ControlFlowFlattening.UseSipHashedStateChance": 40,
    "ControlFlowFlattening.CloneSipHashChance": 80,
});

옵션을 하나씩 살펴보겠습니다:

  • UseFunctionResolverChance: 상태를 확인하는 함수를 주입합니다. 따라서 if (state == expected_state) 대신 if (injected_resolver(state))를 수행합니다. 예:
    root@kitploit:~
    bool __fastcall cff_resolve_state_check_3585(__int64 a1)
    {
        return a1 == 0x288A6154F8A5E3E2LL;
    }
    
  • UseGlobalStateVariablesChance: 비교할 상태 값을 전역 변수에 저장합니다:
    root@kitploit:~
    bool __fastcall cff_resolve_state_check_506(__int64 a1)
    {
        return a1 == qword_1B20D8;
    }
    
  • UseOpaqueTransformationChance: 특정 상태에서만 true가 되는 변환으로 검사를 난독화합니다:
    root@kitploit:~
    bool __fastcall cff_resolve_state_check_7901(__int64 a1)
    {
        return ((((a1 ^ 0xEA9E45BB6099BC6ELL) + qword_1C64D8) << qword_1A63F0)
                   | (((a1 ^ 0xEA9E45BB6099BC6ELL)
                       + qword_1C64D8) >> qword_1ACE98)) == qword_1B0B80;
    }
    
  • UseGlobalVariableOpaquesChance: 를 사용할 때 숫자 대신 전역 변수를 사용합니다. 위 예제에서 볼 수 있듯이 (, , )

간접 분기

다음과 같은 코드가 있다고 가정해 봅시다:

root@kitploit:~
if (x == 2) goto LABEL_A
            goto LABEL_B
LABEL_A:    do_stuff()
LABEL_B:    // ...

다음과 같이 변환됩니다:

root@kitploit:~
@global jump_table = {0, &LABEL_A, &LABEL_B};
if (x == 2) goto jump_table[0] + @inline(decrypt(jump_table[1]));
            goto jump_table[0] + @inline(decrypt(jump_table[2]));
// ...

이 패스를 사용하면 플러그인은 PyPlugin.py에서 사용할 zyrox_tables.txt 파일을 출력합니다.

PyPlugin.py는 점프 테이블을 암호화하고 재배치 항목을 패치한 다음, 릴로케이터가 모든 테이블의 jump_table[0]을 가리키도록 만듭니다. 릴로케이터는 기본적으로 target.writePointer(base.add(value))를 수행합니다. 따라서 value를 0으로 설정하면 릴로케이터가 기본 주소를 제공하고 런타임에 점프 테이블에 넣도록 하며, 이를 goto와 함께 사용하여 런타임 주소를 생성합니다. arm32 thumb 모드에서는 패스가 복호화 후 자동으로 | 1을 추가합니다.

PyPlugin.py를 사용하려면 다음을 수행하면 됩니다:

(venv를 사용하는 경우 먼저 활성화하세요)

root@kitploit:~
python3 PyPlugin.py --in <out_obfuscated_file> --android

arm64 버전을 대상으로 하는 경우 --android를 전달하는 것이 중요합니다. x86_64 버전은 다른 릴로케이터 시그니처를 가지기 때문입니다.

--out을 전달할 수도 있습니다(기본적으로 --in에 전달된 것과 같은 파일을 사용합니다). 또한 --tables를 전달할 수 있습니다(기본값은 zyrox_tables.txt).

단순 간접 분기

간접 분기는 훌륭해 보이지만 런타임에 포인터를 복호화하므로 성능 저하가 있습니다. 이는 성능에 영향을 주지 않는 단순한 버전으로, 다음과 같은 코드가:

root@kitploit:~
if (x == 2) goto LABEL_A
            goto LABEL_B
LABEL_A:    do_stuff()
LABEL_B:    // ...

다음과 같이 변합니다:

root@kitploit:~
            @stack jump_table = {&LABEL_B, &LABEL_A}
            goto jump_table[!(x == 2)]
LABEL_A:    do_stuff()
LABEL_B:    // ...

이 방식은 단순하고 쉽게 깨질 수 있어 보이지만(저도 동의합니다), 성능에 영향을 주지 않으면서 IDA와 Ghidra를 충분히 방해할 수 있습니다.

혼합 부울 산술

MBA Sub(Mixed Boolean Arithmetic Substitution)라고도 알려져 있으며, 동일한 출력을 내는 복잡한 연산으로 단순 연산을 변환합니다. 미리 정의된 집합을 사용합니다. 예:

root@kitploit:~
a ^ b = (~a & b) | (a & ~b)
b * c = (((b | c) * (b & c)) + ((b & ~c) * (c &  ~b)))
r = rand(); c = b + r; a = a + c; a = a - r

관심이 있다면 전체 목록을 Passes/MBASub.cpp에서 확인할 수 있습니다.

Zyrox 어노테이션

그냥 index.d.ts를 확인하세요. 어노테이션 파서는 같은 순서를 사용합니다.

함수를 표시하려면 다음과 같이 하면 됩니다:

root@kitploit:~
__attribute__((annotate("ibr:1,100"))) void hello_world () {
    some_hello ();
}

어노테이션 코드:

  • 기본 블록 분할기: bbs
  • 제어 흐름 평탄화: cff
  • 간접 분기: ibr
  • 단순 간접 분기: sibr
  • 혼합 부울 산술: mba

예: index.d.ts에서 다음을 볼 수 있습니다:

root@kitploit:~
{
    "BasicBlockSplitter.SplitBlockMinSize"?: number;
    "BasicBlockSplitter.SplitBlockMaxSize"?: number;
    "BasicBlockSplitter.SplitBlockChance"?: number;
};

여기서 중요한 점은 첫 번째 인자이자 모든 패스에 공통인 인자가 PassIterations라는 것이므로, 어노테이션에서 첫 번째 인자가 됩니다.

bbs로 무언가를 어노테이션하려면 다음과 같이 합니다:

root@kitploit:~
__attribute__((annotate("bbs:1,15,30,100"))) void hello_world () {
    some_hello ();
}

이것은 hello_world에 기본 블록 분할기를 1회 실행하고 min size = 15, max size = 30, chance = 100으로 실행한다는 뜻입니다.

패스를 결합할 수도 있습니다:

root@kitploit:~
__attribute__((annotate("bbs:1,15,30,100 ibr:1,100 sibr:1,100"))) void hello_world () {
    some_hello ();
}

이것은 hello_world에 기본 블록 분할기를 실행한 다음 간접 분기, 그리고 그 다음 단순 간접 분기를 실행한다는 뜻입니다. 정의된 순서대로 왼쪽에서 오른쪽으로 실행됩니다.

도구 다운로드
UseOpaqueTransformationChance
qword_1C64D8
qword_1A63F0
qword_1ACE98
  • UseSipHashedStateChance: 상태를 확인하기 위해 작고 커스터마이즈된 siphash 함수를 사용합니다. 따라서 if (state == 23872)는 if (siphash(state) == 3874872081) 같은 형태가 되어 블록이 어디로 점프하는지 찾기 어렵게 만듭니다. 블록은 이동할 조건이 해시된 경우 state = 23872를 수행합니다. 각 siphash 호출은 무작위 값을 사용하여 에뮬레이션을 더 어렵게 만듭니다.
  • CloneSipHashChance: 가능할 때 siphash 함수를 복제하고 심지어 인라인하여 하나 이상의 형제 함수를 만들어 단일 함수를 후킹하는 것만으로는 부족하게 만듭니다. 이 옵션은 바이너리 크기만 증가시키고 성능에는 영향을 주지 않으므로 사용하는 것이 매우 권장됩니다.