사용자에게 나이를 입력받아 if으로 비교한 뒤에 18 이상이면 Welcome My Club!!을 출력하게 하고 

18 아래이면 들어올수 없다는 문자를 출력하는 C언어를 컴파일하자.

 






메인함수로 들어오자.


PUSH ECX는 그냥 변수공간을 확보하기 위한 코드이다.

int age; 때문에 4바이트를 확보하기 위한 작업으로 보인다.




scanf에 들어가면 입력을 받을때 까지 기다린다. 16을 입력하면 아래 스택메모리에 10이 들어간 것을 볼 수 있다.

0x10은 10진수로 16이다.


ADD ESP,8

ADD에 8을 더해서 스택을 내린다.


여기서 CMP A,B는 CMP A-B을 한다.

빼기를 해서 결과가 0이면 같은것이다. 만약 0이아니라면 두개가 다르다는 뜻이다.


CMP DWORD PTR SS:[EBP-4],12

는 0x12는 18이다.

EBP는 0018FF44이다.

여기서 4를빼면 아까 SCANF에 입력한 곳에 스택 주소가 되므로 내가 입력한 값과 18을 비교하는 구문이다.





여기서 오른쪽에 Z 0 이라는것이 보이는데 이것으로 같은지 아닌지 판별을 한다.

지금은 16을 넣었으므로 18과 같지 않기 때문에 0이다.

그럼 18을 넣어보자.




Z플래그가 1이 된것을 볼 수 있다. 아래에 스택메모리에 12가 저장된 것을 보아 18을 입력했다.


다음으로 알아야할 플래그는 C인데 C는 캐리 플래그이다.

C 캐리플래그가 1이라는 것은 값을 빌려왔다는 뜻이고, 값을 빌려오면 1이 올라간다.

즉 C 캐리플래그가 1이라는것은 왼쪽이 더 작다는 뜻이다. EX)1-2 

C 캐리플래그가 0이라는것은 왼쪽이 더 크므로 크다 라는것을 판별한다





다음은 5를 입력한 상황이다. 0018FF40에 5가 들어갔고 C가 1이고 Z가 0이므로 

캐리플래그가 올라간 상태라 여기서 정한 18보다 작다는 뜻이된다. 이렇게 if문을 비교할 수 있다.


다음은 점프 구문에 대한 설명이다.

JGE는 ,Conditional Branch, 조건 분기이다.

JMP는 무조건 분기이다. JMP(주소)


JE [주소] =JUMP IF EQUAL 같다면 점프


JZ = JUM IF ZERO =제로플래그가 0이면점프X

JE=JZ


JNE = JUME NON EQUAL 

제로플래그 0이면 0이 아니므로 점프함


JG(JUMP IF GREATER THEN)

크다면 점프해라 (왼쪽이 크다는 뜻)

제로플래그는 0 캐리플래그가 0 이면 왼쪽이 큼


그럼 위에있는 JGE는 무엇인가?

JGE(JUMP IF GREATER OR EQUAL)

같거나 클때 점프한다 라는 뜻이다

Z=0 C=0 A>B이므로 점프 O

Z=0 C=1 A<B 이므로 점프 X

Z=1 C=0 A=B 이므로 점프 O



그럼 만약 이 파일을 조작한다고 하였을 때, JGE구문이 0x12보다 큰지 비교를 하여 "Welcome My Club!!"을

출력하기 때문에 이 구문을 조작하면 되겠다.


밑줄친 부분의 JGE를 JL로 바꾸면 JGE와 반대되는 의미이므로



다음과같이 조작을하면






점프해서 넘어간다





배열을 선언하고 그 안에 문자를 넣는 코드와

int형 변수 두개를 선언하여 출력하는 코드를 만든 뒤 릴리즈로 컴파일을 하자.





늘 하던대로 메인함수를 찾아서  F7을 눌러 들어간다.






스택프레임을 만드는 PUSHMOV가 등장했다.






ESP=EBP로 맞추어 놓았다가

ESP에 0x34를 빼주는데 이것은 스택에 0x34만큼 공간을 만들어준 것이다.





ESP와 EBP사이에 0x34의 공간이 생긴것을 볼 수 있다.

34는 10진수로 52이다.






밑에 이 명령어들은 변수들의 초기값을 지정해주는 명령어라 생각할 수 있다.

처음에 공간을 할당하고 EBP를 기준으로 주소를 빼면서 저장하는 것을 알 수 있따.






MOV EAX,DWORD PRT DS:[4020F4]

는 데이터 영역의 4020F4주소의 값 8바이트를 EAX에 MOV하라는 것이다.


그럼 4020F4에 내용에 뭐가있는지 직접 가본다.




이미 아스키코드로 데이터가 들어가 있다.

DWORD는 4바이트이므로 69 20 61 6D인 i am이 들어간다. 

그값이 EAX레지스터에 MOV 된다.


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

스택영역의 EBP-20(스택 마지막 부분에서 -20을 뺀 주소값)에 아까 가져온 i am을 넣어주는 명령어이다.

EAX에는 아까 가져온 데이터가 저장되어 있다.



이처럼 i am boy you are a girl! 을 다 가져와 스택에 넣으면 스택구조는 이처럼 변한다.


EDX의 반은 DX이고 DX의 반은 DL 이다

위에 나오는 DX는 EDX의 반의 영역이고

AL은 EAX의 반의 반 영역이다.








위를 보면 13,12를 해당 스택 주소값에 넣는 명령어가 나오는데 이것은 처음 코드에서 선언한 INT형 변수에 

19와 18를 넣어주는 명령어이다. 이 처럼 기계어에는 변수이름이 들어가지 않고 주소로 판단한다. 19와 18은 16진수로 

각각 13,12이다.

\

본 게시물은 리버싱에 대한 이해를 목적으로 제작되었습니다. 








리버싱을 위해 제작된 프로그램이다. 인터넷에 찾아보면 구해볼 수 있다.

게임에 돌아다니는 크랙파일들이 어떤식으로 제작되는지 알아보자.




구조는 아이디랑 비밀번호를 입력해서 OK을 눌러야 통과하는 것이다.

비밀번호가 틀리면 위와같은 메시지가 나온다.

프로그램의 주변 글씨같은걸 잘 봐보자.

KeygenME-3,Bengaly,OK,ABOUT 같은 글씨들이 보인다.






일단 F8을 누르다보면 CALL Key4.00401025 에서 프로그램이 실행되는 것을 알 수 있다.

저기 안에 실행되는 명령어가 있다는 뜻이니 F2로 브레이크포인트를 걸고 F7로 들어가본다.

막 내리다보면 다음과 같은걸 발견할 수 있었다.






You Have Entered A Wrong Serial, Please Try Again은 어디서 봤던 문장이다.

아까 잘못된 정보를 입력했을 때 나오는 텍스트창이다.

다시 잘 찾아보면







저 텍스트박스는 텍스트박스에 아무런 글자를 입력하지 않았을 때 나오는 문장일 것이다.

그 위에 보면 

JE SHORT Key4.004012DF

JMP SHORT KEY4.004012F6이 있는데

JE 구문은 뭔가를 비교한후에 조건이 맞다면 004012DF로 가는 명령어 같다.

004012DF는 아무런 문장이 입력되지 않았을 때 나오는 MessageBox의 주소이다.


004012F6도 바로 밑에서 찾을 수 있는데 String 구문을 받는 명령어 이다.


따라서 텍스트박스를 보고 아무것도 없다면 메세지박스를 띄우고

텍스트가 있다면 String으로 받는 구조이다.





계속 분석을 해보자

00401341번지를 보면 00401358로 가는데 이것은 실패했을때 메시지를 출력하는 곳으로 가는 주소이다.

바로 아래 00401343번지는 

"Great, You are ranked as Level-3 at Keygening now"가 보인다.


이것이 성공했을때 나오는 메세지 일 것이다.

그러면 우리는 JMZ SHORT Key4.00401358의 구문이 실행이 안되게하여 

자연스럽게 성공으로 넘어가게 만들면 되는 것이다.

따라서 이곳을 조작하여 점프하지 않게 만든다.






더블클릭을 하여 NOP를 입력한다.





조건구문이 실행되지 않게하여 바로 넘어가게 한다.

이제 F9를 쭉쭉 눌러서 프로그램을 명령어를 끝까지 실행한다.







아무거나 치고 OK만 누르면, 바로 "Great, You are ranked as Level-3 at Keygening now" 구문을 볼 수 있다.







이제 이대로 프로그램을 저장하면 된다.

오른쪽 클릭을 누르고 Copy to executable-All modification





Copy all을 클릭한다.




후에 나오는 창에 Save file을 한다.







Key4가 아까 정상 프로그램이고 Key4_Cracked이 지금 저장한 프로그램이다.








방금 만든 Key_Cracked에 들어가서 아무거나 입력하고 OK를 누르면






아이디와 비밀번호 없이 성공한것을 알 수 있다.


옛날 크랙 파일들이 이런식으로 제작이 되었다.

이것은 기초적인 프로그램 이므로 더 공부할 필요가 있겠다.




이번엔 변수를 받아와서 출력하는 c언어를 리버싱 해본다.





이번에도 메인함수를 잘 찾아서 f7을 눌러서 들어온다.





오른쪽에 현재 스택 프레임의 맨위를 나타내는 ESP가 0018FF48이다.

아래 오른쪽에 스택 메모리를 보면 0018FF48가 스택의 TOP인걸 알 수 있다.


EBP는 스택의 바닥의 주소이다.

ESP와 EBP사이를 Stack Frame이라고 한다.

가장 처음 명령어인

PUSH EBP

MOV EBP,ESP 

는 스택 프레임을 만드는 명령어이다.

메인함수를 호출당한 상태라서 가장 먼저 함수를 위한 스택프레임을 만드는 것이다.



PUSH EBP

는 EBP 주소 0018FF88를 Stack 메모리에 넣어주는 명령어고


MOV EBP,ESP 

ESP에 있는 값을 EBP에 덮어 씌우는명령어다.


이말은 즉 EBP와 ESP를 같게 한다는 뜻이므로 스택에 아무것도 없는 상태에서 스택 프레임이 생성되는 과정이라 본다.,






ESP와 EBP가 같아졌다.

main함수의 Stack Frame이 생성되었다.

그래서 저 명령어는 함수의 맨 앞부분에 자주 나온다.

Function Prologue라 한다.




ECX의 값이 스택에 PUSH 되었다.


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

의 의미는 무엇일까


 DWORD

BYTE는 1바이트

WORD는 2바이트

DWORD는 4바이트(더블워드)

QWORD는 8바이트(쿼드워드)이다.


SS:[EBP-4]

SS는 STACK 영역

DS는 DATA 영역

CS는 CODE영역을 의미한다.




위에 메뉴를 보면 m 이 있는데 클릭한다.






메모리 맵인데 이곳을 보면 스택, 데이터, 코드의 주소가 나와있다.

메모리맵이라고 한다.


[EBP-4]

[EBP-4]는 꺽쇠 안에 있으면 주소라고 생각한다.

따라서 현재 EBP 주소 0018FF44에 4를 뺀값이므로 0018FF40이다





0018FF40은 아까 ECX가 PUSH된 곳의 스택 주소이다. 

DWORD는 4바이트이므로 바로 옆에있는

7573B6F0의 주소를 의미한다.

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

7573B6F0의 값을 5로 바꾸라는 의미가 되겠다





00 00 00 05로 바뀐것을 볼 수 있다.

이렇게하는 이유는



코드 값을 보면 AGE의 값이 5 다. 

따라서 

PUSH ECX

MOV DWORD PTR SS:[EBP-4],5는 

int age=5;를 나타내는 명령어가 되겠다





그다음 

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

EAX에다가 아까 넣은 5의값이 EAX에 들어갈 것으로 예상할 수 있다.





005B1CA8이였던 EAX값이





00 00 00 05로 바뀌었다.


PUSH new.004020F4는 age %d를 가져오는 값이다. 

다음 CALL은 printf를 CALL하는 것이다


따라서


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

PUSH EAX

PUSH new.004020F4

CALL DWORD PTR DS:[<&MSVCR90.printf>]

코드에서 pirntf("age: %d",age)라고 할수 있겠다.


비쥬얼스튜디오와  

http://www.immunityinc.com/products/debugger/

이뮤니티 디버거를 다운받는다.



처음에 비쥬얼 스튜디오를 설정해주어야 한다.

프로젝트를 오른쪽 클릭하여 속성에 들어간다.




링커 - 고급 -임의기준 주소를 '이미지를 임의로 선택하지 않습니다.' 




일반-문자 집합 - '설정안함' 체크





최적화- 최적화 -'사용 안함' 체크





다음은 간단한 c파일을 생성하자


#include<stdio.h>

int main() {

printf("Hello World!");

return 0 ;

}


언어책 가장 먼저 피면 나오는 헬로우 월드 출력문이다.

이 파일을 디버깅 말고 릴리즈로 컴파일을 하자


릴리즈한 파일은

내 문서-Visual Studio [년도]-Project -[프로젝트이름]-Relese-[파일이름]

에 있다.




릴리즈로 컴파일이 끝났다면 이 파일을 이뮤니티 디버거에서 열어본다.

다음과같은 알 수 없는 어셈블리어들이 존재하지만 겁먹지 말자.


오른쪽 클릭해서 View-[릴리즈한 파일 이름] 을 클릭해서 main함수를 콜하는

CALL [파일이름].main 에 f2를 눌러서 브레이크를 걸어주자.

브레이크가 걸리면 f9를 눌러서 진행할 때, 그쪽에 자동으로 멈추게된다.

f2로 브레이크를 걸었다면 f7로 들어가서 main함수를 자세히 살펴보자



만약 찾지못하고 넘어갔을땐 Ctrl+F2로 다시 시작할 수 있다.



PUSH MOV..알수없는게 나오고 3번째 줄을보면 오른쪽에

format = "Hello World!" 라는 걸 볼 수있다.

기본적으로 가장 먼저 앞에 나오는 PUSH MOV ADD XOR 같은 것을

Mnemonic 이라고 한다.

어셈블리어는 Mnemonic [OP1] [OP2] 로 되어있다.

만약 ADD A,B 라면 A와 B를 더한값을 A에 저장해라 라는 뜻이다.


Push 바로 왼쪽을 보면 68 F4204000 이라고 써져있다.

또한 중요한 점이 있는데.


intel CPU는 메모리를 읽고 쓰는 주소를 거꾸로 해놓는다.

이것을 Little Endian 이라고 한다.


그리고 intel CPU는 Byte Machine이다.

여기서 Byte Machine은 메모리를 읽고쓰는 단위가 1바이트라는 뜻이다.


이 두개를 조합하면 F4204000이라는 주소값은 16진수이므로


2자리씩 끊어서 뒤로 읽어줘야 한다.


따라서 F4204000

004020F4 라는 주소가 된다.

이것을 이용해서 Hello World가 있는 주소값으로 가보도록 하자.
















크게 찍어놨는데 왼쪽위는 코드가 저장되어있는 메모리이다.

오른쪽 위는 레지스터의 값을 볼수 있는 창이다.

왼쪽 아래는 메모리 주소를 볼수 있는 창이다.

오른쪽 아래는 스택을 볼 수 있는 창이다.


왼쪽 아래에서 오른쪽 클릭을하여 Go To - Expression을 클릭하여서

방금 찾은 주소인 004020F4로 이동해보자




찾아보면 오른쪽에 Hello World!가 있는것을 확인 할 수 있다.





이제 F7를 한번 눌러 진행해보자. 진행할때 오른쪽 아래인 스택 메모리를 참조하자




한번 누르고..




한번 더누르니 스택값에 Hello World가 들어왔다.




f7을 누르니 mnemonic이 CALL이고 뒤에 보니 printf 를 호출하는것 같다고 이 tool이 얘기해준다.

그리고 f7을 누르면



이상한 주소값으로 가는데 놀라지말자. 여기는 printf의 영역이다.

printf는 내가 선언한 함수가 아니고 이미 선언되어 있는 것이므로 printf를 가져오는 과정이다.



스택메모리를 보면 올라온 것을 확인할 수 있다.

여기서 주목해야할 점은 Call to printf from hello.00401008 옆에있는

0040100E라는 주소값이다. 이 주소값은 함수가 CALL이 되었을 때, 어디로 주소를 복귀시켜야 하는지에 대한 설명이다.

CALL이 되어 printf에 들어가고 RETN을 만났을 때 0040100E라는 주소값으로 나온다는 뜻이다.





뭔가를 막 한다. F7를 눌러서 막 지나가다보면 RETN을 만난다. 

RETN을 만날때 까지가 printf 함수에 대한 내용이다.

여기서 F7을 눌러주면



아까 적혀진 0040100E로 돌아왔다.




그 다음 명령어인 ADD ESP,4에 대해 알아보자




오른쪽 위에 있는 레지스터메모리를 주목하자




ESP:현재 스택의 최상단 주소를알려준다

Extended stack pointer

EBP:현재 스택의 최하단의 주소를 알려준다

Extended Base Pointer

EAX,EBX,ECX,EDX ->범용 레지스터

필요할때 사용하는 레지스터이다.


또한 레지스터의 사이즈는 32비트이다 . 이것은 32비트 CPU라는 것을 의미한다.

64비트 CPU는 레지스터 크기가 64비트이다.



그렇다면 ADD ESP,4 는 

현재 스택 최상단 주소값에 4를 더하라는 뜻이다. 이말은 무엇이냐면

Hello World! 라는 문자열이 이제 더 이상 쓸모가 없으니 주소값을 4를 더해 

스택의 최상단을 한단계 위에있는 주소를 가르키도록 하는 것이다.

이 말은 즉 Hello World 값을 버리겠다는 뜻이다.


그 다음인 XOR EAX,EAX 은 무엇일까.















레지스터 값을 보면 EAX에 0000000C가 저장되어 있다.

똑같은 값에 XOR 연산을 하면 모두 0으로 변한다.


따라서 이 명령어는 EAX레지스터를 초기화 시키는 명령어가 되겠다.




EAX가 초기화 되었다.

여기서 알아두어야 할 점이 하나 더있다.

 여기서 나오는 MOV라는 함수는 MOVE가 아니라 Copy라는 뜻이다

MOV A,B 는 A에 있는걸 B에다가 Copy를 한다는 뜻이다.

그렇다면 MOV 를 이용하여서 EAX값을 0으로 초기화 시켜줄 수 있지 않을까?


어셈블리어는 기계어므로 기계한테는 비트연산이 훨씬 빠르다.

따라서 MOV를 사용하지않고 XOR를 사용하여 초기화 하는 것이다.

F7을 눌러서 Main함수의 RETN을 만나서 나오면




아까 브레이크를 걸어놨던 곳 바로 아래로 나오게 된다.





오른쪽 레지스터 값에 EIP를 확인해 보자

EIP는 다음 명령어의 주소를 적어논 레지스터 이다.

다음 명령어 주소인 0040117E가 적혀져있는것을 확인할 수 있다.


이제 응용하여 2가지 장난을 쳐보자.



1.화면에 FUOK YOO 표시하기 (Hello World 대신에)

2.MAIN 리턴하면 printf로 다시 돌아가서 Hello World를 두번 찍기





1. 화면에 FUOK YOO 표시하기.






아까 Hello World!가 저장된 주소를 가서, 글자 하나를 클릭하여 컨트롤+e를 누른다.

눌러서 unicode란에 하나 하나 바꿔줘보자.







그러면 Hello World!는 사라지고 바뀐 FUOK YOU!!가 보일 것이다.



2.Hello World 2번찍기





00401014 주소에 있는 RETN 값으로 F7을 눌러 이동한 뒤에 오른쪽 아래에있는 스택에 복귀주소를 바꿔준다.

0040117E라고 되어있는 주소를 Hello World! 를 PUSH하는 주소를 적어주면 된다.

00401003 을 적어주자.

그러면 RETN문이 끝나고 메인함수를 빠져나가지 않고 바뀐 복귀주소인 00401003으로 이동하여서 

Hello World!를 한번 더 찍게된다.






바꿔준다,. 오른쪽 클릭을 해서 Modify를 누르면 된다.





두번 찍힌 모습이다.

다음에는 scanf를 이용한 간단한 리버싱을 진행해보겠다.



+ Recent posts