반응형


1.3. add() 함수 파라미터 입력 및 add() 함수 호출




add() 함수에는 a, b 2개의 파라미터가 존재합니다.

401034 ~ 40103B 주소의 명령어들은 변수 a, b 를 스택에 저장하고 있습니다.



[EBP-8]에는 2가 [EBP-4]에는 1이 저장되게 되는데 C언어의 입력 순서와는 반대로 2부터 저장이 되고있습니다.


1.3.1 복귀주소



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



CALL 명령어 ( 40103C )를 실행한 직후 스택에는 복귀주소가 저장되어 있습니다.


1.4. add() 함수 시작 & 스택 프레임 생성




① StackFrame 게시물의 Main() 함수 부분과 동일합니다.

원래의 EBP 값을 스택에 저장 후 현재의 ESP를 EBP에 넣습니다.


1.5. add() 함수의 로컬 변수( x, y )세팅




① : 스택 메모리 영역 ( 8 byte ) 확보

② : [ EBP + 8 ] = a

③ : [ EBP - 8 ] = x

④ : [ EBP + C ] = b

⑤ : [ EBP - 4 ] = y



1.6. ADD 연산




① : EBP-8에 있는 변수 x의 값 1을 MOV명령어를 통해 EAX로 넣는다.

② : EBP-4에 있는 변수 y의 값 2를 ADD명령어를 통해 EAX의 값 1과 더한다 = 3


1.7. add() 함수의 스택 프레임 해제 & 함수 종료 ( 리턴 )


이제 add() 함수의 스택 프레임을 해제 시키고 함수를 리턴시킬 차례입니다.



앞선 MOV EBP, ESP에 대응하는 명령어입니다.

add() 함수가 시작할 때 ESP값을 EBP에 넣어 보존했다가 함수가 종료될 때 ESP값을 복원시키는 겁니다.



앞선 PUSH EBP에 대응하는 명령어입니다.

add() 함수가 시작되면서 스택에 백업한 EBP값을 복원하고 스텍프레임을 해제시킵니다.


이제, RETN명령어를 실행시키게되면 스택에 저장된 복귀 주소로 리턴하게됩니다.



add() 함수를 호출하기 전의 스택상태로 돌아왔습니다.


1.8. add() 함수 파라미터 제거 ( 스택 정리 )



add() 함수가 완전히 종료되었기 떄문에 파라미터 a,b가 필요 없습니다.

따라서 ESP에 8을 더하여 스택을 정리하는 부분입니다.


1.9. printf() 함수 호출


자, 이제 프로그래밍 공부를하며 늘 써왔고 궁금했던 printf() 에 대해 알아보겠습니다.



이전의 과정에서 EAX에 1과 2를 더한 3을 넣어놨었습니다.

PUSH 40B384에는 %d가 들어있고 CALL 401067 명령어는 printf() 함수입니다.

printf() 안에 2개의 파라미터가 있기 때문에 ADD ESP, 8 로 정리해줍니다.


1.10. 리턴 값 세팅




'XOR' Exclusive OR bit 연산은 같은값 끼리 XOR하면 0이 되는 연산입니다.

MOV EAX, 0 명령어보다 실행속도가 빨라 자주 사용된다고합니다.


XOR 아래의 코드들은 나머지 부분은 앞에서했던 MOV, POP RETN 으로 같은 동작을 하며 메인 함수가 종료됩니다.



이번 실습을 진행하면서 막히는 부분도 많았고 꽤 어려웠습니다.

하지만 스택 프레임을 어설프게나마 공부해보니 실력이 많이 향상된 것 같고 리버싱 공부 의지가 더욱 불타는 것 같습니다.


오늘은 여기까지입니다 감사합니다 !




참고서적 이승원 「리버싱 핵심원리」 인사이트(2018) p82~91

반응형




안녕들 하시죠!


오늘은 스택 프레임에 대해 알아보겠습니다.


스택 프레임은 프로그램에서 선언되는 로컬 변수와 함수 호출에 사용됩니다.

이번 실습을 진행하기 위해서는 지난시간에 공부했던 'IA-32 Register' 의 ESP, EBP 개념이 필요합니다.


ESP는 스택포인터로서 프로그램 안에서 ESP 레지스터는 수시로 변경되기 때문에 스택에 저장된 변수, 파라미터에 접근하고자 할 때 ESP 값을 기준으로 하면 프로그램을 만들기 힘들고, CPU가 정확한 위치를 참고할 때 어려움이 있습니다.

따라서, 어떤 기준 시점( 함수 시작 )의 ESP값을 -> EBP에 저장하고 이를 함수내에서 유지해줍니다.

그러면 ESP값이 아무리 변하더라고 EBP를 기준으로 안전하게 해당 함수의 변수, 파라미터, 복귀 주소에 접근이 가능합니다.

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


PUSH EBP  

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


MOV EBP, ESP

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


///


함수 본체 

여기서 ESP가 변경되더라도 EBP에 저장되어 있으므로 안전하게 로컬 변수와 파라미터를 엑세스 할 수 있다.


///


MOV ESP, EBP

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


POP EBP

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


RETN

함수종료


이런 식으로 스택 프레임을 이용해 함수 호출을 관리하면 스택을 완벽하게 관리할 수 있습니다.


이제, 아주 간단한 프로그램을 통해 스택 프레임을 이해해보겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
// StackFrame.cpp
#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


위에있는 코드를 StackFrame.exe로 빌드한 후 Ollydbg를 이용해 열어보겠습니다.

아래에 파일도 같이 첨부하겠습니다.


StackFrame.exe



Go to 명령어로 401000 주소로 이동하겠습니다.


위에 올렸던 코드를 부분부분 찾아 이해해보겠습니다. 


1. StackFrame.exe


1.1. Main() 함수 


우선, Main() 함수 부분 ( 201020 ) 주소 에 BP를 설치[F2] 한 후 [F9]로 실행하겠습니다.




ESP 19FF3C, EBP 19FF80 이고 아래 ④번을 보시면 ESP가 가리키고 있는 19FF3C에 저장된 값 401250은 Main() 함수의 실행이 끝난 후,

돌아갈 리턴 주소입니다.


00401020        PUSH EBP

위의 명령은 'EBP값을 스택에 집어넣어라' 라는 뜻입니다. 

Main() 함수에서 EBP가 베이스 포인터의 역할을 하게 될테니 EBP가 이전에 가지고 있던 값을 스택에 백업해두기 위함입니다.


00401021        MOV EBP, ESP

위의 명령은 'ESP값을 EBP로 옮겨라' 라는 뜻입니다.



이젠 ESP와 EBP 둘다 19F88값을 가지게 되었구요, 19FF80 주소에는 19FF80이라는 값이 저장되어 있습니다.

19FF80은 Main() 함수가 시작할 때 EBP가 가지고 있던 초기 값입니다.


1.2. 지역 변수



로컬 변수 ( a, b )를 위한 공간을 만들고 값을 입력한다.

위의 명령은 'ESP 값에서 8을 빼라' 라는 뜻입니다.



위의 자료형 크기표를 보시면 long 은 4byte로 되어있습니다.

a 와 b 는 long 타입으로 선언되었습니다.

long 타입이 2개이니 8byte를 SUB로 빼주면 a와 b 두 변수가 스택에 저장될 공간이 생기게 됩니다.


위의 명령은 '[EBP-4] 에는 1을 넣고, [EBP-8] 에는 2를 넣어라' 입니다.



다음 게시물에 이어서...



참고서적 이승원 「리버싱 핵심원리」 인사이트(2018) p75~82

+ Recent posts