Tigress Virtualize Analysis - LLVM IR

안상준·2025년 11월 28일

Virtualize Deobfuscator

목록 보기
12/14

이번에는 가상화 난독화를 적용한 C 코드를 LLVM IR로 컴파일 하여 LLVM IR 코드를 분석해 보았다. 세가지 디스패치 옵션 (switch, direct, indirect) 를 적용한 코드를 분석하였고, 원본 코드는 bubble 정렬 알고리즘을 대상으로 하였다.

C Code

#include <stdio.h>

int main(void){
        
    int n;
    scanf("%d", &n);
    int arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    int i;
    int j;
    int temp;
    n = 10;
    for (i = 0; i < n - 1; i++) {            
        for (j = 0; j < n - i - 1; j++) {                
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
    return 1;
}

위의 C 코드를 대상으로 가상화 난독화를 적용하여 분석해 보았다. main함수에 난독화를 적용하였으며, 난독화 적용 후 LLVM IR로 컴파일은

clang -S -emit-llvm bubble_vir.c -o bubble_vir.ll

이렇게 하면 된다.

Switch

디스패치 옵션을 switch로 했을때이다.

Dispatch


위 사진은 디스패치 구조에서 switch부분을 캡쳐한 사진이다. 이 부분이 디스패치 구조의 시작이 되며, 해당 블록에서 분기되는 블록들이 handler가 된다.

Handler


위의 디스패치 시작 블록에서 분기된 블록이다.

중간언어에 해당하는 C 코드다. 중간언어에서 마지막에 390번 블록으로 분기하게 되는데

390번 블록에서는 다시 41번 블록, 위의 switch가 존재하는 dispatch 시작 블록으로 다시 분기하게 된다.

VM End Handler

가상화 종료의 경우 디스패치 시작 블록에서 분기되는 핸들러 중 한 핸들러에서 종료가 된다. 찾는 방법은 매우 쉽다. 핸들러중 분기되는 블록이 390번이 아닌 블록을 찾으면 된다.

382번 블록이다. 보면 종료 명령어가 ret인 것을 확인할 수 있다. main 전체를 가상화 하였기 때문에 마지막으로 실행되는 핸들러는 반드시 리턴이 되게 된다.

Exception

여기서 주의할 점이 모든 핸들러가 바로 390번 블록으로 분기하는 것은 아니다. 앞선 포스터에서 설명한 분기문과 함수 호출 핸들러의 경우 분기되는 위치가 다르다.

위 블록은 디스패치 시작 블록에서 분기된 블록이지만 종료 명령어가 조건분기인 것을 확인할 수 있다.

해당 블록은 함수 호출하는 핸들러다. scanf 함수를 원본에서 사용하였기 때문에 case문이 1개인 것을 확인할 수 있다.

Direct


디스패치 옵션을 direct로 했을 때, 디스패치 시작 블록이다. 조건 분기를 이용하여 각 핸들러로 분기된다.

여기서 의문점은 direct는 threaded 구조라 핸들러에서 다른 핸들러로 직접 이동한다 하였는데 왜 이러한 블록이 존재하는지다.

goto

정확한 이유는 찾지 못했지만 여러가지 코드를 예시로 확인해본 결과, 동일한 goto문을 사용하였기 때문이라 생각한다.

디스패치 옵션 direct로 난독화 했을 때의 코드다. 모두 동일한 goto문을 사용하는 것을 볼 수 있다. 컴파일러가 최적화를 위해 동일한 분기문을 하나의 베이지 블록으로 묶은 것으로 예상한다.

그 외에는 switch 옵션과 동일하다.

Indirect

indirect 디스패치 옵션의 경우 direct와 구조가 동일하다. direct도 마찬가지지만, switch 옵션과 달리 중간언어로 컴파일했을 때 C 코드와 구조가 다르다는 점이 있다.
switch의 경우 코드의 흐름, 예를 들어 switch가 나오고, 그 다음 case들이 순서대로 존재하는 구조를 가지고 있지만,
direct, indirect 의 경우 위에서 보여준 dispatch 시작 블록이 goto문의 label들 사이에 존재하는 차이점이 있다.

0개의 댓글