이 글은 워게임(rev-basic-0)을 풀고 정리하면 추가로 공부하면 좋을 내용을 정리한 것이다.
어셈블리어 못 읽는게 이번 워게임 풀 때는 크게 문제가 되지 않았지만, 어느 정도 알아두는 편이 좋을 것 같았다. 그래서 드림핵의 리버싱 트랙의 Assembly Essential Part를 읽으며 공부했다. 아래 내용은 해당 부분의 요약본이다.
x86 Assembly🤖 : Essential Part(1), Essential Part(2)
어셈블리 언어(Assembly Language) : 컴퓨터의 기계와 치환되는 언어. 어셈블리어의 종류도 다양하다.
- 어셈블러(Assembler) : 일종의 통역사로, 개발자가 어셈블리어로 코드를 작성하면 기계어로 코드를 치환해준다.
- 역어셈블러(Disassembler) : 기계어를 어셈블리 언어로 번역해준다.
x64 어셈블리 언어
기본 구조 : 명령어(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 |
피연산자
- 상수(Immediate Value)
- 레지스터(Register)
- 메모리(Memory) : []으로 둘러싸인 것으로 표현된다. 앞에 크기 지정자(Size Directive) TYPE PTR이 추가될 수 있다. 타입에는 BYTE/WORD/DWORD/QWORD가 올 수 있고, 각각 1바이트/2바이트/4바이트/8바이트의 크기를 지정한다.
*메모리 피연산자의 예
- QWORD PTR [0x8048000] : 0x8048000의 데이터를 8바이트 참조
- DWORD PTR [0x8048000] : 0x8048000의 데이터를 4바이트 참조
- WORD PTR [rax] : rax가 가르키는 주소에서 데이터를 2바이트 만큼 참조
x86 어셈블리 명령어
1. 데이터 이동
- mob dst, src : src에 들어있는 값을 dst에 대입
- lea dst, src : src의 유효 주소(Effective Adress, EA)를 dst에 저장
2. 산술 연산
- add dst, src : dst에 src의 값을 더함
- sub dst, src : dst에서 src의 값을 뺌
- inc op : op의 값을 1 증가
- dec op : op의 값을 1 감소
3. 논리 연산 - AND, OR
- and dst, src : dst와 src의 비트가 모두 1이면 1, 아니면 0
- or dst, src : dst와 src의 비트 중 하나라도 1이면 1, 아니면 0
*논리 연산 예시
#AND 연산자
[Register]
eax = 0xffff0000
ebx = 0xcafebabe
[Code]
and eax, ebx
[Result]
eax = 0xcafe0000
#OR 연산자
[Register]
eax = 0xffff0000
ebx = 0xcafebabe
[Code]
or eax, ebx
[Result]
eax = 0xffffbabe
4. 논리 연산 - XOR, NOT
- xor dst, src : dst와 src의 비트가 서로 다르면 1, 같으면 0
- not op : op의 비트 전부 반전
*논리 연산 예시
#XOR 연산자
[Register]
eax = 0xffffffff
ebx = 0xcafebabe
[Code]
xor eax, ebx
[Result]
eax = 0x35014541
#NOT 연산자
[Register]
eax = 0xffffffff
ebx = 0xcafebabe
[Code]
xor eax, ebx
[Result]
eax = 0x35014541
5. 비교 : 두 피연산자의 값 비교, 플래그 설정
- cmp op1, op2 : op1과 op2 비교, 두 피연산자를 빼서 대소를 비교한다. 연산 결과를 op1에 대입하지 않는다.
- test op1, op2 : op1과 op2 비교, 두 피연산자에 AND 비트연산을 취한다. 연산 결과를 op1에 대입하지 않는다.
*cmp에서 같은 두 수를 뺀 결과 = 0, ZF 플래그 설정. -> CPU는 이 플래그를 보고 두 값이 같았는지 판단.
6. 분기 : rip를 이동시켜 실행 흐름 변경
- jmp addr : addr로 *rip를 이동시킴
- je addr : 직전에 비교한 두 피연산자가 같으면 점프(jump if equal)
- jg addr : 직전에 비교한 두 연산자 중 전자가 더 크면 점프(jump if greater)
*RIP(Instruction Pointer, 프로그램 카운터) : 현재 실행 중인 명령어의 메모리 주소를 저장하는 레지스터, 쉽게 말해 다음에 실행할 명령어가 어딨는지 가리키는 레지스터
7. 스택
- push val : val을 스택 최상단에 쌓음
- pop reg : 스택 최상단의 값을 꺼내서 reg에 대입
*스택 예시
#push 예제
[Register]
rsp = 0x7fffffffc400
[Stack]
0x7fffffffc400 | 0x0 <- rsp
0x7fffffffc408 | 0x0
[Code]
push 0x31337
#push 결과
[Register]
rsp = 0x7fffffffc3f8
[Stack]
0x7fffffffc3f8 | 0x31337 <- rsp
0x7fffffffc400 | 0x0
0x7fffffffc408 | 0x0
#pop 예제
[Register]
rax = 0
rsp = 0x7fffffffc3f8
[Stack]
0x7fffffffc3f8 | 0x31337 <- rsp
0x7fffffffc400 | 0x0
0x7fffffffc408 | 0x0
[Code]
pop rax
#pop 결과
[Register]
rax = 0x31337
rsp = 0x7fffffffc400
[Stack]
0x7fffffffc400 | 0x0 <- rsp
0x7fffffffc408 | 0x0
*RSP(Stack Pointer, 스택 포인터) : 스택의 최상단을 가리키는 레지스터 (변동됨)
8. 프로시저(Procedure) : 특정 기능을 수행하는 코드 조각
호출(Call) : 프로시저를 부르는 행위
반환(Return) : 프로시저에서 돌아오는 것
프로시저 호출 시, 실행 후 원래의 실행 흐름으로 돌아와야 하므로 call 다음의 명령어 주소(Return Address, 반환 주소)를 스택에 저장. 후에 프로시저로 rip를 이동시킴.
- call addr : addr에 위치한 프로시저 호출
- leave : 스택프레임 정리
- ret : return address로 반환
*스택프레임 : 함수별로 서로가 사용하는 스택의 영역을 명확히 구분하기 위해 사용, 대부분의 ABI(Application binary interface)에서 함수는 호출될 때 자신의 스택프레임을 만들고 반환할 때 이를 정리한다.
*RBP(Base Pointer) : 스택 프레임의 기준점이 되는 레지스터, 함수가 시작될 때 현재 rsp 값을 rbp에 저장(고정)
'보안 > 드림핵 강의' 카테고리의 다른 글
[Reverse Engineering]Binary & Analysis 정리 (0) | 2025.03.31 |
---|---|
[Web Hacking]Cross-site Request Forgery (CSRF) 정리 (1) | 2025.02.04 |
[Web Hacking]Cross-site-Scripting (XSS) 정리 (0) | 2025.02.01 |
[Web Hacking]Cookie & Session 정리 (0) | 2025.02.01 |
[Web Hacking]Background - Web 정리 (0) | 2025.02.01 |