기초 악성 코드 분석 2
1. 리틀 엔디언과 빅 엔디언
1.1 엔디언(Endian)이란?
- 컴퓨터 메모리에 데이터를 저장할 때 바이트(Byte)의 순서를 정의하는 방식.
1.2 리틀 엔디언(Little Endian)
정의
- 낮은 주소에 낮은 바이트(Least Significant Byte) 를 저장하는 방식.
- 즉, 데이터의 하위 바이트부터 메모리에 저장.
예시
32비트 정수 값: 0x12345678을 메모리에 저장할 때:
| 메모리 주소 | 값 |
| 0x00 | 78 |
| 0x01 | 56 |
| 0x02 | 34 |
| 0x03 | 12 |
→ 실제 메모리: 78 56 34 12
사용 환경 예시
- Intel x86/x64 아키텍처, ARM(기본 설정), Windows OS 등에서 사용.
1.3 빅 엔디언(Big Endian)
정의
- 낮은 주소에 높은 바이트(Most Significant Byte) 를 저장하는 방식.
- 즉, 데이터의 상위 바이트부터 메모리에 저장.
예시
32비트 정수 값: 0x12345678을 메모리에 저장할 때:
| 메모리 주소 | 값 |
| 0x00 | 12 |
| 0x01 | 34 |
| 0x02 | 56 |
| 0x03 | 78 |
→ 실제 메모리: 12 34 56 78
사용 환경 예시
- 네트워크 프로토콜(TCP/IP), IBM 메인프레임, 일부 RISC 프로세서 등에서 사용.
1.4 리틀 엔디언과 빅 엔디언의 차이점 비교
| 구분 | 리틀 엔디언 (Little Endian) | 빅 엔디언 (Big Endian) |
| 데이터 저장 순서 | 하위 바이트부터 | 상위 바이트부터 |
| 메모리 가독성 | 사람이 보기에는 덜 직관적 | 사람이 보기 쉽고 직관적 |
| 사용 환경 | Intel, Windows, ARM 등 | 네트워크 통신, IBM 등 |
| 장점 | 연산 효율성(간단한 CPU 설계) | 네트워크 전송 시 직관적 |
| 단점 | 데이터 가독성 낮음 | CPU 연산 설계 복잡성 증가 |
nb) 리틀 엔디언의 연산 효율이 높은 이유
- 주소 증가 방향과 CPU 연산 방향 일치
- 하위 바이트부터 처리 → 별도 처리 X
- 캐리(Carry) 처리 간편
- 낮은 자릿수 → 높은 자릿수 방향 일치 (산술연산 최적화)
- CPU 내부 구조 최적화
- 메모리 접근과 데이터 처리의 일관성 유지 (현대 프로세서의 설계 최적화)
1.5 악성코드 분석에서의 중요성
악성코드 분석 시, 특히 역공학(리버싱, Reversing) 과정에서 메모리 덤프나 바이너리 데이터 분석 시 반드시 엔디언 방식을 고려해야 함.
- PE파일(Windows 실행 파일)은 리틀 엔디언 사용.
- 네트워크 패킷 분석 시 프로토콜에 따라 빅 엔디언을 사용하는 경우도 많음.
2. 윈도우 메모리 구조
윈도우 운영체제의 메모리 구조는 크게 사용자 모드(User Mode)와 커널 모드(Kernel Mode)로 나눠짐.
악성코드 분석 시 코드 인젝션, 프로세스 할로잉, 루트킷 등 메모리 기반 공격의 이해에 필수적임.
2.1 사용자 모드(User Mode)
정의
- 일반적인 응용 프로그램이 실행되는 영역.
- 메모리에 직접 접근 불가 (커널 통해 접근).
특징
- 프로세스 간 독립된 메모리 공간 가짐.
- 접근 위반 발생 시 응용 프로그램 충돌, 운영체제는 무사.
구성 요소
- Stack (스택): 지역변수, 함수 호출 관리
- Heap (힙): 동적 할당 메모리
- Code (코드): 실행 가능한 프로그램 명령어
- Data (데이터): 정적 데이터, 글로벌 변수 등
악성코드 분석 포인트
- DLL Injection, Process Hollowing 분석
- Heap Spray(힙 스프레이) 공격 탐지
- 메모리 덤프 분석 및 리버싱 대상
2.2 커널 모드(Kernel Mode)
정의
- 운영체제 커널, 하드웨어 제어 담당 영역.
- 시스템 권한으로 메모리 전체 접근 가능.
특징
- 하드웨어 직접 접근 가능
- 시스템 서비스 제공 (파일, 네트워크, 프로세스 관리 등)
구성 요소
- 페이징(Paging) 및 메모리 관리 시스템
- 디바이스 드라이버(Device Drivers)
- HAL(Hardware Abstraction Layer, 하드웨어 추상 계층)
악성코드 분석 포인트
- 루트킷(Rootkit), 커널 익스플로잇 탐지
- 커널 후킹(Kernel Hooking) 분석
- 시스템 콜 후킹, 디바이스 드라이버 악성코드 탐지
- 사용자 모드 공격: 프로세스 인젝션, 메모리 변조.
- 커널 모드 공격: 루트킷, 드라이버 기반 악성 행위.
2.3 메모리 주소 공간 구성
+---------------------+ 0xFFFFFFFF
| 커널 공간 |
+---------------------+ 0x80000000 (2GB)
| 사용자 공간 |
+---------------------+ 0x00000000+----------------------------+ 0xFFFF'FFFFFFFF'FFFF
| 커널 공간 |
| |
+----------------------------+ 0xFFFF'80000000'0000
| 미사용 공간 |
+----------------------------+ 0x0000'7FFFFFFF'FFFF
| 사용자 공간 |
| |
+----------------------------+ 0x0000'00000000'0000- 악성코드 분석에서의 차이
분석 항목 32비트 64비트 메모리 주소 분석 간단함 (낮은 주소 범위) 주소 범위 큼 → 분석 어려움 루트킷 분석 제한적 64비트는 드라이버 서명, PatchGuard로 보안 ↑ 디버깅 도구 32비트 전용 64비트 환경 별도 도구 필요
3 함수 호출 규약
3.1 함수 호출 규약이란
- 함수 호출 시 인자 전달 방식, 스택 정리 책임, 레지스터 사용 규칙 등을 정의한 약속.
- 주로 사용되는 호출 규약: cdecl, stdcall, fastcall, thiscall, SysV, Microsoft x64, etc.
3.2 32비트 함수 호출 규약 (x86)
- 인자를 대부분 stack에 넣어서 전달함.
cdecl (C Declaration)
- 인자 전달: 오른쪽 → 왼쪽 순으로 스택에 push
- 스택 정리: 호출한 쪽(caller)이 스택 정리
- 예시:
int sum(int a, int b); // b 먼저 push, 그 다음 a- 리버싱에서 가장 일반적인 규약 (특히 C 언어)
stdcall
- 인자 전달: 스택
- 스택 정리: 호출당한 함수(callee)가 정리
- 주로 Windows API에서 사용
fastcall
- 인자 전달: 일부 인자를 레지스터(ECX, EDX)로 전달, 나머지는 스택
- 속도 개선 목적
3.3 64비트 함수 호출 규약 (x86-64)
- 스택보다 레지스터 기반 인자 전달을 우선시함.
Microsoft x64 규약 (Windows 64비트)
- 첫 4개의 인자 → 레지스터로 전달:
RCX, RDX, R8, R9
- 나머지 인자 → 스택
- 스택 정리: 호출자
- 스택 프레임 정렬: 16바이트 정렬
- 예시:
void foo(int a, int b, int c, int d, int e);
→ RCX=a, RDX=b, R8=c, R9=d, [stack]=eSystem V AMD64 규약 (리눅스/유닉스 계열)
- 정수 인자 레지스터 순서: RDI, RSI, RDX, RCX, R8, R9
- 나머지 인자 → 스택
- 스택 정리: 호출자
- 리눅스 기반 바이너리 분석 시 참고
정리 !
<32비트 호출 규약>
| 규약 | 인자 전달 | 스택 정리 주체 | 특징 |
| cdecl | 스택 (Right → Left) | 호출자 | C 함수 기본 |
| stdcall | 스택 | 피호출자 | WinAPI |
| fastcall | 레지스터 + 스택 | 피호출자 | 속도 ↑ |
<64비트 호출 규약>
| OS | 규약(표준) | 인자 레지스터 순서 | 추가 인자 | 스택 정리 |
| Windows | Microsoft x64 calling convention | RCX, RDX, R8, R9 | 스택 | 호출자 |
| Linux | System V AMD64 ABI | RDI, RSI, RDX, RCX, R8, R9 | 스택 | 호출자 |
3.4 호출 규약을 통해 추정할 수 있는 것들
함수 인자의 개수와 위치
- 스택이냐, 레지스터냐에 따라 인자 전달 방식이 다름.
- 악성코드가 어떤 함수에 몇 개의 인자를 전달했는지 파악해야, 그 함수가 뭘 하는지 추정 가능함.
예:
push 4
push 2
call sub_401000→ 스택 두 개 쌓고 함수 호출 → cdecl일 확률 높음
→ 인자 2개 전달 → sub_401000(a=2, b=4) 이런 함수일 수 있겠다(추정)
스택 정리 주체 확인 → 코드 흐름 파악에 도움
- 호출한 쪽이 정리? (cdecl)
- 호출된 함수가 정리? (stdcall, fastcall)
- → 함수 리턴 직후에 스택이 어떻게 변화하는지 이해해야 코드 흐름 정확히 따라갈 수 있음.
예:
call sub_401000
add esp, 8 ; // caller가 스택 정리 중 → cdecl임API 후킹, 쉘코드 분석 시 필수
- 악성코드가 CreateProcess, WriteFile, NtCreateThreadEx 같은 API를 후킹하거나 호출할 때,
인자가 RCX, RDX, R8, R9에 들어가는지 확인해야
→ 함수가 실제 어떤 파라미터로 실행되는지 알 수 있음
예:
mov rcx, rax // lpApplicationName
mov rdx, rsp+30h // lpCommandLine
call CreateProcessW→ RCX/RDX = 첫 두 인자 → Windows x64 호출 규약임 → API 인자 추정 가능
함수 경계 찾기 (함수 시그니처 파악)
- 호출 규약을 알면 어떤 프롤로그(시작), 에필로그(끝) 패턴을 가지는지 예측할 수 있음.
- 이를 통해 IDA나 Ghidra 같은 툴에서 함수 구간 제대로 잡히지 않을 때 수동 분석 가능.
예: push ebp → mov ebp, esp로 시작하면 cdecl이나 stdcall일 가능성 높음
행위 기반 탐지 우회 분석
- 어떤 악성코드들은 표준 호출 규약을 어기고 호출하기도 함 → 후킹 우회
- 예를 들어, 인자를 레지스터가 아니라 특정 메모리 주소에 써놓고 함수 호출 → 이걸 이상 징후로 탐지할 수 있어야 함.
함수를 분석할 때, 그 함수가 무슨 역할을 하고, 무슨 데이터를 가지고, 어디에 영향을 주는지를 이해하려면 호출 규약을 알아야 함
즉, 호출 규약은 함수 분석의 기본 프레임이고,
그걸 알아야 악성 행위의 흐름을 정확하게 추적 → 해석 → 대응할 수 있음
4. 실습
4.1 rev-basic-0






strcmp() 에서 같은 string이면 ‘0’이 return 되어야 하는데 왜 인자가 같으면 ‘1’을 반환한다고 했을까?
| 값 | 의미 |
| 0보다 작음 | string1이 string2보다 작음 |
| 0 | string1이 string2와 같음 |
| 0보다 큼 | string1이 string2보다 큼 |
>> return strcmp(같으면 0 반환함) == 0 ; // 두 string이 같으면 함수 전체적으로 ‘1’을 return 함
>> c언어에서는 strcmp()==0 패턴이 표준처럼 자주 쓰인다고 함

4.2 rev-basic-1




4.3 rev-basic-3



- byte[0] = *(a1+0) + 2*0 …
- byte[0] == a1[0] 인지 묻는 작업 … ?
- 즉 byte_140003000[0..22] 의 배열 정보와 입력된 값이 일치해야 함


XOR 역연산
- XOR는 비트를 비교해서 다르면 1, 같으면 0
data[i] = (a1[i] * i + 2 * i)
= i * (a1[i] + 2)a1[i] = (data[i] - 2 * i) / i나눗셈을 포함한 식이기 때문에 파이썬에서는 오버플로우 보정을 해주어야 함.
for i in range(24):
tmp = data[i] - 2 * i
tmp = tmp % 256 # 오버플로우 보정 (unsigned 8-bit)
a1_i = tmp ^ i # XOR 역연산
result.append(a1_i)XOR는 한번 더 XOR 하면 원래대로 돌아가기에 역연산에 유리 !
A ^ B = C
→ C ^ B = A
→ C ^ A = B
역연산에 자주 등장하는 구조들
| 유형 | 예시 | 역연산 방식 |
| 직접 비교 | if (a[i] != 100) | ASCII 해석 |
| XOR | a[i] ^ 0xAB == b[i] | 다시 XOR |
| 덧셈/뺄셈 | a[i] + 3 == b[i] | a[i] = b[i] - 3 |
| 로테이션 (bit rotation) | ROR(a[i], 3) | ROL(b[i], 3) |
| 해시 기반 (SHA1 등) | hash(input) == target | 역연산 어려움, 브루트포스 필요 |
| 암호화 (AES 등) | 암호 알고리즘에 의한 비교 | 키 없이는 거의 불가능, 공격 필요 |