* 어셈블리어 목적파일 생성 후 링킹작업으로 실행 프로그램 만들기

helloworld를 출력하는 어셈블리어 코드

nasm -f elf64 -o helloworld.o helloworld.s
- -f elf64 : ELF64로 파일 형식 지정
- -o helloworld.o : 출력 파일의 이름 지정
- helloworld.s : 어셈블리 코드 파일의 이름
-> helloworld.s 파일을 읽어와서 64비트 elf형식의 오브젝트 파일 'helloworld.o'로 어셈블링
ld -o helloworld helloworld.o
- ld : GNU Linker를 실행하는 명령어
- 링커는 여러 오브젝트 파일을 결합하여 실행파일을 생성하는 역할
-> 링커를 사용하여 elf형식의 오브젝트 파일을 실행 파일로 링크하는 명령어
* x64 syscall 표
https://rninche01.tistory.com/entry/Linux-system-call-table-정리x86-x64

rax : 특정한 함수가 끝날 때 그 반환값을 갖는 레지스터
-> rax는 함수 실행결과가 담김.
* 어셈블리어로 에코 프로그램 만들기
echo.s 코드 작성
section .text
global _start
_start:
xor rax, rax #rax 값 0으로 초기화 -> system call 0인 sys_read를 불러옴
mov rbx, rax #rbx 값 0으로 초기화
mov rcx, rax #rcx 값 0으로 초기화
mov rdx, rax #rdx 값 0으로 초기화
sub rsp, 64 #RSP에서 64를 빼주는 것은 64만큼 공간을 확보하겠다는 의미

mov rdi, 0 #read니깐 fd를 0으로 초기화
mov rsi, rsp #rsi는 출발지 주소를 넣는 레지스터, rsp부터
mov rdx, 63 #63만큼 문자를 입력 받겠다.
syscall #맨 처음 rax를 0으로 초기화했으니, syscall 표를 보면 0은 read
mov rax, 1 #syscall 1은 write
mov rdi, 1 #write니깐 fd를 1로 초기화
mov rsi, rsp #rsp부터
mov rdx, 63 #63까지 출력해라.
syscall #rax를 1로 초기화했으므로, write를 불러옴
mov rax, 60 #rax 60은 syscall표에서 exit / 즉 프로그램 종료를 의미

syscall # 프로그램 exit
nasm -f elf64 -o echo.o echo.s
-> echo.s 어셈블리어 코드로부터 echo.o 목적파일 생성
ld -o echo echo.o
-> echo.o 목적파일로부터 echo 실행파일 링킹

* 어셈블리어 기본 문법
MOV : A의 값을 B의 값으로 옮긴다.
MOV EAX, 100 : EAX에 100이라는 값을 넣는다. / 다만 구체적인 연산을 포함할 수 없다.
LEA : A의 값을 B의 값으로 연산을 포함하여 복사한다.
LEA, EAX, [EAX + 1000] : EAX에 1000을 넣은 값을 다시 EAX에 삽입한다. 이처럼 연산을 포함할 수 있다.
JMP : 특정한 윙치로 건너 뛰어 코드를 실행한다.
JMP A : A의 위치로 뛰어서 코드가 실행 된다. 비슷하게 조건 점프 명령도 존재한다. JA, JB, JE 등의 명령어는 두 인자를 받아서 비교한 뒤에 결과에 따라서 다른 방향으로 점프할 수 있다.
CALL : 함수를 호출했다가 다시 원래 위치로 돌아올 때 사용한다. JMP와 다른 점은 실행한 뒤 끝나게 되면 RET에 저장하고 다시 원래 상태로 돌아온다는 점이다.
NOP : 아무 것도 하지 않는 명령어, 1Byte의 빈 공간을 차지한다.
RET : 현재 함수가 끝난 뒤에 돌아갈 주소를 지정하기 위해 사용
PUSH : 스택에 해당 값을 넣는다.
POP : 스택에 있는 값을 빼낸다.
LEAVE : 현재까지의 메모리 스택을 비우고 RBP를 자신을 호출한 메모리 주소로 채운다. 실행 중인 함수를 종료하기 위해 정리하는 작업에 사용된다.
* 어셈블리어로 반복문 구현
loop.s 코드 작성
section .data
msg db "A" # 문자열 'A'를 담은 변수 msg 선언
section .text
global _start #start 함수 정의
_start:
mov rax, 1 # syscall 1은 write
mov rdi, 1 # fd를 1로 설정 / 어떠한 것을 출력하겠다는 의미
mov rsi, msg # 'A'가 담긴 변수 msg의 시작 주소를 rsi에 담음(rsi는 출발지 주소!)
mov rdx, 1 # rsi시작 주소부터 한 글자 가져옴 / 'A' 문자열 하나만 가져오겠다는 의미
mov r10, 1 # r10 레지스터의 값을 1로 설정(r10은 잘 사용하지 않는 레지스터라 가져와서 사용)
again:
cmp r10, 100 # r10이 100인지 비교해서
je done # 100이라면 done이라는 함수로 이동
syscall # 100이 아니라면 syscall을 불러서 A문자 출력
mov rax, 1 # 다시 rax에 1을 넣어서 출력할 수 있도록 만들어 줌
inc r10 # r10을 1증가 -> r10이 100이 될 수 있도록 만들어 줌
jmp again # 다시 again 함수 실행
done:
mov rax, 60 # syscall 60은 exit
mov rdi, 0 # rdi 0으로 초기화
syscall # exit
nasm -f elf64 -o loop.o loop.s
-> loop.s 어셈블리어 코드로부터 loop.o 목적파일 생성
ld -o loop loop.o
-> loop.o 목적파일로부터 loop 실행파일 링킹

* 디버깅 strace 명령어
strace -ifx ./echo # echo 파일 strace 명령으로 실행

read상태로 사용자 입력 대기 중

입력값으로 'A' 5개 넣어줌
버퍼값인 63바이트 중 'A' 5개와 \n 까지 6바이트를 입력 받은 것을 확인할 수 있고, 뒤에 남은 버퍼(57바이트)는 null값으로 초기화되어 있는 것을 확인할 수 있음.
'CTF > dreamhack(pwnable)' 카테고리의 다른 글
[Dreamhack] shell_basic (1) | 2024.03.24 |
---|