Hacking & Security/System Hacking

System Hacking(3) - 3 : x64 Assembly : Essential part(1)

희디 2022. 1. 29. 04:33

https://dreamhack.io/lecture/courses/52

 

Background: Linux Memory Layout

리눅스 프로세스의 메모리 구조에 대해 살펴봅니다.

dreamhack.io

 

# 서론

해커의 언어: 어셈블리 

컴퓨터 속의 거대한 세계에 기계어라는 언어가 있으며 해커는 이 세계의 허점을 공격하여 시스템을 장악한다. 

시스템 해커는 기본적으로 컴퓨터의 언어로 작성된 소프트웨어에서 취약점을 발견해야 하므로 컴퓨터 언어를 기본적으로 알아야 한다. 

(*기계어 : 0과 1로만 이루어진 언어) 

 

초기의 개발자들도 마찬가지로 기계어를 어려워했기에 한 컴퓨터 과학자가 EDSAC을 개발하면서 어셈블리 언어어셈블러 라는 것을 고안했다. 

 

어셈블러는 일종의 통역사로 개발자가 어셈블리어로 코드를 작성하면 기계어로 코드를 치환해줬다. 

소프트웨어를 역분석하는 사람은 기계어를 어셈블리 언어로 번역하는 역어셈블러를 개발했다. 이로써 소프트웨어 분석가들은 소프트웨어를 분석하려고 기계어를 읽을 필요가 없어졌다. 


# 어셈블리 언어 

: 컴퓨터의 기계어와 치환되는 언어 

 

# x64 어셈블리 언어 

 

<기본구조>

명령여(동사 역할)와 피연산자(목저어 역할) 두 개로 구성됨. 

 

<명령어>

명령 코드
데이터 이동(Data Transfer) mov, lea
산술 연산(Arithmetic) incdecaddsub
논리 연산(Logical) and,orxornot
비교(Comparison) cmptest
분기(Branch) jmpjejg
스택(Stack) pushpop
프로시져(Procedure) callretleave
시스템 콜(System call) syscall

 

<피연산자> 

1) 상수

2) 레지스터

3) 메모리 : [ ] 으로 둘러싸인 것으로 표현되며 앞에 크기 지정자 TYPE PTR이 추가될 수 있다. 

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바이트 만큼 참조
BYTE PTR 1 바이트 만큼 참조(하위 1바이트)

 

# x86-64 어셈블리 명령어 

<데이터 이동>

: 어떤 값을 레지스터나 메모리에 옯기도록 지시함. 

 

mov dst, src : src에 들어있는 값을 dst에 대입
mov rdi, rsi rsi의 값을 rdi에 대입
mov QWORD PTR[rdi], rsi rsi의 값을 rdi가 가리키는 주소에 대입
mov QWORD PTR[rdi+8*rcx], rsi rsi의 값을 rdi+8*rcx가 가리키는 주소에 대입
lea dst, src : src의 유효 주소(Effective Address, EA)를 dst에 저장합니다.
lea rsi, [rbx+8*rcx] rbx+8*rcx 를 rsi에 대입

mov는 값을 대입하고 lea는 유효주소를 대입하는 것이다. 

 

<산술 연산>

: 덧셈, 뺄셈, 곱셈, 나눗셈 연산을 지시함. 

 

- 덧셈

add dst, src : dst에 src의 값을 더한다. 
add eax, 3 eax += 3
add ax, WORD PTR[rdi] ax += *(WORD *)rdi
inc op: op의 값을 1 증가시킴
inc eax eax += 1

- 뺼셈

sub dst, src: dst에서 src의 값을 뺸다. 
sub eax, 3 eax -= 3
sub ax, WORD PTR[rdi] ax -= *(WORD *)rdi
dec op: op의 값을 1 감소 시킴
dec eax
eax -= 1

- 코드 1번까지 실행했을 때 

: rcx는 0x2로 16진수로 변환하면 2이고 rcx*8을 하면 16인데 표현은 0x로 16진수로 되어 있으므로 꼭!! 16이라 적으면 안되고 0x10이라고 계산해야 한다.  0x10과 rbx의 값을 더하면 0x555555554010이고 이것은 메모리의 3번째 줄 값이다. 그 값은 0x0...03이고 0x31337에 이 값을 더하면 0x3133A가 된다. 

 

- 코드 2 이후 rcx에 현재 0x4이고 코드 3인 rax - (rbx+rcx*8)을 하면 

0x3133A - (0x555555554000+0x0..020=0x555555554020) = 0이다. 

 

- inc rax는 값을 1 증가하는 것이므로 답은 1이다. 

 

<논리 연산> 

and, or, xor, neg 등의 비트 연산을 지시한다. 

비트 단위로 이루어진다.  

 

-and & or

 

and를 하면 대체로 뒤의 부분으로 0이 아닌 부분으로 채우는 듯하고 결과는 앞에 쓴 것에 저장되는 듯?? (확인하기)

1은 and로 앞부분은 0이 아니므로 rcx의 앞부분을 가져오고 뒤부분은 하나라도 0이 있으므로 뒤는 0으로 채워주고 0x1234567800000000이 rax에 저장된다. 

2 또한 and로 앞부분은 rbx에 0이 있어서 0으로 채우고 뒤에는 다 1이므로 뒤의 부분을 따라서 0x000000009abcdef0이 rbx에 저장된다. 

3은 변경된 rax와 rbx를 or 하는 것으로 둘의 값을 합치면 0x123456789abcdef0인 답이 나오는 것을 확인 할 수 있었다. 

 

- xor & not

-- !!! --

<비교>

: 두 피연산자의 값을 비교하고 플래그 설정 

cmp를 써서 차이를 통해서 대소를 비교한다. 

ex) 차이가 0 -> ZF가 설정된다.  ZF 플래그를 보고 두 값이 같은지 확인할 수 있다. 

 

test 명령어는 and 비트 연산자를 사용한다. 

 

<분기>

rip을 이동시켜 실행 흐름을 바꾼다.