[모각코] #9 해킹 맛보기 3장 리버스 엔지니어링(3.5~3.6)
한줄 리버싱 & 함수 리버싱
3.5 한 줄 리버싱
3.5.1 지역변수
-
어셈블리어
: C 코드가 구현하는 프로그램을 디스어셈블리한 결과
-
확인 방법:
올리디버거에서 main 함수로 이동
(주소는 보안떄문에 매번 바뀜)
-
명령어:
-
MOV
-> 2개의 오퍼랜드(Operand)가짐
ex)
0x012613EE MOV DWORD PTR SS:[EBP-8],2
-> 0x012613EE 주소의 오퍼랜드는 [EBP-8]과 2임
=> EBP-8의 주소에 상수 2를 저장하라
-
ADD
-> 1개의 오퍼랜드 가짐
(1개의 오퍼랜드를 가지는 명령어는 연산 값을 해당 오퍼랜드에 저장)
ex)
ADD EAX, 2
=> EAX 레지스터의 값과 상수 2를 더해 EAX에 저장하라
-
1개의 오퍼랜드를 가지는 명령어는 연산 값을 해당 오퍼랜드에 저장
-
오퍼랜드가 변하지 않는 명령어는 EFLAGS 레지스터를 설정
-
-
3.5.2 전역 변수
-
- 전역변수
-
지역변수와 다르게 데이터 세그먼트 위치에 저장
ex) 전역변수 gv의 어셈블리 코드
0x00D713EE MOV DWORD PTR DS:[gv],2
-> 전역변수 gv의 정확한 메모리 위치를 표시하면 EBP 레지스터가 아닌 특정 메모리 주소에 저장됨
(전역변수가 모든 함수에서 사용할 수 있도록)
3.5.3 구조체
-
구조체 출력 어셈블리 코드
-
C 구조체 코드
struct ST { int sv_1; int sv_2; int sv_3; };
-
어셈블리 변환 코드
0x013d1F8 MOV DWORD PTR SS:[EBP-14],1 0x013d1F8 MOV DWORD PTR SS:[EBP-10],2 0x013d1F8 MOV DWORD PTR SS:[EBP-C],3
-
스택 메모리에 저장된 구조체
Low Address 1 EBP-0x14 2 EPB-0x10 3 EBP-0xC -> C언어 int형은 4바이트이므로, ST 구조체의 크기는 12바이트(4*3)
=> 스택메모리와 같이 연속된 12바이트에 차례로 값 저장
-
3.5.4 if 리버싱
-
if 어셈블리 코드
-
C언어 if문 코드
int lv = 2; if(lv>0) print("%x\n",1v);
-
어셈블리 변환 코드
0x011213EE MOV DWORD PTR SS:[EBP-8],2 0x011213EE CMP DWORD PTR SS:[EBP-8],0 #주소는 바뀌니까 그냥 쓰겠음 0x011213EE JLE SHORT 01121416
-
명령어
-
CMP
-> 2개의 오퍼랜드를 비교하여 결과를 EFLAGS 레지스터의 사인 플래그에 저장
-
JLE(Jump if less)
-> CMP가 설정한 EFLAGS 레지스터의 값(사인 플래그)을 보고 점프 여부 판단
-
-
3.5.5 switch 리버싱
-
switch 어셈블리 코드
-
C언어 switch 문
int lv = 2; switch (1v){ case 0: printf("lv is 0\n"); case 1: printf("lv is 1\n"); default: printf("default\n"); }
-
어셈블리 변환 코드
(주소는 생략함)
MOV DWORD PTR SS:[EBP-8],2 MOV EAX,DWORD PTR SS:[EBP-8] MOV DWORD PTR SS:[EBP-D0],EAX CMP DWORD PTR SS:[EBP-D0],0 JE SHORT CMP DWORD PTR SS:[EBP-D0],0 JE SHORT JMP SHORT
-
명령어
-
JE(Jump if Equal)
-> CMP명령어로 비교한 값으로 점프 여부 판단
-
JMP(Jump)
-> EFLAGS 레지스터와 상관없이 무조건 점프
(default에 해당)
-
-
3.5.6 for 리버싱
-
for 어셈블리 코드
-
C언어 for문
int 1v; for(lv=0;lv<2;lv++) print("%x\n",1v);
-
어셈블리 변환 코드
(주소는 생략함)
MOV DWORD PTR DWORD PTR SS:[EBP-8],0 JMP SHORT MOV DWORD PTR DWORD PTR SS:[EBP-8] ADD EAX,1 MOV DWORD PTR DWORD PTR SS:[EBP-8],EAX CMP DWORD PTR DWORD PTR SS:[EBP-8],2 JGE SHORT
-
명령어
-
JGE(Jump if greater of equal)
-> CMP 명령어로 비교한 값으로 반복문 탈출 여부 판단
-
-
3.5.7 while 리버싱
-
while 어셈블리 코드
-
C언어 while문
int lv=0; while(true){ if(lv == 2) break; }
-
어셈블리 변환 코드
MOV DWORD PTR SS:[EBP-8], 0 MOV EAX, 1 TEST EAX, EAX JE SHORT 0138142C CMP DWORD PTR SS:[EBP-8], 2 JNZ SHORT 01381406 JNZ SHORT 0138142C MOV, EAX, DWORD PTR SS:[EBP-8] ADD EAX, 1 MOV DWORD PTR SS:[EBP-8], EAX JMP SHORT 013813F5
-
명령어
-
JNZ(Jump not zero)
-> CMP명령어로 비교한 값으로 점프 여부 판단
-
-
3.6 함수 리버싱
3.6.1 콜링 컨벤션
-
- 콜링 컨벤션
-
함수가 어떻게 인자를 전달받고 자신을 호출한 함수에게 리턴값을 어떻게 돌려주는지에 대한 약속된 함수 호출 규약
-
호출 규약
구분 __cdecl __stdcall __fastcall 인자 전달 방법 스택 스택 레지스터, 스택 스택 해제 방법 호출한 함수 호출된 함수 호출된 함수 -
__cdecl
-
__cdecl 호출 규약 C언어
ex) sum()
int __cdecl sum(int a, int b, int c) { return a+b+c; } int main(){ int s; s=sum(1,2,3); print("%x\n",s); return 0; }
-
__cdecl 어셈블리어 코드
PUSH 3 PUSH 2 PUSH 1 CALL 00221104 ADD ESP, 0C MOV DWORD PTR SS:[EBP-8], EAX
=> PUSH 로 stack에 인자 쌓기
-
__cdecl 인자 전달 스택 구조
Low Addres 1 <- ESP 2 3 High Address => 스택은 높은 주소에서 낮은 주소로 데이터 쌓음
-
__cdecl 스택 해체
Low Addres 1 2 3 <- ESP High Address
-
-
__stdcall
-
__stdcall 호출 규약 C언어
ex) sum()
int __cdecl sum(int a, int b, int c) { return a+b+c; } int main(){ int s; s=sum(1,2,3); print("%x\n",s); return 0; }
-
__stdcall 어셈블리어 코드
PUSH 3 PUSH 2 PUSH 1 CALL 01271104 MO DWORD PTR SS:[EBP-8], EAX
-
명령어
-
RETN
-> 호출된 함수에서 호출한 함수로 되돌아감
-
- __cdecl 호출 규약과 달리 스택 해제를 호출된 함수에서 함 -> RETN 0C를 이용해 스택 해제
-
-
__fastcall
-
sum 함수 __fastcall 함수 어셈블리어 코드
PUSH 3 MOV EDX, 2 MOV ECX, 1 CALL 00B711EA MOV DWORD PTR SS:[EBP-8], EAX
-
__ cdecl , __stdcall 호출 규약과 달리 인자 입력에 메모리 보다 속도가 빠른 레지스터가 사용됨
-> 사용에 한계가 있어 스택도 같이 사용
-
스택 해제는 __stdcall 과 같이 호출된 함수에서 함
-
-
3.6.2 함수 호출 리턴값 확인
-
결과값 리턴하는 main 함수 어셈블리어
MOV DWORD PTR SS:[EBP-8], EAX MOV EAX,DWORD PTR SS:[EBP-8] PUSH EAX
-> EAX 레지스터에 함수 리턴값을 스택에 저장
3.6.3 함수 프롤로그, 에필로그
-
- 프롤로그
-
함수 시작을 위해 스택과 레지스터를 재구성
-
- 에필로그
-
함수 종료 시 스택과 레지스터를 정리
-
문자열 출력 C 언어 코드
int main() { printf("prologue & epiloque\n"); return 0; }
-
문자열 출력 main 함수 어셈블리어
PUSH EBP MOV EBP, ESP MOV ESP,EBP POP EBP RETN
-> 함수 프롤로그:
* 이전 함수에서 사용한 EBP를 **PUSH EBP 명령어로 스택에 저장** * MOV EBP, ESP 명령어로 **현재 스택 위치 EBP에 저장**
- 함수의 변수와 인자를 위해 스택을 사용함
-> 함수 에필로그:
- MOV ESP, EBP를 사용해 스택 위치를 함수 시작 전 위치로 되돌림
- POP EBP 명령어로 스택에 저장된 이전 EBP 값을 EBP 레지스터에 저장
- 스택과 레지스터의 정리 끝나면 RETN 명령어로 호출한 함수로 되돌아감
3.6.4 지역 변수, 전역 변수, 포인터
지역 변수, 전역 변수
-
지역 변수, 전역 변수를 사용하는 C언어 코드
int main() { int lv; int *gv; lv = 1; gv = (int*)malloc(0x4); *gv = 2; printf("lv is %d\n", lv); printf("gv is %d\n", gv); return 0; }
-
지역 변수, 전역 변수 사용 함수의 어셈블리어
MOV DWORD PTR SS:[EBP-8], 1 # 지역변수 1v에 1 대입 PUSH 4 CALL DWORD PTR DS:[2B92C0] #malloc(4)부분으로, 힙 역역 메모리 할당 # malloc() 함수 호출하면 EAX 레지스터로 할당된 주소 리턴 MOV DWORD PTR SS:[EBP-14], EAX MOV EAX, DWORD PTR SS:[EBP-14] MOV DWORD PTR DS:[EAX], 2 # EAX 명령어로 할당된 주소를 스택 [EBP-14] 주소에 저장 # 2개의 명령어를 사용하여 힙 주소에 2 대입 -> 포인터 변수 gc를 통해 할당된 힙 사용가능 MOV EAX,DWORD PTR SS:[EBP-8] # 지역 변수 인자 입력(스택에 저장되어 있는 값 입력) MOV EAX,DWORD PTR SS:[EBP-14] MOV ECX, DWORD PTR DS:[EAX] # 스택 변수를 2개의 명령어로 [EBP-14]에 저장되어 있는 힙 주소를 EAX 레지스터로 저장 & EAX 레지스터에 저장된 힙 주소 값을 ECX 레지스터로 저장
포인터
-
포인터 C언어
int inc(int *a) { *a = *a+1; return *a; } int main() { int s, ret; s = 2; ret = inc(&s); printf("%d, %d\n", s, ret); return 0; }
-
포인터 함수 어셈블리어(주소 이용)
MOV EAX,DWORD PTR SS:[EBP+8] # 지역 변수 s의 스택 주조 할당 MOV ECX,DWORD PTR DS:[EAX] # EAX 레지스터 값을 지역 변수 s의 스택 주소로 할당 ADD ECX,1 # ECX 레지스터에 2할당 MOV EDX,DWORD PTR SS:[EBP+8] # 지역 변수 S의 스택 주소 구하기 MOV DWORD PTR DS:[EDX],ECX # 구한 스택 주소에 1 더한 값 할당 MOV EAX,DWORD PTR SS:[EBP+8] MOV EAX,DWORD PTR DS:[EAX] # 리턴 값 설정