Fascination
article thumbnail

Reversing Engineering - 3. puts("hello world!\n") → x86_64 asm


1. hello world

# hello world - 1

- "hello world!"를 출력하는 코드가 어떤식으로 어셈블리 코드로 바뀌었는지 확인

#include <stdio.h>
int main(){
    puts("hello world!\n");
    return 0;
}

> 소스는 visual studio code 2019를 통해 64비트 릴리즈 모드로 컴파일

 

 

# hello world - 2

- 보는 방법

hello world! 어셈블리 코드

 

1. 주소: 해당 어셈블리 코드의 시작 주소가 표시됨

2. 기계 코드

  > 사람이 읽을 수 있는 어셈블리 코드의 전 단계인 기계어가 여기에 표시됨

  > : 앞에 있는 값은 prefix이고 띄어쓰기 다음에 있는 부분은 어셈블리 코드의 2번쨰 인자 부분

3. 어셈블리어

  > 비교적 사람이 읽기 쉬운 형태인 어셈블리 코드가 여기에 표시됨

  > 주소값의 경우 x64dbg가 적절한 형태로 바꾸어서 보여주기도 함

  > 세번째 줄을 보면 &puts라고 표시해주는 것을 확인할 수 있음

4. 코멘트

  > x64dbg가 프로그램을 분석하여 알게 된 추가적인 정보가 여기에 표시됨

  > 2번째 줄을 보면 7FF6ED802220에 있는 문자열에 대한 정보가 표시되는 것을 확인할 수 있음

  > 디스어셈블리 창에 보이는 숫자는 설정에 따라 0x가 없어도 16진수로 읽어야 하는 경우가 많음

  > 상단에 있는 디스어셈블 결과의 경우 0x가 없지만 전부 16진수로 표기되어 있음

 

 

2. hello-world - 디스어셈블리 결과 살펴보기

# hello-world - 디스어셈블리 결과 살펴보기 - 1

sub rsp, 28

- 해당 명령어는 rsp에서 0x28만큼 빼 함수 내부에서 사용할 스택의 용량을 확보하는 명령어

- 보통 함수에서 지역변수로 선언한 값들이 스택에 위치하게 되는데, 이 지역변수들이 저장될 공간을 컴파일러가 미리 계산하여 함수 시작 부분에 확보를 해두는 것

- 앞의 예시에서 소스코드와 디스어셈블 결과를 살펴보면 지역변수를 사용하는 곳이 없었음

  > 그런데도 0x28만큼 스택을 확보한 이유는 shadow space 또는 home space라 불리는 공간을 확보하고 성능 향상을 위한 메모리 사용 최적화가 적용되었기 때문

 

lea rcx,qword ptr ds:[7FF6ED802220]

- 해당 명령어는 rcx에 0x7FF6ED802220 값을 저장

- 0x7FF6ED802220: 주소값이며 x64dbg가 생성한 디스어셈블 결과의 코멘트 부분을 확인해보면 puts의 첫번째 인자인 hello world!\n가 위치한 주소임을 알 수 있음

- x64에는 많은 레지스터들이 존재하는데, puts의 첫번째 인자를 rcx에 넣은 이유는 사람들이 정해둔 규칙이 있기 때문

  > 이를 calling convention(함수 호출 규약)이라 함

  > 함수 호출 규약은 한 가지가 아니며 여러 종류가 있음

 

 

# 64비트 windows의 함수 호출 규약

- windows의 함수 호출 규약은 다음과 같은 순서로 첫 4개의 인자를 받음

1. rcx(ecx, cx, …)
2. rdx(edx, dx, …)
3. r8(r8d, r8w, …)
4. r9(r9d, r9w, …)

  > 이후 5번째 인자부터는 스택에 넣게됨

- 함수의 리턴 값은 rax(eax, ax, ...)에 저장

- 8개의 인자를 받는 함수의 디스어셈블 결과

8개의 인자를 받는 함수의 디스어셈블 결과

> rcx(ecx), rdx(edx), r8(r8d), r9(r9d)에 4번째 인자까지 넣고

5~8번째 인자는 미리 확보해둔 스택 영역에 저장하는것을 확인할 수 있음

 

 

# hello-world - 디스어셈블리 결과 살펴보기 - 2

 

hello world! 어셈블리 코드

call qword ptr ds:[<&puts>]

- puts를 호출하는 명령어

 

xor eax,eax

- eax를 0으로 만들어주는 명령어

- main 함수의 리턴 값을 0으로 설정해놨기 때문에 리턴값을 의미하는 eax레지스터를 0으로 설정하는 것

- 이때 mov eax, 0을 쓰지 않고 xor을 통해서 하는 이유명령어의 길이가 짧고 CPU에서 좀 더 빠르게 실행시키기 때문

 

add rsp,28

- 함수 시작 시 확보해두었던 스택을 정리하는 명령어

 

ret

- 함수의 실행을 마치고 리턴하기 위해 사용하는 명령어

- call 명령어를 통해 스택에 저장된 return address로 들어감

profile

Fascination

@euna-319

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!