졸업프로젝트

17차 세미나: indrect jump해소, jump table 복구

img1244 2026. 7. 7. 20:43

우선 예제 코드들

1. 우선 기본 jump table(switch)을 보기위한 jt_basic.c

#include <stdio.h>
#include <stdlib.h>

__attribute__((noinline))
int basic_switch(int x) {
    switch (x) {
        case 0: return 11;
        case 1: return 22;
        case 2: return 33;
        case 3: return 44;
        case 4: return 55;
        default: return -1;
    }
}

int main(int argc, char *argv[]) {
    int x = 2;
    if (argc > 1) {
        x = atoi(argv[1]);
    }

    int r = basic_switch(x);
    printf("basic_switch(%d) = %d\n", x, r);
    return 0;
}

2. 인덱스 보정 + default + case 병합 유도를 위한 jt_advaced.c

#include <stdio.h>
#include <stdlib.h>

__attribute__((noinline))
int advanced_switch(int opcode) {
    // 인덱스 보정: case 범위를 10~16으로 이동
    switch (opcode) {
        case 10: return 100;
        case 11: return 110;
        case 12: return 120;
        case 13: return 120; // 일부러 12와 동일 (case 병합 가능성)
        case 14: return 140;
        case 15: return 150;
        case 16: return 150; // 일부러 15와 동일
        default: return -999;
    }
}

int main(int argc, char **argv) {
    int op = (argc > 1) ? atoi(argv[1]) : 13;
    int r = advanced_switch(op);
    printf("advanced_switch(%d) = %d\n", op, r);
    return 0;
}

3. 명시적 indirect jump(함수 포인터 디스패치)

#include <stdio.h>
#include <stdlib.h>

typedef int (*handler_t)(int);

__attribute__((noinline)) int h0(int v) { return v + 1; }
__attribute__((noinline)) int h1(int v) { return v + 10; }
__attribute__((noinline)) int h2(int v) { return v + 100; }
__attribute__((noinline)) int h3(int v) { return v + 1000; }

__attribute__((noinline))
int dispatch(int idx, int v) {
    static handler_t table[] = { h0, h1, h2, h3 };

    if (idx < 0 || idx >= (int)(sizeof(table)/sizeof(table[0])))
        return -1;

    // 간접 호출 (indirect call/jump 패턴 관찰 가능)
    return table[idx](v);
}

int main(int argc, char **argv) {
    int idx = (argc > 1) ? atoi(argv[1]) : 2;
    int v   = (argc > 2) ? atoi(argv[2]) : 7;

    int r = dispatch(idx, v);
    printf("dispatch(idx=%d, v=%d) = %d\n", idx, v, r);
    return 0;
}

 

참고로 이대로 컴파일하면 최적화 돼서 날아가 버릴 가능성이 매우매우매우 높기때문에

__attribute__((noinline))를 추가해서 함수 인라인 최적화를 막아 놓았다. 제어흐름을 조금 더 잘 보려고 추가했다.

gcc/Clang에서 쓰이는 거니까 gcc를 사용해서 해주도록 하자.

gcc -std=c11 -Wall -Wextra -O2 -g -o jt_basic.exe jt_basic.c
gcc -std=c11 -Wall -Wextra -O2 -g -o jt_advanced.exe jt_advanced.c
gcc -std=c11 -Wall -Wextra -O2 -g -o indirect_dispatch.exe indirect_dispatch.c

 

-Wall // 경고 활성화

-Wextra // 더 많은 경고 활성화

-O2 // 최적화 수준 2, O0로 할 경우 if-else 체인으로만 가득 채울 수 있어서, 실무 바이너리와 더 유사하게 확인하기 위해 설정

-g // 디버깅 심볼 포


기본 jump table(jt_basic.exe) 분석

 

상단의 display symbole tree를 누르면 화면 좌측 중단에 트리구조가 나오는데, 거기서 Functions → basic_switch로 이동

그러면 아래같은 디컴파일 코드가 나온다 짜잔

/* WARNING: Unknown calling convention */

int basic_switch(int x)

{
  int iVar1;
  
  iVar1 = -1;
  if ((uint)x < 5) {
    iVar1 = *(int *)(&_CSWTCH.3 + x * 4);
  }
  return iVar1;
}

대략 해석하자면 아래와 같은 의미로 볼 수 있다.

  • iVar1 = -1 //default
  • if ((uint))x < 5)  // case 유효 범위 체크 0-4
  • *(int *)(&_CSWTCH.3 + x * 4); // 점프 테이블/ 케이스 값 테이블 인덱싱

jmp가 나오지 않은 걸로 봐서 최적화가 발생한 것으로 보인다.

CSWTCH는 점프대상 테이블이 아니라 11,22,33,44,55(반환값)를 담고 있는 값테이블(value lookup table)이다. 

어쨌든 테이블 비스무리한 걸 찾았으니까 이걸로 일단 계속 진행

 

위의 CSWTCH를 symbol tree의 Labels 항목에서 찾을 수 있다(눈 빠지게 직접 찾지 말고 검색창이라는 문명의 이기를 사용하자)

그걸 더블클릭하면 listing 창에서 아래와 같은 값을 찾을 수 있다.

4바이트 little-endian, int이니까,

0b 00 00 00 = 0x0000000b = 11

16 00 00 00 = 0x00000016 = 22 ... 와 같이 해석해서 테이블 값을 해석할 수 있다.

이렇게 보면 좀 머리 아프고 생각하기 싫으니까

data → pointer or int 로 바꾸면 깔끔하게 볼 수 있다.

테이블들은 이런식으로 복구하면 될 듯하다.


인덱스 보정 + default + case 병합 유도 (jt_advanced.exe) 분석

위와 같은 방식으로 쭉 확인하면 

/* WARNING: Unknown calling convention */

int advanced_switch(int opcode)

{
  int iVar1;
  
  iVar1 = -999;
  if (opcode - 10U < 7) {
    iVar1 = *(int *)(&_CSWTCH.4 + (opcode - 10U) * 4);
  }
  return iVar1;
}

요런 코드를 확인할 수 있다. 

동일한 방식으로 CSWTCH.4를 열어보면

switch문에서 연산을 하고 인덱스 간의 거리로 값을 확인하는 방식을 잘 활용할 수 있다.

p code를 참조하면 위의 사실을 더 명확하게 확인 가능(EDX가 opcode -10)값임

또한, 10진수 값 { 100, 110, 120, 120, 140, 150, 150 }인걸로 봐서 최적화는 일어나지 않은 정상적인(?) 상황인 것으로 보인다.

 


명시적 Indirect Jump(indirect_dispatch.exe) 분석

테이블을 찾는 것이 중요한데, symbol tree에서 검색해서 찾아보자면, 아래와 같이 존재하는 것을 확인할 수 있다.

친절하게 어떤 함수에서 오는 table인지, 해당 위치가 어디인지도 확인할 수 있다.

 

004014d1에서 확인해 보자면,

 

이런 구조임을 확인할 수 있다. EAX는 원본 코드의 테이블의 인덱스로 들어가는 idx임으로

jmp [레지스터 + 함수::table+...]의 구조로 indrirect jump가 발생하는 것을 알 수 있다.

옆에 있는 0041460(테이블의 시작 주소, 옆에 흰 글씨로 적혀있음)으로 가면, 아래와 같이 h0로 이어짐 또한 확인할 수 있다.

 

 

디컴파일 된걸로만 보면 

위와 같이 단순하게 최적화가 된 걸로 보이지만, 어셈블리와 p-code로 표현된 값들을 보면 return 부분에 함수들이 들어가있는 형태임도 추정이 가능하다.

 

세줄요약

1. 일단 Symbol Tree를 켜서

2. 'table'이나, 'SWTCH', '함수명'::'table이름' 같은 키워드들을 검색하여 테이블이 구성되어있는지 확인

3. 없다면 'jmp [레지스터 +... :: ...]'와 같은 구조가 있는지 확인