반응형

어셈블리어

우리가 기존에 배우던 c, c++, Java 같은 언어들이 고급언어이고, 어셈블리어는 기계어(기계어는 0과 1로만 이루어진 것임)와 거의 가까운 저급언어라고 합니다. 프로그래밍 언어가 기계어에 가까울수록 어려운 대신 명령 실행 속도가 빠릅니다. python같은 언어를 문제 풀 때 실행시켜보면 메모리를 엄청 많이 잡아먹고 실행속도가 C보다 더 느리다는 것을 알 수가 있죠.

우선 linux에서 할 것이기 때문에 우분투와 가상머신을 다운로드 해주세요(과정은 생략)

그리고 terminal에 sudo apt-get install nasm을 치면 다운로드가 완료됩니다.

그럼 시작해볼게요!!

 

 

어셈블리어(Assembly Language)로 Hello World 출력하기

 

nano 파일명.s: nano는 에디터 프로그램으로 마치 메모장처럼 어떤 내용을 작성할 수 있는 프로그램을 말함. 즉 .s를 통해 에셈블리 소스코드 파일을 생성하겠다는 것을 말함.

 

 
nano프로그램을 실행시킨 모습

 

이것을 치시면 됩니다.

 

여기서 data는 프로그램에서 사용되는 문자열 같은 데이터를 저장할 수 있는 공간을 의미합니다.

text: 위에서부터 차례대로 읽어내려가는 소스코드 자체를 의미
_start: C언어에서 main 함수보다 먼저 실행되는 함수
mov: 어떠한 레지스터 안에 특정한 값을 넣는 하나의 명령어
mov rax, 1: rax라는 레지스터 안에 1을 넣겠다는 뜻

 

프로그램을 끝낼 때는 ctrl x를 누르고 y를 눌러주고 enter를 치면 나올 수 있습니다.

여기서 cat helloworld.s를 치게 되면 helloworld 파일의 내용을 볼 수 있습니다.

 

 

 

nasm -f elf64 -o helloworld.o helloworld.s: 만든 파일을 목적코드로 변환
ld -o helloworld helloworld.o: 실행할 수 있는 프로그램으로 만들어줌
ls를 쳐서 실행할 수 있는 프로그램이 생겨난 것을 볼 수 있음(linux의 경우 연두색으로 표시된 것은 실행할 수 있음을 뜻함)
./helloworld를 치시면 파일이 실행되면서 Hello World가 뜨게 됩니다.

 

만약에 세그멘테이션 오류가 뜨셨다면 section .data를 써야하는데 section.data라고 붙여쓰신 경우일겁니다.

편집
nano helloworld.s를 누르면 다시 편집할 수 있습니다. 또는
vi helloworld.s: vi 편집기 실행, 편집을 할 수 있게 됨

 

여기서 i를 누르셔야 편집을 할 수 있게 됩니다.

나갈 때: ESC를 누르면 INSERT라는 문구가 사라짐

:q : 나감
:w : 저장
:wq : 저장하고 나가기

 

vi 편집기에 대해 더 자세히 알고 싶다면

https://dog-developers.tistory.com/43

 

vim 설치 및 설정

https://norux.me/13

 

 
파일이 제거 되었음을 알 수 있음

 

rm 파일: 파일 제거(Remove)

 

 


 

레지스터의 용도와 시스템 콜 이해하기

 

 
위에서부터 64비트, 32비트, 16비트, 8비트짜리 레지스터를 보여줌

 

64비트는 레지스터 이름으로 앞에 R이 쓰인다는 것을 알 수 있고, 32비트는 E라는 것을 알 수 있네요.

 

 

 
레지스터의 크기 비교
 

 

 

RDX의 반이 EDX, 그것의 반이 DX, DL이네요.

 

//데이터 레지스터

★RAX: system call의 실질적인 번호를 가르키는 레지스터이자 함수가 실행된 다음 그 결과가 담기는 레지스터
RBX: base register로 메모리 주소를 저장할 때 쓰임
RCX: Counter register로 주로 반복문에 많이 사용됨
RDX: Data register로 RAX와 함께 많이 사용됨

 

//pointer register

RSI: 메모리를 이동하거나 비교할 때 그 출발지 주소를 가리킬 때 사용
RDI: 메모리를 이동하거나 비교할 때 그 목적지 주소를 가리킬 때 사용
RBP: 함수의 파라미터나 변수의 주소를 가리킬 때 사용
★RSP: stack의 삽입 및 삭제 명령에 의해서 변경되는 스택에서 가장 위에 있는 주소를 가리키는 하나의 레지스터
나머지 r8~r15는 함수의 매개변수로서 사용됨

 

//시스템 콜(system call)

 

 
64 bit system call table

 

table을 더보고 싶다면

https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/

 

%rax: 위에서 설명했던 레지스터죠. 어떠한 시스템 콜 함수를 불러올 때 그 번호를 가르킴.

즉, 시스템 콜 함수를 사용하기 전에 rax에 0이라는 값이 담겨 있다면 sys_read 함수를 사용한다고 해석하시면 됩니다.

예를 들어 sys_write 같은 경우에 rdi는 매개변수로서 fd(file descriptor) 역할을 한다는 것을 알 수 있고, rsi에는 어떤 문자를 출력할지, 출력할 문자열의 길이는 rdx에 담으면 됩니다.

 

 

 

그전에 썼던 걸 가져와봤는데요, 이제 해석할 수 있게 되었습니다.

rax에 1을 할당함으로써 system_write를 호출한다고 얘기를 해주고, rdi에 fd에 1을 넣음으로써 우리가 어떤 문자열을 출력하겠다고 시스템 콜 함수 매개변수로 넣어주고, rsi에 msg를 넣음으로써 msg, 즉 Hello World를 출력할 수 있게 한 후 rdx에 12을 넣어줌으로써 문자열 길이를 정해준 후 syscall을 써줘 Hello world를 출력하게 만듭니다.

밑에 rax에 60을 할당해줬는데요, 60은 sys_exit로 프로그램을 종료한다는 뜻입니다.

 

 

 

 

error_code를 써줘야 하는데 rdi에 0을 넣음으로써 안전하게 종료할 수 있습니다.

 

어셈블리 명령어

 

mov x, y
xy
and x, y
xx and y
or x, y
xx or y
xor x, y
xx xor y
add x, y
xx + y
sub x, y
xxy
inc x
xx + 1
dec x
xx – 1
syscall
Invoke an operating system routine
(운영체제에게 기능을 수행해달라고 하는 하나의 수단)
db
A pseudo-instruction that declares bytes that will be in memory when the program runs
(프로그램을 실행할 때 메모리를 선언하는 의사 명령)

 


 

메모리 구조 이해하기

 

 
메모리의 구조

 

 

 

Stack: 한 쪽 끝에서만 자료를 넣고 뺄 수 있는 LIO(Last In First Out) 형식의 자료 구조

스택은 취약점이 많이 발생하는 공간입니다.

 

 

 
stack

 

 

heap: 동적으로 할당되는 변수들이 위치하는 공간 Malloc에서 할당된 데이터들의 공간
BSS: 프로그램에서 사용된 변수(초기화가 되지 않은 변수)들이 실제적으로 위치해 있는 공간
Data: 프로그램에서 사용된 변수(초기화가 된 변수)들이 실제적으로 위치해 있는 공간
Text: 실제로 작성한 소스코드들의 공간

 

 


 

스택 프레임(Stack Frame) 이해하기

 

스택과 관련된 c파일을 만들어보겠습니다.

nano sum.c
 

이걸 쳐주세요

 

 

 

gcc: C코드를 컴파일하는 프로그램
nasm: 에셈블리어를 컴파일하는 프로그램

 

gcc 설치

https://byd0105.tistory.com/9

 

 

 
sum이라는 c언어 파일을 assembly코드로 바꿔주는 명령어
vi sum.a
 
이것을 입력해 어셈블리 파일을 열어주면
 

 

이렇게 어셈블리어 형태로 바뀐 것을 볼 수 있습니다:) esc->:q 누르셔서 나간 후 터미널에서

nano sum.c
 

를 치면 아까 작성했던 c파일이 나옵니다.

 

 
sum.c에서 main 함수의 stack 프레임

 

 

RET(return address): 특정한 함수가 끝난 후에 돌아갈 장소
RBP: 스택이 시작하는 base pointer
변수 C: int c 를 선언

 

 

 

 

 

 

위에서부터 하나씩 꺼내지면서 변수 C에 3이 담기게 되고 C가 return 값으로 가면서 main을 불러온 곳으로 돌려주게됩니다.

sum파일을 실행

sum파일에 들어가서 printf를 추가해서 c를 출력하게 만들수도 있습니다.

여기서 stack frame의 취약점이 RET를 바꾸어서 특정 함수가 돌아가는 위치를 해커가 임의로 조작해서 서버에 나쁜짓을 끼칠 수 있다는 것입니다. 대표적인 기법들로 버퍼오버플로우가 있습니다.

 

sum파일의 어셈블리어 코드(main 부분)

 

main에서 call sum 부분을 봐서 sum 함수를 불러왔다는 것을 알 수 있습니다.

pushq %rbp: rbp를 push한다
movq %rsp, %rbp : rbp에 rsp를 대입한다.
subq $16, %rsp: rsp에서 메모리16을 뺀다
movl $2, %esi : esi에 2를 대입한다
movl $1, %edi : edi에 1을 대입한다
call sum : sum 함수를 불러온다

 

음.. 근데 nasm에는 mov x,y : x←y, 즉 y를 x에 대입한다고 했는데 실제로는 반대네요...아무래도 무언가 뒤에 붙어있으면 위치를 바꿔주나봅니다...(아니면 어셈블리어로 바꿀 때 시스템이 바꿔준다던가??)

 

mov나 push뒤에 붙는 l,b,w는 오퍼랜드의 크기를 뜻합니다.

movl: 32bit 크기를 다룸
movw: 16bit 크기를 다룸
movb: 8bit 크기를 다룸

 

 

call sum 전까지

 

pushq %rbp: rbp를 push한다
movq %rsp, %rbp : rbp에 rsp를 대입한다.
movl %edi, -4(%rbp) : rbp의 -4 위치에 edi를 대입한다
movl %esi, -8(%rbp) : rbp의 -8 위치에 esi를 대입한다
addl %edx, %eax : edx와 eax의 값을 더한 후 eax에 넣어준다
popq %rbp : rbp를 꺼낸다
eax는 rax와 마찬가지로 특정한 함수가 끝날 때 그 반환값을 가지고 있는 레지스터

 

sum()

 

call sum후에

movl %eax, -4(%rbp): eax를 -4위치 rbp에 넣어준다
movl -4(%rbp), %eax: -4위치 rbp를 eax에 넣어준다
leave, ret(return)후 최종적으로 3이라는 반환값을 가지게 됨

 

 


어셈블리어(Assembly Language)로 에코 프로그램 만들기

 

에코 프로그램: 내가 입력한 문자를 그대로 출력해주는 프로그램

nano echo.s를 쳐서 파일을 만들어보겠습니다

 

똑같은 값을 xor 해주게 되면 0이라는 값이 담깁니다. mov rax, 0 과 같습니다. 즉 0으로 초기화해주겠다는 것이죠

 
 
AND
OR
XOR
0 0
0
0
0
0 1
0
1
1
1 0
0
1
1
1 1
1
1
0

 

mov rbx, rax~ mov rdx, rax까지는 0으로 초기화 해준다는 뜻입니다.

sub rsp, 64: rbx=rsp이기 때문에 rsp에 6를 빼줘 데이터를 담을 수 있게 합니다.

rax값에 따라 syscall이 바뀐다고 했었습니다.

fd=0 : 무언가를 읽음
fd=1 : 무언가를 씀

 

mov rsi, rsp : rsi에 rsp의 주소값이 들어간다

mov rdx, 63 : 63만큼 문자열을 불러온다.

즉 사용자가 문자를 입력하고 그 문자를 읽고 rax에 저장합니다.

그다음 rax에 1을 대입하고, rdi에 1을 대입합니다.

sys_write를 불러오면서 그 문자를 출력해주게 됩니다.

rax에 60을 대입해 끝내줍니다.

echo 파일을 목적프로그램으로 변환해 프로그램을 실행합니다.

 

 


 

어셈블리어(Assembly Language) 기본 문법

- 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: 현재까지의 메모리 스택을 비우고 EBP를 자신을 호출한 메모리 주소로 채움. 실행 중인 함수를 종료하기 위해 정리하는 작업에 사용됨.

 


어셈블리어(Assembly Language)로 반복문 구현하기

 

nano loop.s
 

loop 파일을 만들어볼게요

mov rax 60, syscall 하지 않으면 세그멘테이션 오류가 뜹니다.(참고하세요)

종료까지 적어줍니다.

해주면 A가 출력됩니다.

이제 A가 여러 번 출력되도록 만들어 볼게요~~

 

cmp: 어떠한 것을 비교하는 compare 함수임

cmp r10, 100: r10과 100을 비교하여

je: jump equal, 비교 결과가 같을 때 점프
je done: r10이 100이면 done으로 간다
syscall: 100이 아니면 syscall 한다
mov rax, 1: rax는 함수를 실행하면 어떤 값이 담기기 때문에 1로 다시 초기화합니다
inc r10: r10을 1증가시킨다
jmp again: again으로 다시 돌아간다
done: 함수를 끝낸다

 

 

더 많은 명령어를 알고 싶다면

http://pyrasis.com/blog/entry/ReverseEngineeringForNewbieAssemblyOperation

 

 

출처:

https://youtu.be/uOIq-P2eQXs

:동빈나의 시스템 해킹 강좌(15강까지 있습니다.)

 

참고:

https://cs.lmu.edu/~ray/notes/nasmtutorial/

 

 

반응형

'Hacking > Assembly' 카테고리의 다른 글

어셈블리어(Assembly Language) 기초 정리  (0) 2026.02.15

+ Recent posts