카테고리 없음

[실전형사이버훈련장/리버싱] 학습 내용 정리 2

공지혜 2026. 4. 2. 16:16

13. CPU의 레지스터와 주요 명령어 - IA32 주요 레지스터 세트

프로세서 레지스터(레지스터): CPU 내에 존재하는 작은 크기의 저장 공간.

  • CPU 내부에 있으므로 HDD나 SSD와 같은 주기억장치에 비해 빠른 접근 가능.
  • 프로그램이 실행될 때 필요한 다양한 정보들(메모리 주소, 데이터 등)을 저장.

*Intel 32-bit 아키텍처에서 사용되는 주요 레지스터 세트

(E=Extended, 16-bit 아키텍처에서 32-bit로 넘어오면서 붙은 것)

  • 범용 레지스터 (General-purpose register): 산술/논리 연산에 사용되는 피연산자 정보, 주소 계산을 위한 피연산자 정보, 메모리 포인터 정보 등 저장.
    • EAX: 윈도우 함수의 호출 결과값 or 산술/논리 연산 수행 결과값 저장.
    • ECX: 카운터 레지스터. 반복문 Loop 수행 시 주로 남은 반복 횟수를 저장.
    • ESI: string 연산(복제) 시, source의 시작 주소를 저장.
    • EDI: string 연산(복제) 시, destination의 시작 주소를 저장.
    • EBP: 베이스 포인터. 스택 자료 구조의 데이터(스택 프레임에서 base가 되는 지점) 주소를 저장.
    • ESP: 스택 포인터. 스택 자료 구조의 top 주소를 저장.

범용 레지스터

  • 세그먼트 레지스터 (Segment register): GDT(Global Descriptor Table, 메모리상의 세그먼트 정보를 담은 자료 구조)의 인덱스 값을 저장.
     
    • CS: Code Segment register, 코드 부분의 시작 주소를 저장.
    • DS: Data Segment register, 데이터 부분의 시작 주소를 저장.
    • SS: Stack Segment register.
    • ES: Extra Segment register.
    • FS
    • GS

세그먼트 레지스터

 

  • EFLAGS 레지스터: 명령이 실행되는 과정 중 현재 프로세서의 상태 정보, 제어 정보 등을 저장. 각 32개의 비트가 별도의 의미를 가지고 있음.
    즉, 각 비트마다 서로 다른 플래그 값 저장. 이 레지스터에 하나의 커다란 정수나 문자열을 저장하지 않음

  • DR (Debug Register): 디버깅과 관련하여 주로 브레이크 포인트 주소값을 저장.
    • 브레이크 포인트: 특정 조건이 만족되면, 특정 주소에서 프로그램의 실행을 멈추는 것.
  • EIP 레지스터: Instruction Pointer 또는 Program Counter라고 불림. '다음에' 실행할 instruction의 주소 값을 저장.
    값이 가장 자주 바뀌는 레지스터.

EIP가 mov 명령어를 가리키고 있다면, mov 명령어는 아직 실행되지 않았고, 바로 다음에 실행되어야 함.

 

14. CPU의 레지스터와 주요 명령어 - IA32 주요 명령어

IA-32: 인텔의 비트 마이크로프로세서에서 사용하는 명령 집합 아키텍처

 

  • mov [destination] [source]: Source 피연산자 값을 destination 피연산자로 복사.
    • 두 피연산자의 사이즈는 반드시 동일해야 함.
    • mov의 피연산자: 메모리 공간, 데이터, 레지스터

ex1. mov DWORD [0x00004000], 0Ah: h는 hexa(16진수)값이라는 뜻. 16진수 0A를 메모리 주소 0x00004000 위치에 4바이트(DWORD)만큼 복사.

ex2. mov eax, DWORD [0x00004000]: 메모리 주소 0x00004000 위치에서 4바이트(DWORD)만큼의 값을 eax 레지스터에 복사.

ex3. mov ecx, eax: eax 레지스터 값을 ecx에 복사.

 

  • lea [destination] [source]: Load Effective Address. Source 피연산자를 참조하여 계산된 메모리 주소가 destination 피연산자(범용 레지스터)에 저장.
    -> 즉, 레지스터(destination)에 주소 (source)값을 저장.

ex. lea eax, [edx+4]: edx에 저장된 값이 8이었으므로 edx+4는 0xC임. 대괄호가 쳐져 있으니까 이걸 메모리 주소로 보면 됨. 그 0xC라는 (단순 숫자가 아닌)메모리 주소를 eax에 저장.

 

  • add [destination] [source]: 두 피연산자를 더한 값을 destination 피연산자에 저장.
    • 두 피연산자의 크기 동일해야 함.
    • Source 피연산자의 값은 변하지 않음.
  • sub [destination] [source]: Destination 피연산자를 source 피연산자만큼 감소시킴.
    • CPU의 ALU 내부 연산: source 피연산자에 2의 보수를 취해 음수로 만든 후, destination에 '덧셈'을 함.

 

CALL 명령어와 JMP 명령어 비교

  • IA32에서 EIP(=PC) 레지스터는 프로그램 흐름에 직접적인 영향을 미치는 레지스터이므로 임의로 값 수정 불가. 대신에 JMP, CALL, RET 등 명령어를 이용하여 간접적으로 수정 가능.
  • CALL, JMP 명령어의 공통점: 피연산자로 지정된 주소로 프로그램의 실행 흐름을 변경.
    • call [0x100]: 0x100 주소로 eip를 이동.
    • jmp [0x100]: 0x100 주소로 eip를 이동.
  • CALL, JMP 명령어의 차이점:
    • CALL은 분기하기 직전에 현재 EIP값(CALL 다음 명령어)을 스택에 백업, JMP는 백업하지 않음.
    • CALL은 조건 분기 불가능, JMP는 가능.

 

  • ret = pop eip: 현재 스택의 top에 있는 값을 스택에서 빼서(pop) eip에 저장하는 명령어. (보통 함수 스택 프레임 정리가 다 끝나고 백업해 놓은 이전 eip가 top에 남았을 때.) 그러나 pop eip는 eip 값을 직접 바꾸는 명령어가 되기 때문에 이 형태로는 사용 불가.

 

  • push [operand]: 피연산자를 스택 메모리 최상단에 입력한 후 스택 포인터(esp)를 변화시킴.
  • pop [operand]: 스택 메모리 최상단에 있는 값을 피연산자에 저장한 후 스택 포인터(esp)를 변화시킴.
    • ex. push ebp: ebp 레지스터에 저장된 값을 스택에 저장.
    • ex. pop eax: 스택의 top 값을 eax 레지스터에 저장.
    • => 이렇게 연달아서 쓰면 ebp에 있는 값을 스택 메모리를 매개로 하여 eax에 저장하겠다는 뜻. 

프로그램 실행 흐름 예제

  • mov dword ptr [esp+1Ch], 5: esp는 지금 100을 가리키고 있음. 1Ch는 십진수로 28. 따라서 [esp+1Ch]=128번지에 4바이트 크기만큼 5를 저장.
  • mov dword ptr [esp+18h], 7: [esp+18h]=124번지에 4바이트 크기만큼 7을 저장.
  • mov eax, [esp+18h]: [esp+18h]=124번지 값인 7을 eax 레지스터에 저장.
  • mov eax, ds:_loop_count: loop count라는 변수의 값을 eax 레지스터에 저장.
  • cmp [ebp-8], eax: [ebp-8]=104번지에 있는 값과 eax값을 비교.
  • jl short loc_4014ED: jl=jump if less. less의 기준은 cmp에서 첫 번째 피연산자"가" 더 작으면 점프하는 것. 만약 cmp 명령에서 104번지 값이 더 작았다면 (실제로는 플래그 비트 보고) loc_4014ED로 점프.

 

15. CPU 레지스터와 주요 명령어 - IA32 주요 명령어와 레지스터 사용예제 분석 실습

  1. IDA를 이용하여 예제 실행 파일을 로드하고 "IDA VIEW-A" 탭에서 main 함수 주소로 이동한 후 코드 루틴을 확인.
  2. xDbg를 이용하여 예제 실행 파일을 실행시킨 후 앞서 확인한 지점에 브레이크 포인트를 설정. (단축키 F2)
  3. Run(F9)하면 main 함수 내에서 첫 번째 브레이크 포인트가 히트되면서 프로그램이 일시 정지됨.
  4. Stepping을 하면서 각 코드의 기능을 확인.
    • 한 줄 실행(step into): 호출되는 함수로 이동. (단축키 F7)
    • 한 줄 실행(step over): 호출되는 함수의 실행 과정 건너뜀. (단축키 F8)
  5. loop 함수(main 함수 외의 함수) 주소로 이동한 후 코드 루틴을 확인.

어셈블리 명령어 한 줄 한 줄을 '직역'하는 것은 큰 의미가 없음.

'맥락'을 이해하는 것이 중요. 전후 코드를 하나의 묶음으로 해석할 수 있어야 함.

 

16. 가상주소공간의 스택 메모리 - 스택 메모리 개요

  • IA32 메모리의 프로세스가 사용하는 가상 주소 공간(VAS)에서 대표적으로 많이 사용되는 버퍼 메모리: 힙(heap), 스택(stack)
  • 스택은 호출되는 함수들의 운용을 위해 존재. ex. 함수에게 전달되는 파라미터, 지역 변수, 반환 주소 등이 저장됨.
  • 사이버 보안 분야에서 스택 메모리는 대표적인 보안 공격 대상이며, 스택 운용을 위한 루틴들이 코드 영역 곳곳에 존재함. -> 중요!!
  • 콜 스택(Call stack): 컴퓨터 프로그램에서 현재 실행 중인 서브 루틴에 관한 정보를 저장하는 스택 자료 구조. 함수의 call과 관련이 있어서 call stack. 일반 스택 자료 구조와는 다르게 top이 아닌 데이터에도 접근 가능.
  • 스택은 2개의 오퍼레이션(push, pop)과 1개의 탑 포인터(esp 레지스터)로 운용됨.
    • push: 스택의 top에 데이터를 저장한 후, esp 레지스터 값 변화시킴.
    • pop: 스택의 top에 있는 데이터를 추출한 후, esp 레지스터 값 변화시킴. + 보통 피연산자인 레지스터에 저장
  • 스택 자료 구조 ~= Call stack
  • 스택의 종류

 

  • IA32 기반 스택은 탑 포인터가 마지막으로 저장된 데이터를 가리키며, 낮은 주소 방향으로 자라는 Full Descending Stack 방식으로 운용됨.

 

 

17. 가상주소공간의 스택 메모리 - IA32 스택 프레임

  • 스택 프레임: 동작 중인 쓰레드에 의해서 실행되는 함수가 사용하는 고유한 스택 영역.
  • 스택 프레임에 대한 이해 -> 악성 코드 분석, 취약점 분석 시 스택 메모리의 데이터 흐름과 구조 이해
  • 함수 실행 중 EBP는 고정, ESP는 상황에 따라 변함.
    • EBP(Base Pointer): 현재 실행 중인 함수의 스택 프레임 시작 주소(base)를 가리킴.
    • ESP(Stack Pointer): 현재 실행 중인 함수의 스택 프레임 top을 가리킴.
  • ex. main 함수가 A 함수를 call, A 함수가 B 함수를 call한 상황. (SF=Stack Frame, 아래는 descending 방식 말고 그냥 위로 올라가게 표현한 그림!)

  • A 함수, B 함수 실행이 끝나면, EBP는 다시 main 함수의 base를 가리키고, A, B가 사용하던 스택 프레임 공간에 저장된 값들은 삭제되지는 않지만 쓰레기 값으로 취급됨.

 

함수 프롤로그 - main 함수 시작

sub esp, n 끝난 후 모습

  • push ebp: main 함수 시작하기 전에는, main 함수를 부른 함수(main caller)의 base 주소가 저장되어 있음. ebp 값(=main caller의 base 주소)를 스택에 push해서 백업. -> main 함수 끝나면 main caller로 되돌아오기 위함.
  • mov ebp, esp: main 함수 시작 전 esp(스택의 top)은 main 스택 프레임의 거의 맨밑을 가리키고 있음. 그 esp의 값을 ebp에 복사하여 main caller 스택 프레임 시작점에 있던 ebp를 main 스택 프레임 시작점으로 끌어올림.
  • sub esp, n: 현재 esp 값에서 필요한 만큼의 메모리 크기 n를 빼서 지역 변수를 저장할 공간 만듦.

 

함수 에필로그 - main 함수 종료

  • mov esp, ebp: main 함수 스택 프레임 top에 있던 esp를 main 함수의 base(=ebp 값)로 끌어내림. ebp가 가리키는 곳의 데이터 값 = main caller의 base 주소.
  • pop ebp: ebp 값을 main 함수의 base에서 main caller 함수의 base로 끌어내림. 
  • retn: main caller로 리턴 = pop eip(의미상). 다음에 실행할 명령어를 main caller에서 main 호출한 라인 다음 줄로.

 

Stack Frame Pointer Omission

  • ebp 레지스터를 스택의 프레임 포인터로 사용하지 않도록 하는 컴파일 방법.
    즉, ebp 레지스터를 스택 프레임의 base를 가리키고만 있는 데에 낭비하지 않고, 계산이나 다른 생산적인 일을 하는 데에 사용.
  • 스택 프레임 셋업/제거 코드(프롤로그/에필로그) 업음.
  • ebp 레지스터를 다른 용도로 사용 가능 -> 프로그램 성능 향상.
  • 대부분 컴파일러가 최적화 옵션 형태로 제공.
    ex. Visual Studio (/O2, /FPO), GCC(--formit-frame-pointer)
  • 컴파일러가 런 타임에 사용되는 주소들을 미리 계산해야 하므로 컴파일 단계에서 부담이 커짐.
  • ebp가 base를 가리키는 스택 프레임 개념 사라짐 -> 지역 변수와 매개 변수 모두 esp를 기준으로 esp+N 형태 표현.
  • 분석자 입장에서 디버깅 번거로워짐.

ebp가 스택 프레임 base를 가리킬 때 -> ebp-N 형태로 스택 메모리 주소 표현 용이. (실제 메모리 주소는 descending 방식이므로 그림상 위쪽에 있는 변수를 -4 해서 접근한 것.)

ebp가 스택 프레임 base를 가리키지 않을 때(Stack Frame Pointer Omission) -> ebp는 다른 아무 데나 가리키거나 아무 데이터나 저장하고 있음. 함수의 콜 스택 관련 포인터 레지스터는 esp뿐이므로 esp+N 형태로 접근해야 함. 그런데 esp는 항상 바뀌는 값이므로 상황에 따라 계산 복잡.

 

18. 가상주소공간의 스택 메모리 - IA32 스택 프레임 분석 실습

함수 내에 정의된 에필로그/프로롤그 코드 분석 -> 함수가 사용하는 스택 프레임의 생성, 소멸 과정 분석.

예제 PE-COFF 파일을 대상으로 디컴파일러(IDA)를 이용하여 관련 코드 분석 후 디버거(xDbg)를 사용하여 실제 동작 과정 관찰.

  1. IDA를 이용하여 예제 실행 파일을 로드.
  2. "IDA VIEW-A" 탭에서 main 함수로 이동한 후 프롤로그/에필로그 코드 루틴 확인.
  3. loop 함수로 이동한 후 프롤로그/에필로그 코드 루틴 확인.
  4. xDbg를 이용하여 프롤로그 코드와 에필로그 코드 루틴에 브레이크 포인트를 설정하고 디버깅.
    -> 스택 프레임에서 ESP, EBP값 변화 관찰!!

 

19. 가상주소공간의 스택 메모리 - 호출된 함수의 복귀주소

  • 호출자 함수(caller)는 호출 대상 함수(callee)를 호출할 때 되돌아올 주소값(return address)를 스택에 백업.
  • 호출된 함수의 복귀 주소(return address)는 "call 함수명()" 명령이 실행될 때 스택에 백업됨.
    • 인텔 계열 아키텍처: return address를 주로 스택에 저장.
    • MIPS 및 ARM 아키텍처: return address를 레지스터에 저장.
  • 스택에 백업된 복귀 주소는 함수의 마지막 코드인 RET 명령에 의해 참조됨. 이때 복귀 주소는 주로 caller 함수에서 "call" 명령어의 다음 명령어 주소.
  • RET = POP EIP (기능상)
  • call A() = push EIP + jmp A() : <EIP 값 = call A() 다음 instruction=Return address>를 스택에 push하고 함수 A가 시작되는 instruction으로 점프.

여기서 LV는 local variable(지역 변수)

* Save Frame Pointer = Saved EBP = Caller의 EBP. 이건 push ebp로 따로 push하는 것.

RET -> return address가 pop돼서 eip에 저장됨. 따라서 caller인 main으로 명령어 컨트롤 이동.

A가 또 B를 호출해도 동일한 구조.

해커는 스택 오버플로우 공격을 할 때, 흔히 Return Address를 악성 함수 주소로 바꿔 놓는 방식으로 공격함.

 

20. 가상주소공간의 스택 메모리 - 호출된 함수의 복귀주소 분석 실습

예제 프로그램의 함수 흐름

  • SFP = Saved Frame Pointer = Caller의 ebp
  • main 함수의 SFP 아래에도 main 함수의 return address 존재. 
  1. IDA를 이용하여 예제 실행 파일을 로드하고 "IDA VIEW-A" 탭에서 main 함수로 이동한 후 call sum(), retn 명령어의 주소를 확인.
  2. sum 함수로 이동한 후 retn 명령어의 주소를 확인. -> call sum() 다음 명령어의 주소
  3. xDbg를 이용하여 위의 프롤로그/에필로그 코드 루틴에 브레이크 포인트를 설정하고 동작 확인.
    => 호출된 함수는 모든 동작을 마친 후 stack 상에 백업해 놓은 복귀 주소로 돌아감을 확인할 수 있음.

 

21. 함수 호출규약 - 개념과 함수 호출규약의 종류

ABI(Application Binary Interface): 바이너리 기반 프로그램 모듈에서 스택 구성 방식, 메모리 액세스 방식들을 정의한 일정의 규칙.
ex. 기본 데이터 유형의 크기, 레이아웃, 정렬 등을 정의

> 함수 호출 규약은 ABI의 일부 유형.

함수 호출 규약(Function Calling Convention)

  • caller와 callee 간 혼선이 발생하는 것을 막기 위한 규칙.
  • 함수들은 다양한 주체에 의해 만들어짐 -> caller, callee 상호 간 규칙이 없다면 프로그램이 올바르게 동작하기 어려움.
  • 주요 내용
    • 매개 변수 전달 방법: 스택 or 레지스터 or 스택+레지스터
    • 매개 변수 전달 방향: (Right to Left) or (Left to Right)
    • Callee가 caller를 위해 보존해 줘야 할 레지스터 종류(callee가 함부로 값을 바꾸고 리턴하면 안 되는 레지스터): Callee Saved Register 또는 Caller Saved Register라고 불림
    • 매개 변수 전달에 사용된 스택 영역 해제 주체: Caller or Callee

매개 변수 전달 방향

  • IA-32, x86-84 아키텍처에서는 함수 호출 시 파라미터와 복귀 주소를 주로 스택에 저장
  • MIPS, ARM 아키텍처에서는 레지스터에 저장

 

22. 함수호출규약 - IA32의 호출규약 유형별 특징

IA32의 함수 매개 변수 전달 - __cdecl, __stdcall 

  • 매개 변수 전달 방법: __cdecl, __stdcall 모두 스택을 이용하여 파라미터 전달.
  • 매개 변수 전달 방향: __cdecl, __stdcall 모두 Rightmost to Leftmost 방식 사용.

Push 명령어 사용 목적

  • 스택 프레임에서 지역 변수 공간 확보
    : 스택에 어떤 값이든 push를 하면 결국 esp(스택의 top)이 변화하기 때문에 스택 메모리 공간을 확보하기 위해서 Push 이용 가능.
  • 파라미터 전달
    : push 명령어 직후 call [함수명] 명령어가 연달아 나온다면, callee에 전달할 매개 변수를 스택에 push한 것일 가능성 높음.
  • 레지스터 값 백업
    : 아래와 같은 명령어 구조가 보인다면, edx, ecx 레지스터의 값을 스택에 백업했다가 함수 동작이 끝난 후 다시 원래 레지스터에 저장하는 것.
push edx
push ecx
......
pop ecx
pop edx
ret

IA32의 리턴 값 전달

  • 리턴 값 크기 <= 4bytes: EAX 레지스터를 이용하여 리턴 값 전달.
  • 4bytes <= 리턴 값 크기 <= 8bytes: 상위 4바이트는 EDX, 하위 4바이트는 EAX 레지스터를 이용하여 리턴 값 전달.

  • mov eax, [ebp+var_4]: callee가 자신의 스택 프레임에 저장된 값을 eax에 옮겨서 저장한다. 리턴 값을 eax에 저장한 것. (다음 줄부터는 스택 프레임 닫는 에필로그 코드.)
  • push eax: eax 값을 스택에 저장한다. call sub_401000 함수 호출 후, 리턴 값이 eax에 저장되어 있을 것을 알아서 eax 값을 바로 쓴 것이다. 

레지스터의 관리 주체

  • Caller Saved Register: Caller가 관리 책임을 가지는 레지스터들
    • Callee가 자유롭게 사용할 수 있는 레지스터.
    • 해당 레지스터의 값이 함수 호출 전후로 같아야 한다면, caller가 callee를 호출하기 전에 해당 레지스터 값들 백업해 둬야 함.
    • ex. EAX, ECX, EDX
  • Callee Saved Register: Callee가 관리 책임을 가지는 레지스터들
    • Callee가 자유롭게 사용할 수 없는 레지스터.
    • Callee가 함수 호출 전후로 해당 레지스터들 값이 동일하도록 보장해야 함.
    • Callee가 해당 레지스터들을 사용해야 하는 경우, 그 값을 백업해 두었다가 복귀하기 전에 다시 복원.
    • ex. EBX, ESI, EDI

 

23. 함수호출규약 - IA32의 호출규약 유형별 특징 분석 실습

  1. 함수 호출 규약 이해를 위해 예제 소스 코드 파일을 편집기를 이용하여 로드.
    - main 함수가 cdecl_sum, stdcall_sum, fastcall_sum 3개의 함수에게 정수 매개 변수 두 개를 전달함. (기능은 모두 같음.)
  2. IDA에서 예제 실행 파일을 로드.
    - IDA View-A 탭에서 Ctrl+G -> main 함수 주소로 이동.
  3. main 함수 내에서 cdecl, stdcall, fastcall 함수 호출 규약을 사용하는 함수 3개가 호출되는 것을 확인할 수 있음.
  4. 다음의 관점에서 코드를 분석해 보기.
    Q1. main 함수의 매개 변수 전달 방법? 레지스터 사용 or 스택 사용 or 레지스터+스택 사용.
    Q2. main 함수의 매개 변수 전달 순서? (Leftmost to Rightmost) or (Rightmost to Leftmost)
    Q3. 호출되는 함수가 리턴 값을 전달할 때 사용하는 레지스터: eax or eax+edx 혼합 사용.
  5. 디버거를 이용하여 앞서 분석한 내용을 직접 검증해 보기.

+ Callee Saved Register 규약에 따라 함수가 ebx, esi, edi 레지스터를 사용하기 전에 push로 백업하고, 종료 직전에 pop으로 복원하는 것도 확인할 수 있음.

 

24. 컴파일된 코드의 패턴 식별 - 순환문 패턴 식별 (For, While)

  • 많은 악성 코드가 데이터 암/복호화, 무작위 문자열 생성, 데이터 복제 등을 목적으로 순환문을 사용함.
  • 이러한 순환문들은 어셈블리 코드상에서 외관상 일정한 패턴을 가짐. 그 패턴을 식별하면 문석 대상 파일의 동작 방식을 정확하게 빠르게 이해할 수 있음.
  • 컴파일된 순환문의 영역 구분
    • "순환문 탈출 조건 점검" 영역
    • "순환 코드 바디" 영역
    • "조건 값 변화" 영역

while문 패턴 식별

위와 같은 while loop를 포함하는 C 코드가 있다고 하자. 어떤 컴파일러로, 어떤 옵션을 써서 컴파일하는지에 따라 어셈블리 코드의 구조가 매우 다르게 나타난다.

 

gcc 컴파일러로 컴파일했을 때 어셈블리 코드의 흐름

  1. 반복문 시작 부분에서 바로 "탈출 조건 점검" 영역으로 점프한다. (파란색 화살표는 처음 딱 한 번만 실행.)
  2. 탈출 조건에 맞지 않으면(cmp) 위쪽 코드로 다시 점프해서(jle) "순환 코드 바디" 영역을 실행한다. (분홍색 화살표.)
  3. "순환 코드 바디"를 다 실행하면, "조건 값을 변화"시킨다.
  4. "탈출 조건 점검"을 해서 탈출 조건에 맞지 않으면 다시 "순환 코드 바디" 영역으로 jle 점프. (분홍색 화살표는 탈출 조건 만족할 때까지 실행.)

Visual Studio 컴파일러로 컴파일했을 때 어셈블리 코드 흐름

  1. 순환 코드 실행하기 전에 먼저 "순환문 탈출 조건 점검" 영역에서 loop 내의 코드 실행할 것인지 확인.
  2. 탈출 조건에 부합할 경우(jge) 점프. (파란색 화살표는 탈출할 때 한 번만 실행.)
  3. 부합하지 않으면 점프 없이 "순환 코드 바디" 영역 실행.
  4. "조건 값 변화" 영역 실행.
    mov eax, [ebp+a] ; 어떤 메모리 위치에 있는 값을 eax에 저장
    add eax, 1 ; eax++
    mov [ebp+a], eax ; eax 레지스터에 저장했던 값을 다시 원래 메모리에 저장
  5. "순환 코드 바디" 실행하고 "조건 값 변화"시켰으면, 다시 "순환문 탈출 조건 점검" 영역으로 위로 점프해서 (분홍색 화살표) 조건 체크하고 반복문 실행.

for문 패턴 식별

위와 같은 for loop를 포함하는 C 코드가 있다고 하자.

 

gcc 컴파일러로 for문을 컴파일했을 때의 어셈블리 코드를 보면, while문을 컴파일한 어셈블리 코드와 거의 유사하거나 동일한 것을 볼 수 있다.

Visual Studio 컴파일러로 컴파일했을 때 어셈블리 코드 흐름

  1. 반복문 시작 부분에서 바로 "탈출 조건 점검" 영역으로 점프한다. (파란색 화살표 처음 딱 한 번만 실행.)
  2. 탈출 조건에 맞으면 점프. (분홍색 화살표도 탈출할 때 한 번만 실행.)
  3. 탈출 조건에 맞지 않으면 "순환 코드 바디" 영역 실행.
  4. "순환 코드 바디" 영역 실행 끝나면 무조건 "조건 값 변화" 영역으로 점프. (초록색 화살표는 반복문 끝날 때까지 계속 실행.)
  5. "조건 값 변화"시킨 후 다시 "순환문 탈출 조건 점검".

 

  • 디컴파일 도구(ex. Ghidra)로 for.exe을 디컴파일하든, while.exe을 디컴파일하든, 동일한 pseudo code를 출력함.
    즉, for문으로 쓴 코드인지, while문으로 쓴 코드인지보다 '반복문이라는 것', '어떤 코드 덩어리를 반복하는지', '몇 번 반복하는지'가 중요함.

  • 컴파일러에 따른 각 영역의 배치 순서

 

25. 컴파일된 코드의 패턴 식별 - 순환문 패턴 식별 실습 (For, While)

for, while 루틴이 컴파일된 실행 파일을 대상으로 IDA를 이용하여 어셈블리 코드 분석, Ghidra 디컴파일 기능을 이용하여 C 언어 포맷 가상 코드를 추출하고 분석.

  1. 디스어셈블러 IDA를 이용하여 for, while 문법을 사용하는 예제 C 언어 코드를 확인.
    * 컴파일된 순환문은 일반적으로 "순환문 탈출 조건 점검" 영역, "순환 코드 바디" 영역, "조건 값 변화" 영역으로 구분.
  2. 디스 어셈블러를 이용하여 gcc로 컴파일한 "for.exe", "while.exe" 파일을 분석
    * 각 영역의 등장 순서와 전후 관계에 초점을 두어 분석.
  3. 디스 어셈블러를 이용하여 Visual Studio로 컴파일한 "for.exe", "while.exe" 파일을 분석
    * 각 영역의 등장 순서와 전후 관계에 초점을 두어 분석.
  4. Ghidra를 실행한 후 앞서 분석한 바이너리 파일들을 프로젝트 내에 등록하고 "Code Browser" 기능을 통해 디컴파일 결과를 확인
    -> gcc, Visual Studio 두 종류의 컴파일러에 한해서 for, while 문법과 상관없이 'for문'으로 해석하는 것을 확인 가능

따라서, 동일한 코드 루틴이라고 해도 컴파일러의 종류에 따라 머신 코드의 표면적인 표현 방법이 달라질 수 있음.

분석하는 데 사용되는 디컴파일러가 추출한 가상 코드 != 원본 소스 코드의 문법.

문법과 별개로, 코드 루틴에서 식별되는 '각 섹션 간 전환 조건의 논리적 흐름'을 이해하는 것이 중요.

 

26. 컴파일된 코드의 패턴 식별 -악성코드의 순환문 사용 실습 (파일명 무작위 생성)

for 반복문을 사용하는 PE-COFF 포맷의 악성 코드 샘플 파일을 분석, 로직이 구체화된 가상 코드를 만들어 보는 실습.

악성 코드 샘플: 시스템 내에서 백도어 역할을 하여 지속적으로 C2 서버와 통신하여 명령을 수신, 악성 행위를 수행.수신한 명령어 중에는 악성 코드 유포 서버에서 파일을 다운로드하는 기능이 있으며, 이때 만들어진 악성 파일의 이름은 반복문을 통해 생선됨.

  1. 분석용 가상 환경 내에서 디스어셈블러(IDA)를 이용하여 예제 악성 코드를 로드.
  2. IDA View-A 탭에서 Ctrl+G를 눌러 메인 함수 주소로 이동.
  3. 함수 내의 반복문 분석.
  4. Ghidra의 코드 브라우저를 이용하여 가상 코드 확인.