PE 파일.docx


1. 스택프레임

// ESP가 아닌, EBP레지스터를 사용하여 스택 내의 로컬 변수, 파라미터, 복귀 주소에 접근하는 기법. 

왜?? ESP는 수시로 변경되기 때문에 ESP를 기준으로 프로그램을 만들면 정확한 위치를 참고할 때 CPU가 힘들어. 


예시>

PUSH EBP  // 함수 시작(EBP를 사용하기 전에 기존의 값을 스택에 저장)

MOV EBP, ESP // 현재 ESP(스택 포인터)를 EBP에 저장 

......

......

MOV ESP, EBP // ESP를 정리 (함수 시작했을 때의 값으로 복원시킴)

POP EBP // 리턴되기 전에 저장해 놓았던 원래 EBP 값으로 복원

RETN  // 함수 종료 




call 02_hello.00401000 // add() 함수 



PUSH EBP // EBP값을 스택에 넣어라. EBP가 메인함수에서 베이스 포인터의 역할을 하게 되니깐 EBP가 이전에 갖고 있던 값을 스택에 백업하기 위한 용도
MOV EBP,ESP // ESP의 값을 EBP로 옮겨라. 즉, EBP는 ESP와 같은 값을 갖게됨.
SUB ESP,8 // long은 4byte이며, a,b 2개이므로 8byte의 공간 확보
MOV DWORD PTR SS:[EBP-4],1 // a의 1을 [EBP-4]에 저장.
MOV DWORD PTR SS:[EBP-8],2 // b의 2를 [EBP-8]에 저장.
MOV EAX,DWORD PTR SS:[EBP-8] // [EBP-8]주소에서 4byte 크기의 메모리 내용을 EAX로 이동
PUSH EAX // b에 값인 2를 스택에 넣기
MOV ECX,DWORD PTR SS:[EBP-4] // [EBP-4]주소에서 4byte 크기의 메모리 내용을 ECX로 이동
PUSH ECX // a에 값인 1을 스택에 넣기
CALL 02_hello.00401000 // add()함수
ADD ESP,8
PUSH EAX ; /<%d>
PUSH 02_hello.004020F4 ; |format = "%d
"
CALL DWORD PTR DS:[<&MSVCR90.printf>] ; \printf
ADD ESP,8 // 스택공간 정리 (파라미터 a,b관련 공간)
XOR EAX,EAX // 리턴값을 받는 EAX 초기화
MOV ESP,EBP // 함수의 에필로그
POP EBP
RETN


< CALL 02_hello.00401000 >

PUSH EBP // main() 함수에서 사용되던 EBP값을 스택에 백업
MOV EBP,ESP // add() 함수의 프롤로그
SUB ESP,8 // 스택에 8byte의 공간확보 (로컬변수 a,b의 공간)
MOV EAX,DWORD PTR SS:[EBP+8] // 파라미터 a를 가리킴
MOV DWORD PTR SS:[EBP-8],EAX // a의 값인 1을 [EBP-8]에 넣음
MOV ECX,DWORD PTR SS:[EBP+C] // 파라미터 b를 가리킴
MOV DWORD PTR SS:[EBP-4],ECX // b의 값인 2를 [EBP-4]에 넣음
MOV EAX,DWORD PTR SS:[EBP-8] // 1을 EAX에 넣음
ADD EAX,DWORD PTR SS:[EBP-4] // EAX에 2를 더해서 EAX는 3이 됨
MOV ESP,EBP // ESP를 원래대로 복원 (함수가 시작할때 ESP의 값을 EBP에 넣어두었다가 함수가 종료될 때 ESP를 원래대로 복원시키는 목적)
POP EBP





006.



#include <stdio.h>
main()
{
int i;
int hap = 0;
for( i = 1; i <= 10; i = i + 1 )
{
hap = hap + i;
}
printf( "1부터 10까지의 합: %d", hap );
}








'리버싱 > C언어 300제' 카테고리의 다른 글

리버싱 - c언어(001-005)  (0) 2018.12.13

001. c 프로그램 시작하기.

#include <stdio.h>
void main()
{
printf("안녕하세요."); // '안녕하세요' 출력
}



PUSH EBP

MOV EBP, ESP // 스택에 공간 확보

-----------------함수 프롤로그 ------------------

PUSH c_pro.004020F4 // c_pro.004020F4를 스택에 넣기

CALL DWORT PTR..... // printf함수 호출

------------------------------------------------

ADD ESP,4 // 사용한 스택없애주는 (ESP를 내리므로써 사용한 값이 들어있는 스택을 쓰레기값으로 만든다)

XOR EAX,EAX // EAX는 리턴값을 반환하는 데 사용하는 것. 메인함수의 일이 끝났으니 반환값이 들어있는 EAX를 XOR연산을 이용하여 0으로 초기화.

RETN

--------------- 함수 에필로그 --------------------



002.


#include <stdio.h>
void main()
{
int x;
int y;
int z; // 변수 생성
x = 1;
y = 2; // 변수의 값 입력
z = x + y;
printf( "%d", z ); // z의 값을 출력
}





PUSH EBP

MOV EBP,ESP // 함수의 프롤로그 SUB ESP,0C // 변수선언을 위해 스택에 공간확보 (변수가 3개이며, 스택은 4byte 간격이기에 12byte확보 위해 hex 0C를 스택에 확보) MOV DWORD PTR SS:[EBP-8],1 // [EBP-8]은 변수 x의 공간이므로, 1을 넣음 MOV DWORD PTR SS:[EBP-4],2 // [EBP-4]는 변수 y의 공간이므로, 2를 넣음. MOV EAX,DWORD PTR SS:[EBP-8] // [EBP-8]에 있는 1을 EAX에 넣음. ADD EAX,DWORD PTR SS:[EBP-4] // [EBP-4]에 있는 2를 EAX에 더함. 따라 2+1은 3 MOV DWORD PTR SS:[EBP-C],EAX // EAX에 있는 3을 [EBP-C]에 넣음. MOV ECX,DWORD PTR SS:[EBP-C] // [EBP-C]에 있는 3을 ECX에 넣음. PUSH ECX ; /<%d> // ECX에 있는 3을 스택에 넣음. PUSH c_pro.004020F4 ; |format = "%d" CALL DWORD PTR DS:[<&MSVCR90.printf>] ; \printf ADD ESP,8 // printf에 사용한 스택공간 버리기 (함수의 에필로그) XOR EAX,EAX // EAX 0으로 초기화

MOV ESP,EBP // ESP를 EBP와 같은 값으로 넣음으로써, 계산에 사용된 스택공간 버리기 POP EBP // EBP 스택에서 버리기. RETN



003.


#include <stdio.h>
#define X 1
#define PI 3.141592
int main()
{
double z;
z = X + PI; // 위에서 정의한 X와 PI의 값을 더하여 저장하는 변수
printf( "%f", z ); // 더하여 저장한 값을 실수형으로 출력
}























// FLD를 하면, 지정된 데이터가 ST0에 저장되면서 empty -> vaild로 변경.



PUSH EBP MOV EBP,ESP // 함수 프롤로그 SUB ESP,8 // 변수 저장을 위해 스택에 공간 확보 FLD QWORD PTR DS:[4020F8] // 004020F8에 있는 정보 8byte를 ST0에 로드 (ST0는 empty-> valid) FSTP QWORD PTR SS:[EBP-8] // [EBP-8]에 원래 있던 데이터는 스택에서 pop하고, ST0에 있는 데이터를 EBP-8스택에 저장, 그러면 ST0는 다시 valid -> empty

SUB ESP,8 // 변수 저장을 위해 스택에 공간 확보 FLD QWORD PTR SS:[EBP-8] ; | // [EBP-8]에 있는 8byte의 데이터를 ST0에 로드 (ST는 empty -> valid) FSTP QWORD PTR SS:[ESP] ; | // ESP에 있던 기존의 내용은 스택에서 POP하고, ST0에 들어있는 8Byte에 내용을 ESP에 저장(ST0은 valid ->empty) PUSH c_pro.004020F4 ; |format = "%f" CALL DWORD PTR DS:[<&MSVCR90.printf>] ; \printf ADD ESP,0C // 함수의 에필로그 XOR EAX,EAX // 리턴값 반환하는 EAX를 초기화 MOV ESP,EBP POP EBP RETN




* FLD

// 데이터를 로드



* FSTP

// 데이터를 저장 (저장시 기존내용을 POP한다)




004.



#include <stdio.h>
void main()
{
int x;
int y;
x = 10;
y = x - 5;
if( x > y )
{
printf( "x의 값이 y보다 큽니다." );
}
else
{
printf( "x의 값이 y보다 작거나 같습니다." );
}
}





PUSH EBP
MOV EBP,ESP // 함수의 프롤로그
SUB ESP,8 // 변수 선언을 위해 스택 공간 확보
MOV DWORD PTR SS:[EBP-8],0A // 10을 [EBP-8]에 저장
MOV EAX,DWORD PTR SS:[EBP-8] // [EBP-8]에 있는 10을 EAX에 저장
SUB EAX,5 // EAX=EAX-5
MOV DWORD PTR SS:[EBP-4],EAX // EAX에 있는 5를 [EBP-4]에 저장
MOV ECX,DWORD PTR SS:[EBP-8] // [EBP-8]에 있는 10을 ECX에 저장
CMP ECX,DWORD PTR SS:[EBP-4] // ECX에 있는10에서 [EBP-4]에 있는 5를 뺀다. ECX가 더 크므로 점프하지 않는다.
JLE SHORT c_pro.0040102E // 작거나 같으면 점프니깐 점프를 하지 않는다.
PUSH c_pro.004020F4 ; /format = "x의 값이 y보다 큽니다."
CALL DWORD PTR DS:[<&MSVCR90.printf>] ; \printf
ADD ESP,4 // 스택에 공간확보
JMP SHORT c_pro.0040103C // 무조건 0040103C로 점프하기
PUSH c_pro.0040210C ; /format = "x의 값이 y보다 작거나 같습니다."
CALL DWORD PTR DS:[<&MSVCR90.printf>] ; \printf
ADD ESP,4
XOR EAX,EAX // 함수 에필로그
MOV ESP,EBP
POP EBP
RETN



* CMP A, B
// A에서 B를 뺀다.
















* JLE A, B (Jump Less Equals)
// A<=B => 점프 (무조건 A기준이다.)



005.





#include <stdio.h>
void main()
{
int x;
int y;
x = 20;
y = 10;
if( x > y )
{
printf( "x의 값이 y보다 큽니다." );
}
else
{
printf( "x의 값이 y보다 작거나 같습니다.");
}
}





























PUSH EBP
MOV EBP,ESP // 함수의 프롤로그
SUB ESP,8 // 변수 선언을 위해 스택에 공간확보
MOV DWORD PTR SS:[EBP-8],14 // [EBP-8]에 14 넣기
MOV DWORD PTR SS:[EBP-4],0A // [EBP-4]에 0A넣기
MOV EAX,DWORD PTR SS:[EBP-8] // [EBP-8]에 있는 14를 EAX에 넣기
CMP EAX,DWORD PTR SS:[EBP-4] // EAX에 있는 14와 [EBP-4]에 있는 0A를 비교하기
JLE SHORT c_pro.0040102C // 작거나 같으면 점프하기. 하지만, 크기때문에 점프하지 않는다.
PUSH c_pro.004020F4 ; /format = "x의 값이 y보다 큽니다."
CALL DWORD PTR DS:[<&MSVCR90.printf>] ; \printf
ADD ESP,4 // 사용한 함수있는 스택공간 지우기
JMP SHORT c_pro.0040103A // 무조건 점프
PUSH c_pro.0040210C ; /format = "x의 값이 y보다 작거나 같습니다."
CALL DWORD PTR DS:[<&MSVCR90.printf>] ; \printf
ADD ESP,4 // 사용한 함수가 있는 스택공간 지우기
XOR EAX,EAX // EAX(리턴값)을 초기화
MOV ESP,EBP // 함수의 에필로그
POP EBP
RETN






'리버싱 > C언어 300제' 카테고리의 다른 글

리버싱 - c언어(006-010)  (0) 2018.12.24

1. 스택(Stack)

// 높은 주소 -> 낮은 주소 (스택에 값을 입력하면 ESP가 감소하고, 값을 꺼내면 ESP는 증가한다)


- 함수 내의 로컬 변수 임시 저장

- 함수 호출 시 파라미터 전달

- 복귀 주소(return address) 저장




2. abex' crackme #1 분석 



* 어셈블리 명령어 설명

- PUSH : 스택에 값을 입력

- CALL : 지정된 주소의 함수를 호출

- INC : 값을 1 증가

- DEC : 값을 1 감소

- JMP : 지정된 주소로 점프

- CMP : 주어진 두 개의 operand 비교 ( SUB와 비슷하지만, CMP는 operand의 값이 변경되지 않고, 

                                                     EFLAGS 레지스터만 변경됨.  두 operand의 값이 동일 하면 ZF=1이다.)

- JE : 조건 분기(Jump if equal), ZF=1이면 점프 



* 스택에 파라미터를 전달하는 방법

// MessageBoxA() 함수를 호출하기 전에 4번의 PUSH 명령어를 사용하여 필요한 파라미터를 역순으로 입력.



------------------------------ c 언어로 변환 ------------------------------------------------


MessageBoxA(NULL, "Make me think your HD is CD-ROM.", "abex' 1st crackme", MB_OK | MB_APPLMODAL ); 


하지만, 받는입장에서 생각하면 Stack은 FIFO이기때문에 역순이 아닌 제대로 받는 것. 


 

'리버싱 > 리버싱의 핵심원리' 카테고리의 다른 글

PE구조 헤더  (0) 2019.03.20
Reversing - 스택프레임  (0) 2018.12.26
Reversing - 바이트 오더링, IA-32 Register 기본 설명  (0) 2018.12.13
Reversing - Hello World!  (0) 2018.12.08
Reversing - 시작<기본>  (0) 2018.12.08

1. 바이트 오더링(Byte Ordering) 

// 데이터를 저장하는 방식.



1) 빅 엔디언(Big Endian) 

// 사람이 보는 것과 동일, 앞부터 순차적으로 저장.

ex) 대형 UNIX서버(RISC계열의 CPU)



2) 리틀 엔디언(Little Endian)

// 데이터를 역순으로 저장.  

- 리틀 엔디언에서 2byte(WORD), 4byte(DWORD)와 같은 멀티 바이트인 경우 역순으로 저장

- 리틀 엔디언에서 2byte 미만 , 문자열(str은 곧 char이기에)은 빅 엔디언처럼 앞부터 순차적으로 저장.  

ex) Windows 계열 Intel x86 CPU 


2. IA-32 Register 기본 설명

* 레지스터 (Register)
// CPU내부에 존재하는 다목적 저장 공간. 

* Basic program execution registers
1) General Purpose Registers(32bit - 8개)
// 범용레지스터

- EAX (Extended Accumulator Register)
// 산술, 논리 연산 수행시 사용되는 레지스터 , 함수 리턴 값에 사용(리턴 값은 EAX에 저장한 후 리턴)
   EAX - 32bit, 반 쪼개면 AX - 16bit , 또 다시 반 쪼개면 AH, AL(각 8bit)

- EBX (Extended Base Register)
// 메모리 주소를 저장하기 위한 용도의 레지스터 

- ECX (Extended Count Register)  
// 반복 명령어(for, while 등) 사용시 카운터로 사용되는 레지스터 

- EDX (Extended Data Register)
// EAX와 함께 쓰이며, 부호 확장 명령이나 큰수의 곱셉/나눗셈 연산에 사용되는 레지스터

-------------------------- 산술 연산 명령어에서 상수/변수 값의 저장 용도로 사용 -------------------------

 - ESI (Extended Source) / EDI (Extended Destination Index)
// ESI는 데이터 조작/복사 시 데이터의 주소가 저장.
   EDI는 복사할 때 목적지의 주소가 저장. 

----------------------------------- 메모리 복사에 사용 -----------------------------------------------------------

- EBP ( Extended Base Pointer) / ESP (Extended Stack Pointer)
 // ESP는 Stack의 끝 지점 주소를 가리킴, 명령어(PUSH, POP, CALL, RET)들을 직접 조작
    EBP는 Stack의 첫 시작 주소를 가리킴, 함수가 호출되었을 때 그 순간의 ESP를 저장하고 있다가 함수가 
             리턴하기 직전에 다시 ESP에 값을 되돌려줘서 스택이 깨지지 않도록 함.(Stack Frame 기법) 

----------------------------- 메모리 주소를 저장하는 포인터로 사용 ----------------------------------------


2) Segment Registers(16bit - 6개)
// CS, DS, SS, ES, FS, GS


3) Program Status and Control Register (32bit - 1개)
// EFLAGS, 종류가 많지만 기초적인 단계에서는 3가지만 알기
   Flag는 단어 그대로 깃발이 올라가면 1(TRUE/On), 내려가면 0(False/Off) 

- Zero Flag(ZF)
// 연산 명령 후에 결과 값이 0이면 -> ZF = 1 세팅

- Overflow Flag(OF)
// 부호 있는 수의 오버플로우 발생/ MSB(Most Significant Bit) 변경 시  -> 1로 세팅  

- Carry Flag(CF)
// 부호 없는 수의 오버플로가 발생했을 때 -> 1로 세팅 


4) Instruction Pointer(32bit - 1개)
// EIP 

- EIP (Instruction pointer)
// cpu가 처리할 명령어의 주소를 나타내는 레지스터,  명령어를처리 후 자동으로 그 명령어 길이만큼 EIP를 증가시킴. 







'리버싱 > 리버싱의 핵심원리' 카테고리의 다른 글

PE구조 헤더  (0) 2019.03.20
Reversing - 스택프레임  (0) 2018.12.26
Reversing - 스택(Stack), abex' crackme #1 분석  (0) 2018.12.13
Reversing - Hello World!  (0) 2018.12.08
Reversing - 시작<기본>  (0) 2018.12.08

1. Hello World! 프로그램


#include "windows.h"
#include "tchar.h"
int _tmain(int argc, TCHAR *argv[])
{
MessageBox(NULL,
L"Hello World!",
L"www.reversecore.com",
MB_OK);
return 0;
}

// 릴리즈 모드로 빌드하기.

c언어(HelloWorld.cpp) ---- <빌드> -----> 실행파일(HelloWorld.exe)// 기계어 ------ <디버거> ----> 어셈블리어



2. 디버깅 시작
// Immunity Debugger 사용

 Code Window 

Register Window 

Dump Window 

Stack Window 


- 사용법

// Restart (ctrl+F2)

Step Into (F7) : CALL함수를 만나면, 함수 코드 내부로 따라 들어감.

Step Over (F8) : CALL함수를 만나면, 따라 들어가지 않고, 함수 자체를 실행.

Execute till Return (Ctrl+F9) : 함수 코드 내에서 RETN 명령어까지 실행(함수 탈출 목적)

Go to(Ctrl+G) : 원하는 주소로 이동

Comment (;) : comment 추가

Set/Reset Break Point (F2) : BP 설정/해제

Run (F9) : 실행(BP가 걸려있으면 그곳에서 실행이 정지됨)

Show the previous Cursor (-) : 직전 커서 위치를 다시 보여줌.



* EP(Entry Point)

// Windows 실행 파일(EXE, DLL, SYS)의 코드 시작점을 의미.

address Instruction      Disassembled code                           comment


- address : 가상 메모리내의 주소

- instruction : cpu 명령어

- Disassembled code : OP code를 보기 쉽게 어셈블리로 변환한 코드 (OP code란 Operation Code)

- comment : 디버거에서 추가한 주석



* BP(Break Point)

- view -> Breakpoints // BP목록 보기(Alt+B)



3. main() 찾기

- 경험과 노하우를 통한,,

// push , push, call...~

call에는 메인함수의 주소가 들어있다.



- 문자열 검색 방법

// code window 부분에서 우클릭 -> Search for -> All referenced text strings -> 이동하고자 하는 문자열을 더블클릭




// 제대로 간지 확인법> dump window 에 ctrl+G -> push 뒤에 있는 주소 입력 (0040211c)

-> dump window보면 아스키에 helloworld!가 찍혀 있음.




- API 검색

// 우리가 사용한 MessageBox()를 이용해서 찾는 것.

// Search for -> All intermodular calls -> 원하는 것 더블클릭하면 이동



- 문자열 패치1 <원본문자열의 크기와 수정문자열의 크기가 같거나 작은 것>

만약,,, 크다면,,, 이거는 뒤에 있는 함수에 영향을 주기때문에 프로그램 에러가 발생.

// 바꾸고자 하는 문자열을 드래그 후 ctrl+E (문자열 패치)


// 문자열이 바뀐 것을 확인할 수 있다. 하지만 이건 임시적으로 바꾼 것이다.

따라서, Restart를 하면 처음 상태 그대로 문자열이 helloWorld가 나온다.



// 임시적인게 아니라 저장하고싶다??

저장하고자 하는 문자열 드래그 -> 우클릭 -> Copy to executable file



// hex파일에 드래그 된 부분 우클릭 -> save file



- 문자열패치2 <원본문자열보다 수정문자열이 크기가 더 큰 경우>


// 맨 밑에 널패딩으로 가득한 곳을 찾는다. 그 주소 00401810을 dump window에서 ctrl+G로 검색

// 검색해서 쓰고싶은 만큼 드래그 -> ctrl+E -> 문자열 입력.



// 아까 위에있던 원본 문자열 push하는 부분에서 주소값을 널패딩으로 가득했던..

내가 새로 입력한 문자열 주소로 바꾼다. 주소를 바꾸기 위해서는 바꾸고자하는 줄을 더블클릭해준다.


// 바꾸고, 그 부분에 BP를 추가하여 Run한다. 그럼 내가 바꾼 문자열로 실행되는 것을 볼 수 있다.



* 깨알용어정리

- VA(Virtual Address) : 프로세스의 가상 메모리

- OP code (Operation code) : cpu 명령어(바이트 code)

- PE(Portable Executable) : Windows 실행파일(EXE, DLL, SYS 등)




1. RCE

// Reversing Code Engineering(역공학)이라고 하며, 시스템의 구조, 기능, 동작 등을 분석/ 원리 이해 


2. RCE 방법

1) 정적 분석 <파일을 실행 X>

//  파일의 종류, 크기, 헤더정보, 내부 문자열, 실행 압축 여부, 등록 정보, 디버깅 정보 등의 다양한 내용을 확인하는 것.


2) 동적 분석 <파일을 실행>

// 디버깅을 통하여 코드 흐름과 메모리 상태보기, 파일/레지스트리/네트워크 행위 분석, 

    디버거를 이용하여 프로그램 내부 구조와 동작 원리 분석.



 * Immunity Debugger //어셈블리어 관찰 


* HxD // HEX(16진수)로 보기  


* vitual studio 2008 // c 및 c++로 코딩 


이 외에도 레지스트리를 관찰하는 Process Moniter

              dll 분석을 위한 BinText 등이 있다. 


+ Recent posts