System Hacking/assembly

[assembly] 어셈블리어로 별피라미드 출력하기-(1)

jir4vvit 2020. 7. 8. 18:17

환경 : 칼리리눅스 2019.2

 

칼리 리눅스에서 어셈블리어를 이용하여 별 피라미드를 출력해보도록 하자.

아래와 같은 피라미드를 옆으로 눕힌 모양(?)을 찍어보도록 하겠다. 

별 피라미드를 찍는다면 어셈블리어 반복문을 제대로 이해할 수 있을 것이다.


어셈블리어로 출력하기 전에 java로 위의 피라미드를 먼저 찍어보자.

 

public class Main {

    public static void main(String[] args) {
	// write your code here

        int n = 5;

        // upPyramid
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < i; j++) {
                System.out.print("*");
            }
            System.out.println();
        }

        // downPyramid
        for(int i = n; 0 < i; i--) {
            for(int j = i; 0 < j; j--) {
                System.out.print("*");
            }
            System.out.println();
        }
        
    }
}

 

출력결과는 아래처럼 나오게 된다.

Main.java


어셈블리코드와 java코드를 보면서 차근차근 이해해보자.

 

section .data 부분은 메모리 구조에서 데이터 영역을 의미한다. 변수 설정을 여기에서 한다고 이해하면 된다.

section .text 부분은 메모리 구조에서 텍스트 영역을 의미한다. 여기에 실제 코드가 들어가게 되는 것이다. 컴퓨터는 이 부분을 차례대로 한 줄씩 읽으면서 프로그램을 실행하게 되는 것이다.

 

section .data 부분에 피라미드를 찍기 위한 '*'과 다음 줄로 넘어가기 위한 줄바꿈인 0x0a를 선언해주었다. 

그리고 section .text 영역에는 최초로 실행되는 함수인 _start를 정의해주었다. 

 

_start에는 여러가지 초기설정(?)들을 해주었다. wirte 시스템 콜을 위해 rax와 rdi에는 1을 넣어주고 출력길이는 우리는 별(*) 또는 줄바꿈(0x0a) 한글 자만 출력해주면 되기 때문에 1로 설정하였다. 그리고 잘 쓰지 않는 레지스터인 r10을 초기화해줬는데, 이는 반복적으로 어떠한 역할을 수행하기 위해 사용된다. 위의 Main.java에서 int j에 해당된다고 생각하면 편하다. 

 

mov r9, [rsp+16] 부분은 pyramid를 실행하면서 사용자가 입력한 문자열을 받는다. 매개변수를 받는다는 뜻이다. java에서는 argv, c에서는 args라고 생각하면 된다.

 

왜 rsp+16 위치인지 살펴보자.

위의 그림에서 5는 우리 사용자가 입력한 매개변수값이다. 

RSP가 위치한 공간에서 16을 더해준 곳이다. 

참고로 매개변수는 RET 아래 담긴다는 사실을 잊지말자.

 

여기서 왜 RSP에서 16을 뺀게 아니라 더해줬는지 헷갈릴 수도 있다.

저기는 스택공간이다. 스택은 나무처럼 아래에서 위로 자라기 때문이다. 메모리 공간이 커질수록 스택의 주소값을 작아지게 된다. 

 

 

다시 사진을 가져와서 _start 함수를 살펴보자. 

만약 매개변수가 없다면(사용자의 입력이 없다면) 프로그램을 종료시켜주도록 하자.

프로그램 종료는 je 명령을 이용해 _done 함수로 점프하여 _done 함수에서 구현해주도록 하자.

 

그리고 우리는 두자리수는 무시할 것이다. 예를들어 45를 입력한다면 맨 앞의 바이트만 잘라 4로 입력했다고 가정할 것이다. 이를 위해서 mov cl, [r9] 에서 한바이트만 cl에 저장해주자. 한바이트만 잘라서 cl에 저장해주고 다시 r9에 저장해주자. 참고로 r9는 우리가 입력한 수다.

 

sub r9, 0x30 을 해주자. 우리가 어떠한 매개변수를 넣어서 프로그램을 실행하게 되면 그 매개변수는 무조건 문자열 형태로 들어가게 된다. 한마디로 메모리상에서는 모두 16진수라고 저장이 되는 것이다.

아스키코드표를 살펴보면 우리가 입력한 5는 10진수의 5가 아니라 문자 5로 들어가게 되어 메모리상에서는 16진수인 0x35로 저장이 된다. 

 

아까 java코드를 생각해보면 우리가 입력한 수만큼 for문을 돌리게 된다. 어셈블리어에서 만약 저대로 냅두면 35바퀴를 돌게 되는 것이다.

 

그래서 10진수로 0을 의미하는 0x30을 빼주어서 0x05로 만들어서 5바퀴 돌리게 해줘야한다. 잘 이해가 되었는가..?

 

 

다시 아래를 계속 보자... 

내가 입력한 수를 r8에 저장하고 r9는 0으로 초기화된다.. 결국 r8은 위의 자바코드에서 n을 뜻하게 된다.

그러고 마지막으로 _syscall함수를 호출하고 있다.

_syscall 함수는 간단하게 syscall과 ret 명령밖에 없다.

피라미드 매개변수 입력을 위해서 _syscall함수를 호출해주었다. 덕분에 돌아올 곳까지 완벽..!

 

 

여기까지 온다고 고생많았다.

그 다음 별을 출력하기 위한 반복문 구현은 다음 포스팅에서 하도록 하겠다.