함수의 기본 구조
- 리버스 엔지니어링의 기본을 익히기 위해서는 함수에대해 잘 알아야한다!! (소스코들은 많은 함수로 구성)
- 크게 함수가 하는 기능, 세세하게 파라미터 개수, 리턴값으로 어떤 값들이 전달되는 지 분석해야함
분석 시 주의할점 !
- 빌드시에 컴파일러가 자동으로 생성해내는 코드를 필터링해야한다!!
/* ========== 함수의 기본구조(C) ========== */
int sum(int x, int y){
int sum = x + y;
return sum;
}
/* ========== 디스 어셈브링한 코드 ========== */
push ebp // 베이스 주소를 스택에 저장
mov ebp, esp // 현재 스택포인터를 ebp로 바꾼다
push ecx
mov eax, [ebp+arg_0] // eax에 파라미터(int x)를 넣는다
add eax, [ebp+arg_4] // eax(x)에 파리미터(int y)를 더한다
mov [ebp+var_4], eax // [ebp+var_4](int sum)에 eax(x+y)를 넣는다
mov eax, [ebp+var_4] // eax에 [ebp+var_4](sum)을 넣는다
mov esp, ebp // 함수가 종료되기 전 스택 위치를 원래대로 돌린다
pop ebp // 함수를 종료 (eax를 리턴한다)
retn
- 이 함수는 push ebp로 시작해서 pop ebp로 끝난다!
- 간단한 함수의 경우에는 대부분 push ebp를 통해 함수시작한다
함수의 호출 규약
- __cdecl
- 호출 규약이 없을 경우 기본 값으로 사용!
- add를 통해 스택을 정리한다
- 메인 함수(0x401020)에서 add esp, 8을 통해 스택을 정리하는 것을 확인
- __stdcall
- 많이 사용되는 방식
- 함수 안에서 스택을 처리한다
- 메인함수(0x4010020)에서 스택을 처리하지않고 함수(0x401000) 안에서 처리하는 것을 확인!
- __fastcall
- 함수의 파라미터가 2개 이하일 경우 인자를 push로 넣지 않고 레지스터(ecx, edx)를 이용한다
- 레지스터를 이용해서 다른 호출규약보다 빠르다
- 메인함수(0x401020)에서 push를 이용하지 않고 edx, ecx 레지스터리를 이용하는 것을 확인
- __thiscall
- 주로 C++의 클래스에서 이용되는 방식이다
- 객체에 대한 포인터를 ecx에 전달한다
- c++에서 ecx로 전달되는 값이 this 포인터가 된다
if 문 (조건문)
위 사진은 visual stuido로 코딩한 것을 X32dbg로 역어셈블한 결과이다
- 0x401030이 main 함수인것을 추측할 수 있다
- 0x401033에서 push 1을 하는 것을 확인하고 0x401035에서 Temp함수(0x401000)를 호출하는 것을 확인!!
- 0x401000이 Temp()함수라는 것을 추측
- 0x401004가 if문으로 (cmp dword ptr ss:[ebp+8], 1) if(a == 1)인것을 확인
- ebp+4는 파라미터이며, dword(4Byte)인 것을 봐서 int a라고 추측
- ebp - 4는 int b, ebp+8은 a라고 추측할 수있다
- ebp - n 일 경우 보통 스택에서 변수를 의미한다
- ebp + n일 경우 보통 파라미터로 사용된다
- 0x401004의 조건문 ([ebp+8] == 1)에 따라 조건이 맞을 경우 0x40100F를 무시하고 0x401011로 가서 a를 1 증가하는 것을 확인
- 조건문이 다를 경우 0x40100F 점프문을 통해 0x40101C로 가서 b를 1 증가하는 것을 확인
- 추측을 통해 0x401004가 조건문인 것을 확인하였고, 0x401030이 메인 함수, 0x401000이 Temp()함수 인것을 확인
for, while문 (반복문)
위 사진은 visual stuido로 코딩한 것을 X32dbg로 역어셈블한 결과이다
- 0x401003을 통해 int형(4Byte) 변수 2개가 선언되었다는 것을 추측할 수 있다
- 0x401006, 0x40100D에서 각각 mov dword ptr ss:[ebp-8], 0 / mov dword ptr ss:[ebp-4], 0이므로 변수 2개가 선언되고 초기화된것을 확인
- ebp-4 가 i, ebp-8이 a인 것을 추측
- 0x401016 ~ 0x40101F에서 확인 할 수 있는데 ebp-4가 100(0x64)보다 크거나 같을 경우 종료
- 크거나 같지 않을 경우 ebp-4를 더하고 또 ebp-4에 1씩 증가하는 것을 확인 할 수 있다
- ebp-8은 a, ebp-4는 i라는 것을 확인
- 0x40100D~ 0x40102E가 for문(반복문)이라는 것을 확인
- 위에 추측하고 확인 한것을 통해 for(int i = 0 ; i < 100; i++) a += i; 문법인 것을 확인
- 디버거에서 확인 한 코드는 함수가 따로 있는게 아니라 메인 함수에 for문이 있는 것을 확인했다
※ 어셈블(Assemble) : 컴파일러가 어셈블리코드를 바이너리 코드로 전환하는 과정을 의미
※ 역어셈블(Disassemble) : 바이너리 코드를 어셈블리코드로 바꾸는 과정!!
* 실습을 하기위해서는 Visual studio의 보안 설정을 해제!!
- 프로젝트 설정(구성 : Release, 플랫폼 : 활성(win32))
- 구성속성 -> 고급 -> 멀티바이트 문자 집합 사용
- 구성속성 -> C/C++ -> 최적화 -> 최적화 : 사용안함
- 구성속성 -> C/C++ -> 코드 생성 -> 버퍼 보안 검사 : 아니요(/GS-) / 보안 검사 :보안검사 사용 안함 (/GS-)
- 구성속성 -> C/C++ -> 전처리기 -> _CRT_SECURE_NO_WARNNIGS
- 구성속성 -> 링커 ->(고급->) DEP(데이터 실행 방지) : 이미지가 DEP와 호환되지 않습니다(/NXCOMPAT:NO)
- 구성속성 -> 링커 -> 명령줄 -> /SAFESEH:NO
'개인 공부 > 리버싱' 카테고리의 다른 글
Reverse Engineering-006 PE-01 (0) | 2023.04.17 |
---|---|
Reverse Engineering-005 (0) | 2023.04.13 |
Reverse Engineering-004(C++) (0) | 2023.04.12 |
Reverse Engineering-003(구조체 & API) (0) | 2023.04.12 |
Reverse Engineering-001(Assembly) (0) | 2023.04.09 |