기계어를 어셈블리 언어로 번역하는 역어셈블러.
어셈블리어만 이해할 수 있다면 역어셈블러를 사용하여 소프트웨어를 분석할 수 있다.
어셈블리 언어는 기계어와 치환되는 언어. 🤖
CPU에 사용되는 명령어 집합구조(ISA)는 x86-64 등 종류가 다양하기 때문에 이에 따른 어셈블리어의 종류도 다양하다.
ex) x64 → x64 어셈블리어
x64 어셈블리어
[ 문법 구조 ]
명령어 (Operation Code, Opcode) & 피연산자 (Operand)
mov eax, 3 -> 대입해라 eax에, 3을
opcode operand1, operand2
피연산자
1. 상수
2. 레지스터
3. 메모리 ex) QWORD PTR [0x8048000] : 0x8048000의 데이터를 8바이트만큼 참조
[ OPCODE(명령어) ]
1. 데이터 이동
mov QWORD PTR[rdi], rsi : rsi의 값을 rdi가 가리키는 주소에 대입
lea dst, src : src의 유효 주소(EA)를 dst에 저장
2. 산술 연산
add dst, src : dst에 src의 값을 더함
sub dst, src : dst에서 src의 값을 뺌
inc op : op의 값을 1 증가시킴
dec op : op의 값을 1 감소시킴
3. 논리 연산
and dst, src : dst와 src의 비트가 모두 1이면 1, 아니면 0
or dst, src : dst와 src의 비트 중 하나라도 1이면 1, 아니면 0
xor dst, src : dst와 src의 비트가 서로 다르면 1, 같으면 0
not op : op의 비트 전부 반전
(이상하게 어렵고 헷갈림 잘 모르겠음 ^_ㅠ.. 하..)
11111111111100000000000000000000
AND
11001010111111101011101010111110
=
11001010111100000000000000000000
4. 비교
cmp op1, op2 : op1에서 op2를 빼서 대소 비교, 결과값은 대입하지 않음 -> ZF(Zero Flag)를 설정, 같으면 1
test op1, op2 : op1과 op2에 AND 비트연산, 결과값은 대입하지 않음, 플래그를 설정
5. 분기: rip(현재 실행 중인 명령어 주소 저장, Instruction Pointer)를 이동시켜 실행 흐름을 바꿈
jmp addr : addr로 rip를 이동
je addr : 직전에 비교한 두 피연산자가 같으면 addr로 rip 이동 (jump if equal)
jg addr : 직전에 비교한 두 피연산자 중 전자가 더 크면 addr로 rip 이동 (jump if greater)
6. 스택
push val : rsp를 8만큼 빼고, val을 스택 최상단에 쌓음
pop reg : 스택 최상단의 값을 reg에 넣고, rsp를 8만큼 더함
7. 프로시저
: 프로시저 사용 시 반복되는 연산을 프로시저 호출로 대체할 수 있어 코드 크기를 줄일 수 있고 가독성이 높아짐
- 호출(call, 프로시저 부르는 행위) <-> 반환(return, 프로시저에서 돌아오는 것)
프로시저 호출 시, 프로시저를 실행하고 다시 실행 흐름으로 돌아와야 하므로 call 다음의 명령어 주소(반환 주소)를 스택에 저장하고 프로시저로 rip(현재 실행 중인 명령어 주소 저장, Instruction Pointer)를 이동시킴
call addr : addr에 위치한 프로시저를 호출
leave : 스택프레임 정리
ret : return address로 반환. 호출자의 실행 흐름으로 다시 돌아감.
시스템 콜
- OS는 컴퓨터 자원의 효율적인 사용을 위해 커널모드와 유저모드로 권한을 나눔
? 커널모드 : OS가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한. 파일시스템, 입출력, 네트워크 통신, 메모리 관리 등의 작업이 사용자 모르게 진행됨.
? 유저 모드 : OS가 사용자에게 부여하는 권한
? 시스템 콜 : 유저 모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용됨. syscall 명령어
syscall : 커널에게 필요한 동작을 요청
x64 syscall table이 따로 있음. 시스템콜은 함수임.
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 |
x64 syscall table의 일부임. 나머지는 검색해서 찾아볼 수 있음. 외우기 어렵다 300개라..
☆ rsp - stack pointer, 스택의 삽입 및 삭제 명령어에 의해 변경되는 스택의 가장 위를 가리키는 포인터
☆ rbp - 함수 파라미터나 주소를 가리킬 때 사용
☆ 명령어 포인터 레지스터 (Instruction Pointer Register, IP)
: CPU가 실행해야할 코드를 가리키는 레지스터, x64 아키텍처의 명령어 레지스터는 rip이며, 크기는 8바이트.
☆ rax - 시스템콜의 실질적 번호를 가리키는 번호, 함수 결과값이 담기는 레지스터
Quiz: x86 Assembly 1
Quiz: x86 Assembly 2
1) push rbp : 스택에 rbp를 저장한다. (rbp = 스택의 시작점)
2) mov rbp, rsp : rsp의 값을 rbp에 저장한다. (rsp = 스택의 꼭대기)
3) mov esi, 0xf : 0xf를 esi에 저장한다. esi = 0xf
4) mov rdi, 0x400500 : 0x400500을 rdi에 저장한다. rdi = 0x400500
5) call 0xx400497 <write_n> : 0x400497에 위치한 write_n 함수를 호출한다.
6) push rbp
7) mov rbp, rsp
8) mov QWORD PTR [rbp-0x8], rdi
: rdi의 값을 rbp-0x8의 데이터에 8바이트 참조하여 저장한다. [rbp-0x8] = 0x400500 (0x3037207964343372)
9) mov DWORD PTR [rbp-0xc], esi
: esi의 값을 rbp-0xc의 데이터에 4바이트 참조하여 저장한다. [rbp-0xc] = 0xf
10) xor rdx, rdx : rdx 값을 0으로 초기화
11) mov edx, DWORD PTR [rbp-0xc]
: [rbp-0xc]가 가리키는 주소의 값을 4바이트만큼 참조하여 edx에 저장한다. edx = 0xf
12) mov rsi, QWORD PTR [rbp-0x8]
: [rbp-0x8]이 가리키는 주소의 값을 8바이트만큼 참조하여 rsi에 저장한다. rsi = 0x400500 (0x3037207964343372)
13) mov rdi, 0x1 : 0x1을 rdi에 저장한다. rdi = 0x1
14) mov rax, 0x1 : rax = 0x1
15) syscall : write(0x1, [0x400500], rdx)
16) pop rbp : 스택 최상단의 값을 꺼내서 rbp에 대입
'Reversing' 카테고리의 다른 글
VMware Pro / Windows XP 설치 (0) | 2022.04.09 |
---|---|
crackme #1 / 리틀 엔디안 / PEview (0) | 2022.03.26 |
Visual Studio 2019 / Immunity Debugger / 문자열 패치 (0) | 2022.03.11 |
[Dreamhack] Computer Science (0) | 2022.02.28 |
[Dreamhack] Binary & Analysis (0) | 2022.02.25 |