Investigate and write! :D

■ 기초 리버싱 ■



1. IA-32 Register 기본 설명 (4)

2. 스택 (5)

3. crackme 분석 #1 (6)

4. 스택 프레임 (7)







odbg110.zip



1. IA-32 Register 기본 설명 (4)


레지스터를 모르면 리버싱을 하면서 나오는 명령어에 대해서 이해하기 힘들기 때문에 집고 넘어가야 합니다!


* 1.1. CPU 레지스터


레지스터는 CPU 내부에 존재하는 다목적 저장 공간입니다.

우리가 일반적으로 메모리라고 얘기하는 RAM과는 성격이 다릅니다.


RAM : CPU가 RAM에 있는 데이터를 엑세스하기 위해서는 물리적으로 접근해야 되기 때문에 시간이 오래 걸립니다.

레지스터 : 레지스터는 CPU와 한 몸이기 때문에 고속으로 데이터를 처리할 수 있습니다.




* 1.2. IA-32의 레지스터


IA-32는 지원하는 기능도 무척 많고 그만큼 레지스터의 수도 많습니다.


< iA-32에 존재하는 레지스터들의 종류 >

- Basic program execution registers

- x87 FPU registers

- MMX registers

- XMM registers

- Control registers

- Memory management registers

- Debug registers

- Memory type range registers

- Machine specific registers

- Machine check register

...


애플리케이션 디버깅의 초급 단계에서는 Basic program executing register에 대해서 알아두어야 합니다.

(향후 중/고급 단계로 올라가면 추가적으로 Control registers, Memory management, registers, Debug registers 등 공부하기)



* 1.3. Basic program executing registers


- 1.3.1 General Purpose Registers (32비트 - 8개) / 중요

범용 레지스터(General Purpose Registers)는 이름처럼 범용적으로 사용되는 레지스터들 입니다.

IA-32에서 각각의 범용 레지스터들의 크기는 32비트(4바이트)이고 보통은 상수/주소 등을 저장할 때 주로 사용됩니다.


특정 어셈블리 명령어에서는 특정 레지스터를 조작하기도 합니다.

또한 어떤 레지스터들은 특수한 용도로 사용되기도 합니다.

다음 그림에 범용 레지스터들이 표시되어 있습니다


- EAX: : Accumulator for operands and results data / 피연산자 및 결과 데이터를 위한 누적 장치

- EBX : Pointer to data in the DS segment / DS세그먼트의 데이터에 대한 포인터

- ECX : Counter for string and loop operations / 문자열 및 루프 작동에 대한 카운터

- EDX : I/O pointer / 입출력 포인터


[I/O] input/output 를 줄인말이며 '입출력'이라는 뜻이 있습니다.

[Accumulator] '어큐물레이터'라고 부르며 장치 또는 축적하는 장치라는 뜻이 있습니다.


위 4개의 레지스터들은 주로 산술연산(ADD, SUB, XOR, OR 등) 명령어에서

상수/변수 값의 저장 용도로 많이 사용됩니다.


어떤 어셈블리 명령어(MUL, DIV, LODS 등)들은 특정 레지스터를 직접 조작하기도 합니다.

(이런 명령어가 실행된 이후에 특정 레지스터들의 값이 변경됩니다.)



그리고 추가적으로 ECX와 EAX는 특수한 용도로 사용됩니다.

- ECX : 반복분 명령어(LOOP)에서 반복 카운트로 사용됩니다.(루프 돌 때마다 ECX를 1씩 감소)

- EAX : 일반적으로 함수 리턴 값에 사용됩니다. 모든 WIN32 API 함수들은 리턴 값을 EAX에 저장한 후 리턴합니다.


* Windows 어셈블리 프로그래밍에서 주의할 점!

Win32 API 함수들은 내부에서 ECX와 EDX를 사용합니다.

따라서 이런 API 가 호출되면 ECX와 EDX의 값은 변경되어 버립니다.

따라서 ECX와 EDX어| 중요한 값이 저장되 어 있다면 API 호출 전에 다른 레지스터나 스택에 백업해야 합니다


나머지 범용 레지스터들의 이름은 아래와 같습니다.

이 범용 레지스터들은 주로 메모리 주소를 저장하는 포인터로 사용됩니다.


- EBP : Pointer to data on the stack (in the SS segment)

EBP는 함수가 호출되었을 때 그 순간의 ESP를 저장하고 있다가

함수가 리턴하기 직전에 다시 ESP에 값을 되돌려줘서 스택이 깨지지 않도록 합니다.

(이것을 Stack Frame 기법이라고 하며, 리버싱에서 중요한 개념입니다.)

- ESI : source pointer for string operations

- EDI : destination pointer for string operations

- ESP : Stack pointer (in the SS segment)

ESP는 스택 메모리 주소를 가리킵니다.

어떤 명령어들(PUSH, POP, CALL, RET)은 ESP를 직접 조작하기도 합니다.

(스택 메모리 관리는 프로그램에서 매우 중요하기 때문에 ESP를 다른 용도로 사용하지 말아야 합니다.)


ESI와 EDI는 특정 명령어들(LODS, STOS, REP MOVS 등)과 함께 주로 메모리 복사에 사용됩니다.




< 참고 >


각 레지스터들은 16비트 하위 호환을 위하여 몇개의 구획으로 나뉘어집니다.

EAX 기준으로 설명하겠습니다.


⊙ EAX : (0 ~ 31) AX의 확장 / 32비트

AX + Extended = EAX

⊙ AX : (0 ~ 15) EAX의 하위 / 16비트

AL(Upgrade) = AX

⊙ AH : (8 ~ 15) AX의 상위 / 8비트

AX(Upgrade) = AH

⊙ AL : (0 ~ 7) AX의 하위 / 8비트

제일 버전 낮아요!


AL -> AX / EAX -> AH


32비트를 다 사용하고 싶을 경우 EAX,

16비트만 사용할 때는 AX를 사용하면 됩니다.


AX는 다시 상위 8비트인 AH,

하위8비트인 AL로 나눠집니다.


이런 식으로 하나의 32비트 레지스터를 상황에 맞게 8, 16, 32비트로 알뜰하게 사용할 수 있습니다.






- 1.3.2 Segment Registers (16비트 - 6개)


세그먼트란 IA-32의 메모리 관리 모델에서 나오는 용어입니다.


IA-32 보호 모드에서 세그먼트란 메모리를 조각내어 각 조각마다

시작 주소, 범위, 접근 권한 등을 부여해서 메모리를 보호하는 기법을 말합니다.


또한 세그먼트는 페이징(Paging) 기법과 함께 가상 메모리를 실제 물리 메모리로 변경할 때 사용 됩니다.


세그먼트 메모리는 Segment Descriptor Table(SDT)이라고 하는 곳에 기술되어 있는데,

세그먼트 레지스터는 바로 이 SDT의 index를 가지고 있습니다.


- CS : Code Segment

- SS : Stack Segment

- DS : Data Segment

- ES : Extra(Data) Segment

- FS : Data Segment

FS 레지스터는 애플리케이션 디버깅에도 자주 등장하는데

SEH(Structured Exception Handling), TEB(Thread Environment Block),

PEB(Process Environment Block) 등의 주소를 계산할 때 사용됩니다. <- 고급 디버깅 내용

- GS : Data Segment


Code Segment : CS

Stack Segment : SS

Data Segment : DS, ES(Extra), FS, GS



위 그림은 보호 모드에서의 세그먼트 메모리 모델을 나타내고 있습니다

세그먼트 레지스터는 총 6개 (CS, SS, DS, ES, FS, GS)이며 각각의 크기는 16비트(2바이트)입니다.

그렘에 대해 좀 더 부연설명을 하면 각 세그먼트 레지스터가 가리키는 세그먼트 디스크립터(Segment Descriptor)와

가상메모리가 조합되어 선형주소(Linear Address)가 되며,

페이징 기법에 의해서 선형주소가 최종적으로 물리주소(Physical Address)로 변환됩니다.


※ 만약 OS에서 페이징을 사용하지 않는다면 선형주소는 그대로 물리주소가 됩니다.






- 1.3.3 Program Status and Control Register (32비트 - 1개)


EFLAGS : Flag Register


플래그(Flag) 레지스터의 이름은 EFLAGS이며 32비트(4바이트) 크기입니다.

(16비트의 FLAGS 레지스터의 32비트 확장 형태입니다)



* Flag(ZF, OF, CF)

. Zero Flag(ZF)

연산 명령 후에 결과 값이 0이 되면 ZF가 1(True)로 세팅됩니다.

. Overflow Flag(OF)

부호 있는 수(signed integer)의 오버플로가 발생했을 때 1로 세팅됩니다.

그리고 MSB(Most Significant Bit)가 변경 되었을 때 1로 세팅됩니다.

. Cany Flag(CF)

부호 없는 수(unsigned integer)의 오버플로가 발생했을 때 1로 세팅됩니다.


이 3개의 플래그가 중요한 이유는 특히 조건 분기 명령어 (Jcc)에서 이들 Flag의 값을 확인하고

그에 따라 동작 수행 여부를 결정하기 때문입니다.



EFLAGS 레지스터는각각의 비트마다 의미를 가지고 있습니다.

각 비트는 1 또는 0의 값을 가지는데, 이는 On/Off 또는 True/False를 의미합니다.

일부 비트는 시스템에서 직접 세팅하고, 일부 비트는 프로그램에서 사용된 명령의 수행 결과에 따라 세팅됩니다.


* Flag는 단어 그대로 깃발이 올라가면 1(On/True), 내려가면 0(Off/False)으로 이해하면 됩니다.






- 1.3.4 Instruction Pointer (32비트 - 1개)


EIP : Instruction pointer


Instruction Pointer는 CPU가 처리할 명령어의 주소를 나타내는 레지스터이며,

크기는 32비트(4바이트)입니다.(16비트의 IP 레지스터의 확장 형태입니다).


CPU는 EIP에 저장된 메모리 주소의 명령어(instruction)를 하나 처리하고 난 후

자동으로 그 명령어 길이만큼 EIP를 증가시킵니다.

이런 식으로 계속 명령어를 처리해나갑니다.


범용 레지스터들과는 다르게 EIP는 그 값을 직접 변경할 수 없도록 되어 있어서

다른 명령어를 통하여 간접적으로 변경해야 합니다.


EIP를 변경하고 싶을 때는 특정 명령어(JMP, Jcc, CALL, RET)를 시용하거나

인터럽트(interrupt), 예외(exception)를 발생시켜야 합니다.


< 참고 >

레지스터 이름에 E(Extended)가 붙은 경우는 예전 16비트 CPU인 IA-16 시절부터 존재하던

16비트 크기의 레지스터들을 32비트 크기로 확장시켰다는 뜻




향후에 실력이 쌓이면 고급 디버깅(커널 디버깅, 안티 디버깅)을 위해서 IA-32 Architecture와 다른 레지스터도 공부하는게 좋다고 하네요.






2. 스택 (5)


스택 : 로벌 변수 저장, 함수 파라미터 전달, 복귀 주소 저장 등의 다양한 용도로 사용


프로세스에서 스택 메모리의 역할

1. 함수 내의 로컬 변수 임시 저장

2. 함수 호출 시 파라미터 전달

3. 복귀 주소(return address) 저장






- 2.1.1. 스택의 특징


일반적으로 스택 메모리는 아래 그림과 같이 표현합니다.

 


프로세스에서 스택 포인터(ESP)의 초기 값은 Stack Bottom쪽에 가깝습니다.

PUSH 명령에 의해서 Stack에 값이 추가되면 스택 포인터는 Stack Top을 향해 (위쪽으로) 움직이고,

POP 명령에 의해 스택에서 값이 제거되면 스택 포인터는 Stack Bottom을 향해 (아래쪽으로) 움직입니다.


즉 높은 주소에서 낮은 주소 방향으로 스택이 자라납니다. 위 그림으로 보면 아래 에서 윗방향으로스택이 자랍니다.

이러한스택의 특성 때문에 보통 "스택은 거꾸로 자란다" 라는 표현을 쓰기도 합니다.


이렇게 하는 이유는 스택이라는 단어는 뭔가를 쌓는다는 뜻이기 때문에

쌓을수록 위로 올라오는 것이 더 직관적이기 때문입니다.






- 2.1.2. 스택 동작 예제


Stack.exe



Ctrl + F9를 눌러서 초기 상태의 스택을 보러갑시다!







초기 상태의 스택 입니다.

스택 포인터 (ESP)의 값은 19FF84입니다.


우측 하단의 스택 창을보면 ESP가 가리키는주소와 그 값을 보여줍니다.

Step lnto[F7] 명령으로 401000 주소의 PUSH 100 명령을 실행합니다.







ESP의 값은 19FF80로 4바이트만큼 줄어들었습니다.

그리고 현재 스택 포인터(ESP)가 가리키는 주소 19FF80에는 PUSH 명령에 의해 100이 저장되어 있습니다.


즉, 스택에 값을 집어 넣었더니 ESP가 위쪽 방향으로 이동한 것을 볼 수 있습니다.

(ESP의 값이 4만큼 줄었습니다)


다시 한 번 Step lnto[F7] 명령으로 401005 주소의 POP EAX 명령을 실행합니다.







ESP의 값은 4바이트만큼 증가하여 19FF80에서 19FF84로 되었고, 스택은 초기 상태와 같아졌습니다.


즉, 스택에서 값을 꺼냈더니 ESP는 이래 방향으로 이동하였습니다.


정리하자면 스택에 값을 입력하면 스택 포인터 (ESP)는 감소하고, 스택에서 값을 꺼내먼 스택 포인터는 증가합니다.







3. crackme 분석 #1 (6)


abexcm1-voiees.exe


crackme라는 프로그램은 말 그대로 크랙 연습 목적으로 작성되어 공개된 프로그램입니다.

리버싱을 처음 시작할 때 간단한 crackme를 분석해보면 실제로 디버거와 디스어셈 코드에 익숙해지는 효과가 있습니다.


abex' crackme는 매우 간단해서 초보자에게 알맞고, 아주 유명하기 때문에 해외에는 물론 국내에도 이를 설명한 사이트가 많습니다.


3.1. abex' crackme #1


디버깅을 시작하기 전에 먼저 파일을 실행시켜서 어떤 프로그램인지 살펴봅니다.




부정적인 메시지 박스가 출력되면서 종료 되었네요.


바로 디버깅을 해보도록 하겠습니다.






디스어셈 코드를 살펴봅니다.







코드를 살펴보니 GetDriverType() API로 C드라이브의 타입을 얻어오는데

이걸 조작해서 CD-ROM 타입으로 인식하도록 만들어서

Title = "Error" 가 있는 곳이 아닌 Title = "YEAH!"가 있는 부분으로 가서

정상적인 메시지 박스가 출력되도록 하면 되는거였네요!







역시 계속 F8(Step Over)를 눌러서 정상적으로 진행을하니 Error가 나옵니다.





 


디스어셈을 살펴보니 401026 주소에 'JE SHORT 0040103D' 명령어가 적혀있네요.




* 'JE SHORT 0040103D'의 뜻


EAX(1)와 ESI(2) 비교

JE(Jump if Equal) 조건분기 명령

if : 두값이같으면 40103D로 점프하고,

else : 다르면 그냥 밑 (401028)으로 진행!






이것을 'JMP SHORT 0040103D' 명령어로 바꿔주세요. (변경 후, Assemble 클릭)




* 'JMP SHORT 0040103D'의 뜻


0040103D로 무조건 점프







크랙 완료







4. 스택 프레임 (7)


스택 프레임(Stack Frame) : 프로그램에서 선언되는 로컬 변수와 함수 호출에 사용


- 스택 프레림의 동작 원리를 이해

- 간단한 프로그램을 디버거를 이용하여 스택 프레임을 확인

- 간단한 어셈블리 명령어의 상세 설명


4.1. 스택 프레임


스택 프레임이란 ESP(스택 포인터)가 아닌 EBP(베이스 포인터) 레지스터를 사용하여

스택 내의 로컬 변수, 파라미터, 복귀 주소에 접근하는 기법을 말합니다.


ESP 레지스터의 값은 프로그램 안에서 수시로 변경되기 때문에 스택에 저장된 변수와

파라미터에 접근하고자 할 때 ESP 값을 기준으로 하면 프로그램을 만들기 힘들고

CPU가 정확한 위치를 참고할 때 어려움이 있습니다.


따라서 어떤 기준 시점(함수 시작)의 ESP 값을 EBP에 저장하고 이를 함수 내에서 유지해주면,

ESP 값이 아무리 변하더라도 EBP를 기준(base)으로 안전하게 해당 함수의 변수, 파라미터, 복귀 주소에 접근할 수 있습니다.


이것이 바로 EBP 레지스터의 베이스 포인터 역할입니다.


< 스택 프레임의 구조 >


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

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


... (여기서 ESP가 변경되더라도 EBP가 변경되지 않으므로 안전하게 정보에 엑세스할 수 있음)


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

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

RETN : 함수 종료




스택 프레임을 이용하여 함수 호출을 관리한다면

아무리 함수 호출 depth가 깊고 복잡해져도 스택을 완벽하게 관리할 수 있습니다.


< 참고 >


- 최신 컴파일러는 최적화(Optimization) 옵션을 가지고 있어서

간단한 함수 같은 경우에 스택 프레임을 생성하지 않습니다.


- 스택에 복귀 주소가 저장된다는 점이 보안 취약점으로 작용할 수 있습니다.

buffer overflow 기법을 사용하여 복귀 주소가 저장된 스택 메모리를 의도적으로 다른 값으로 변경할 수 있습니다.





4.2. stackframe.exe 리버싱


< 파일 >


StackFrame.exe


< c++ 소스 >


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "stdio.h"
 
long add(long a, long b)
{
    long x = a, y = b;
    return (x + y);
}
 
int main(int argc, char* argv[])
{
    long a = 1, b = 2;
    
    printf("%d\n", add(a, b));
 
    return 0;
}

cs


* 스택 프레임이 잘 적용되기 위해서는 Visual C++의 최적화 옵션을 끄고(/Od) 빌드해야 합니다.



올리디버거로 StackFrame.exe를 열어줍니다.







'00401000' 주소로 빠르게 이동합니다.






'00401000' 주소를 BP로 설정하시고 실행(F9 또는 'Ctrl + F9')을 해주세요.







'00401000', '00401020' 주소가 눈에 보입니다.


하지만 '00401000' 주소로 가기전에 '00401020' 를 먼저 지나치는 것을


확인한 저는 재실행 시켜서 '00401020' 주소에 BP를 걸어주고 실행 시켰습니다.








< 소스 >


int main(int argc, char* argv[])

{



'00401020' PUSH EBP에 도착하시면 ESP와 EBP의 주소값을 메모해주세요.


저는 (ESP, 0019FF3C), (EBP, 0019FF80) 이네요.


특히 (ESP, 0019FF3C)에 저장된 값 00401250은 mainO 함수의 실행 이 끝난 후,

돌아갈 리턴 주소(Retum Address)라는 걸 알아두시길 바랍니다.







스택에 값이 쌓이면서 ESP의 값이 '0019FF3C' 에서 '0019FF38' 로 내려갔습니다. (스택 크기가 커졌습니다)


-> '0019FF38' 주소에는 '0019FF80' 이라는 값을 저장함으로써 main() 함수가 끝나기 전 까지 EBP의 오리지널 값을 잃지 않도록 했습니다.







MOV EBP, ESP 명령어를 실행 시켜서 EBP는 ESP와 같은 값을 같게 됩니다.


ESP = 0019FF38

EBP = 0019FF80 -> 0019FF38






올리디버거의 스택 창에서 EBP 위치를 확인하도록 하겠습니다.


스택 창에서 오른쪽 클릭 누르기 -> ('Address -> Relative to EBP')



그리고 다음으로 SUB ESP, 8 명령어를 실행 시켜주세요.







< 소스 >


long a = 1, b = 2;



EBP의 위치가 잘 나오는 것을 확인 했습니다.



Long Type이 a, b가 있기 때문에 4+4=8 만큼 자리를 만들어야 합니다.


SUB ESP, 8 명령어를 실행 시켜서 ESP의 주소값을 감소 시킵니다. (ESP 8감소)












MOV DWORD PTR SS: [EBP-4], 1

MOV DWORD PTR SS: [EBP-8], 2


EBP-4에 1를, EBP-8에 2를 저장합니다.







< 소스 >


printf("%d\n", add(a, b));



MOV EAX, DWORD PTR SS: [EBP-8]

PUSH EAX

MOV ECX, DWORD PTR SS: [EBP-4]

PUSH ECX



* 레지스터 말고 스택 창을 봐주세요.


EBP-8에 저장되었던 값을 EAX에 저장 (00000003)

스택에 EAX값을 푸쉬 (00000003) -> 2

EBP-4에 저장되었던 값을 ECX에 저장 (00000002)

스택에 ECS값을 푸쉬 (00000002) -> 1







Arg1, Arg2에 대해서 확인했으니 이제 add()로 이동해야 합니다.


그러기 위해서는 리턴이 있는 곳 까지 실행을 해야합니다.







< 소스 >


    return 0;

}



CALL 명령어가 실행되어 해당 함수로 들어가기 전에 CPU는 무조건 해당 함수가 종료될 때 복귀할 주소(return address)를 스택에 저장합니다.


EBP-40 0019FF40 == ESP / 0019FF40







'00401000' add()로 이동합니다.







< 소스 >


long add(long a, long b)

{



add() 함수의 시작 지점 입니다.







원래의 EBP 값(main() 함수의 Base Pointer)을 스택에 저장 후

현재의 ESP(Stack Pointer)를 EBP에 입력합니다.


이제 add()의 스택 프레임이 생성되었습니다.

add() 함수 내에서 EBP 값은 고정됩니다.







< 소스 >


long x = a, y = b;



SUB ESP, 8 명령어가 실행되고


스택 크기는 늘어나고 ESP 주소 값은 내려갔습니다.







MOV EAX, DWORD PTR SS: [EBP+8]

-> EAX = ([EBP+8])1

MOV DWORD PTR SS: [EBP-8], EAX

-> [EBP-8] = (EAX)1

MOV ECX, DWORD PTR SS: [EBP+C]

-> ECX = ([EBP+C])2

MOV DWORD PTR SS: [EBP-4], EAX

-> [EBP-4] = (ECX)2


EBP-8 / 0019FF18 / 값 : 1

EBP-4 / 0019FF1C / 값 : 2

EBP / 0019FF20 / 값 : 0019FF38

EBP+4 / 0019FF24 / 값 : return

EBP+8 / 0019FF28 / 값 : 1

EBP+C / 0019FF2C / 값 : 2

EBP+10 / 0019FF30 / 값 : 2

EBP+14 / 0019FF34 / 값 : 1







< 소스 >


return (x + y);



EBP-8 / 0019FF18 / 값 : 1

EBP-4 / 0019FF1C / 값 : 2

EBP / 0019FF20 / 값 : 0019FF38

EBP+4 / 0019FF24 / 값 : (00401041)return

EBP+8 / 0019FF28 / 값 : 1

EBP+C / 0019FF2C / 값 : 2

EBP+10 / 0019FF30 / 값 : 2

EBP+14 / 0019FF34 / 값 : 1


MOV EAX,DWORD PTR SS: [EBP-8]

-> EAX = 1

ADD EAX,DWORD PTR SS: [EBP-4]

-> EAX += 2 / EAX = 3


* 함수가 리턴하기 직전에 EAX에 어떤 값을 입력하면 그대로 리턴 값이 됩니다.







< 소스 > // 위에 소스랑 조금 달라요 :)


return (x + y);

}



MOV ESP, EBP

-> ESP는 EBP에서 원래 자신의 주소를 받습니다.



< ? >

즉, add() 함수 시작할 때의 ESP값을 EBP에 넣어 두었다가

함수가 종료될 때 ESP를 원래대로 복원시키는 목적으로 사용하는 것







POP EBP

-> EBP가 스택에 저장한 원래 자신의 주소값을 돌려 받습니다.


* 이 값은 main() 함수의 EBP 값 입니다.

* add() 함수의 스택 프레임은 해제 되었습니다.



스택 창을 보시면 (0019FF24 / 값 : 00401041) 을 보실 수 있는데요.

이 값은 앞에서 CALL 401000 명령에서 CPU가 스택에 입력한 복귀 주소 입니다.







0040101B RETN 명령어가 실행 되면서

메인 창에서는 00401041 주소로 (복귀 주소로)이동 했습니다.








ADD ESP, 8

-> add() 파라미터의 찌꺼기를 제거합니다. / 파라미터에 있던 x, y



파라미터 x와 y는 각각 Long Type이였기 때문에 4+4=8의 공간을 지웁니다.



< 정리 >


스택에게 ADD란...

-> 공간 지우기

스택에게 SUB란...

-> 공간 만들기







< 소스 >


printf("%d\n", add(a, b));



PUSH StackFra.0040B384 명령어를 확인하고나서

덤프 창에서 '0040B384' 를 입력하고 심심해서 확인 해봤습니다.







잘 있네요 :D


이걸 수정하면 도스창에서 나오는 내용을 바꿀 수 있습니다.







PUSH EAX

-> (인자1) 리턴값 스택에 삽입 / add() 함수의 리턴 값 입니다.

PUSH StackFra.0040B384

-> (인자2) '%d\n'

CALL StackFra.00401067

-> printf('%d\n', EAX) -> (output)$EAX\n







ADD ESP. 8

-> 스택에서 함수 파라미터를 정리







< 소스 >


return 0;



XOR EAX, EAX

-> main() 함수의 리턴 값을 0으로 세팅합니다.







< 소스 >


return 0;

}



MOV ESP, EBP

POP EBP


메인함수가 종료 되었고, add()와 마찬가지로 리턴하기 전에 스택 프레임을 해제합니다.







RETN 명령어가 실행 되었습니다.

-> ESP-44 / 0019FF3C / 값 : (00401250)return

-> 메인 창에서 00401250 주소로 이동





4.3. 올리디버거 옵션 변경


4.3.1. Disasm 옵션


< 초기 옵션 >







< 설정 후 >








4.3.2. Analysis1 옵션



< 초기 옵션 >







< 설정 후 >