어셈블리어와 x86-64

어셈블리 언어

컴퓨터의 기계어와 치환되는 언어로 기계어가 여러 종류 있다면 어셈블리어도 그만큼 다양하게 존재한다.

ISA에는 IA-32, x86-64, ARM 등등 여러 종류가 있는데 이 종류만큼의 어셈블리어가 존재하는 것이다.

드림핵에서는 x86-64 어셈블리 언어만 다룬다.

 

 

 

 

 

x86-64 어셈블리 언어

기본 구조

명령어(Operation Code, Opcode) + 피연산자(Operand)

 

 

 

 

명령어

명령 코드
데이터 이동(Data Transfer) mov, lea
산술 연산(Arithmetic) inc, dec, add, sub
논리 연산(Logical) and, or, xor, not
비교(Comparison) cmp, test
분기(Branch) jmp, je, jg
스택(Stack) push, pop
프로시져(Procedure) call, ret, leave
시스템 콜(System call) syscall

 

 

 

피연산자

피연산자에는 3가지 종류가 있다.

 

  • 상수(Immediate Value)
  • 레지스터(Register)
  • 메모리(Memory)

 

메모리 피연산자는 []으로 둘러싸인 것으로 표현되며, 앞에 크기 지정자(Size Directive) TYPE PTR이 추가될 수 있다.

타입에는 BYTE, WORD, DWORD, QWORD가 올 수 있으며, 각각 1바이트, 2바이트, 4바이트, 8바이트의 크기를 나타낸다.

 

더보기

👇 메모리 피연산자의 예

 

 

 

 

 

x86-64 어셈블리 명령어

데이터 이동

어떤 값을 레지스터나 메모리에 옮기도록 지시하는 명령

 

 

 

 

예제 문제

 

 

 

 

 

산술 연산

덧셈, 뺄셈, 곱셈, 나눗셈 연산을 지시하는 명령어

 

 

 

 

예제 문제

 

 

 

 

 

논리 연산 - and & or

and, or 등의 비트 연산을 지시하는 명령어

 

 

 

 

예제 문제

 

 

 

 

 

논리연산 - xor & not

xor, neg 등의 비트 연산을 지시하는 명령어

 

 

 

 

예제 문제

 

 

 

 

 

비교

두 피연산자의 값을 비교하고, 플래그를 설정하는 명령어

 

 

 

 

 

 

분기

rip를 이동시켜 실행 흐름을 바꾸는 명령어

 

 

 

 

 

 

스택

스택을 조작하는 명령어

 

 

 

 

 

 

프로시저

프로시저(Procedure)는 특정 기능을 수행하는 코드 조각이다.

프로시저를 부르는 행위를 호출(Call), 프로시저에서 돌아오는 것을 반환(Return)이라고 부른다.

프로시저 호출 후에는 원래의 실행 흐름으로 돌아와야 하므로, 그 다음 실행할 명령어 주소가 필요하다.

call 다음의 명령어 주소(return address, 반환 주소)를 스택에 저장하고 프로시저로 rip를 이동시키면 된다.

 

 

 

→ 스택프레임의 할당과 해제: https://learn.dreamhack.io/63#6

 

ret(return address): 프로그램의 원래 실행 흐름 위치

 

 

 

 

 

시스템 콜

운영체제는 해킹으로부터 소프트웨어에 대한 권한을 보호하기 위해 커널 모드와 유저 모드로 권한을 나눈다.

 

커널 모드

운영체제가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한이다.

파일시스템, 입력/출력, 네트워크 통신, 메모리 관리 등 모든 저수준의 작업은 사용자 모르게 커널 모드에서 진행된다.

따라서 해커가 커널 모드까지 진입하게 되면 시스템은 거의 무방비 상태가 된다.

 

 

 

유저 모드

운영체제가 사용자에게 부여하는 권한이다.

유튜브를 시청, 게임, 프로그래밍을 하는 것 등은 모두 유저 모드에서 이루어진다. 리눅스에서 루트 권한으로 사용자를 추가하고, 패키지를 내려 받는 행위 등도 마찬가지다.

유저 모드에서는 해킹이 발생해도 해커가 유저 모드의 권한까지 밖에 획득하지 못하기 때문에 해커로 부터 커널의 막강한 권한을 보호할 수 있다.

 

 

 

시스템 콜(system call, syscall)

유저 모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용된다.

소프트웨어 대부분은 커널의 도움이 필요한데 도움이 필요하다는 요청을 시스템 콜이라고 한다.

 

ex) 사용자가 cat flag를 실행하면, cat은 flag라는 파일을 읽어서 사용자의 화면에 출력해야 한다. 이때 flag는 파일 시스템에 존재하므로 해당 파일을 읽기 위해선 파일시스템에 접근할 수 있어야 한다. 유저 모드에서는 이를 직접 할 수 없으므로 커널에 요청하여 해결한다. 이후 커널은 요청한 동작을 수행하여 유저에게 결과를 반환한다.

 

x64아키텍쳐에서는 시스템콜을 위해 syscall 명령어가 있다.

 

 

 

시스템 콜은 함수이기 때문에 필요한 기능과 인자에 대한 정보를 레지스터로 전달하면, 커널이 이를 읽어서 요청을 처리한다.

리눅스에서는 x64아키텍쳐에서 rax로 무슨 요청인지 나타내고, 아래의 순서대로 필요한 인자를 전달한다.

 

 

 

 

x64 syscall 테이블

 

syscall rax arg0 (rdi) arg1 (rsi) arg2 (rdx)
read 0x00 unsigned int fd char *buf size_t count
write 0x01 unsigned int fd const char *buf size_t count
open 0x02 const char *filename int flags umode_t mode
close 0x03 unsigned int fd    
mprotect 0x0a unsigned long start size_t len unsigned long prot
connect 0x2a int sockfd struct sockaddr * addr int addrlen
execve 0x3b const char *filename const char *const *argv
const char *const *envp

 

 

 

 

 


Quiz

 

1. rsi+rcx가 참조하는 메모리에서 1바이트를 가져온다.

2. 가져온 데이터를 0x30과 xor연산한다.

3. 연산한 값을 다시 데이터를 가져왔던 곳에 돌려놓는다.    →    rsi+rcx

4. rcx가 0x19보다 커질 때까지 반복한다.

 

결론 0x400000에서 0x40019까지 각 데이터에 0x30과 xor연산한다.

 

 

 

1. esi = 0xf, rdi = 0x400500

2. write_n 호출

3. [rbp-0x8]에 rdi값 넣기  →  0x400500

4. [rbp-0xc]에 esi값 넣기  →  0xf

5. rdx == 0

6. edx = 0xf

7. rsi = 0x400500

8. rdi = 0x1

9. rax = 0x1

10. syscall  →  write(0x1, 0x400500, 0xf)

 

결론 0x400500에서부터 문자 15개 출력.

 

:)

'Study > System Hacking' 카테고리의 다른 글

[dreamhack] Tool: pwntools  (0) 2022.03.27
[dreamhack] Tool: gdb  (0) 2022.03.26
[dreamhack] Linux Memory Layout  (0) 2022.03.25
[dreamhack] Computer Architecture  (0) 2022.03.25
[lazenca] Protection Tech / RELRO & PIC & PIE  (0) 2021.02.22
복사했습니다!