본문 바로가기
해킹/시스템해킹

[드림핵 시스템해킹]x86 Assembly:Essential Part

by IT공부방 2022. 1. 10.

x86Assembly

해커의 언어:어셈블리

시스템 해커가 가장 기본적으로 습득해야하는 지식은 '컴퓨터 언어'에 관한 것이다. 왜냐하면 시스템 해커는 컴퓨터의 언어로 작성된 소프트웨어에서 취약점을 발견해야 하기 때문이다. 하지만 컴퓨터 언어인 '기계어'는 0과 1로만 구성되어 있어서 인간이 이해하기 어렵다. 이를 해결하기 위해서 어셈블러와 어셈블리 언어를 고안했다. 어셈블러는 개발자들이 어셈블리어로 코드를 작성하면 기계어로 코드를 치환해준다. 역어셈블러는 기계어를 어셈블리 언어로 번역한다. 따라서 기계어로 구성된 소프트웨어를 역어셈블러에 넣으면, 어셈블리 코드로 번역된다.

 

어셈블리어

어셈블리 언어는 컴퓨터의 기계어와 치환되는 언어이다. 기계어가 여러 종류라면, 어셈블리어도 여러 종류여야 한다. 명령어 집합구조(ISA)에서 배웠듯이 CPU에 사용되는 ISA는 IA-32, x86-64 등 종류가 다양하다. 여기에서는 x64 어셈블리어만을 다룬다. 

 

x64 어셈블리 언어

명령어(Opcode)와 피연산자(Operand)로 구성된다. 

-> mov(opcode) eax(operand1), 3(operand2) : 대입해라, eax에, 3을

 

명령어의 종류는 아래와 같다. 

피연산자(opcode)에는 상수, 레지스터, 메모리가 올 수 있다. 메모리 피연산자는 []로 둘러싸인 것으로 표현되고, 앞에 '크기 지정자 type ptr'이 추가될 수 있다. 타입에는 BYTE, WORD, DWORD, QWORD가 올 수 있고 각각 1,2,4,8바이트의 크기를 지정한다.

->예) QWORD PTR[0x8048000] : 0x8048000의 데이터를 8바이트만큼 참조한다.

        WORD PTR[rax] :  rax가 가리키는 주소에서 데이터를 2바이트 만큼 참조한다.

더보기

<자료형 WORD가 2바이트인 이유>

초기에 인텔은 WORD의 크기가 16비트인 IA-16 아키텍처를 개발했다. 이후에 개발된 IA-32, x86-64 아키텍처는 CPU의 WORD가 32비트, 64비트로 확장됐다. 하지만 인텔은 WORD의 자료형 크기를 32비트, 64비트로 확장하지 않고 16비트로 유지했다. 왜냐하면, WORD 자료형의 크기를 변경하면 기존의 프로그램들이 새로운 아키텍처와 호환되지 않을 수 있기 때문이다. 따라서 WORD의 크기를 유지하고 DWORD, QWORD라는 자료형을 추가로 만들었다.

 

x86-64 어셈블리 명령어

->데이터 이동 : 어떤 값을 레지스터나 메모리에 옮기도록 지시한다.

예1) mov dst, src : src에 들어있는 값을 dst에 대입

더보기

1) mov rdi, rsi                            => rsi의 값을 rdi에 대입

2) mov QWORD PTR[rdi], rsi         =>rsi의 값을 rdi가 가리키는 주소에 대입

3) mov QWORD PTR[rdi+8*rcx], rsi =>rsi의 값을 rdi+8*rcx가 가리키는 주소에 대입

예2) lea dst, src : src의 유효주소(Effective Address, EA)를 dst에 저장

더보기

1) lea rsi, [rbx+8*rcx]                   => rbx+8*rcx를 rsi에 대입

관련예제)

더보기

[레지스터]

rbx = 0x401A40

[메모리]

0x401a40 | 0x0000000012345678

0x401a48 | 0x0000000000C0FFEE

0x401a50 | 0x00000000DEADBEEF

[코드]

1: mov rax, [rbx + 8]

2: lea rax, [rbx + 8]

---------------------------------------------------------------------------------

문제

1번. 코드를 1까지 실행했을 때 rax에 저장된 값은?

-> 0xC0FFEE이다. 왜냐하면, mov 명령어이고 rbx의 주소에 8을 더하여 그 값을 rax에 저장하는 것이기 때문이다.

2번. 코드를 2까지 실행했을 떄 rax에 들어있는 값은?

-> 0x401a48이다. 왜냐하면, lea 명령어이고 rbx의 주소에 8을 더한 '유효 주소'를 rax에 저장하는 것이기 때문이다.

 

-> 산술연산 : 덧셈, 뺄셈, 곱셈, 나눗셈 연산을 지시한다.

예1) add dst, src : dst에 src의 값 더하기

더보기

1) add eax, 3                     => eax += 3

2) add ax, WORD PTR[rdi]    => ax += *(WORD *)rdi

예2) sub dst, src : dst에서 src의 값 빼기

더보기

1) sub eax, 3                    => eax -= 3

2) sub ax, WORD PTR[rdi]   => ax -= *(WORD *)rdi

예3) inc op : op의 값을 1 증가시킴 (inc eax => eax += 1)

예4) dec op : op의 값을 1 감소시킴 (dec eax => eax -= 1)

관련예제)

더보기

[레지스터]

rax = 0x31337

rbx = 0x555555554000

rcx = 0x2

[메모리]

0x555555554000 | 0x0000000000000000

0x555555554008 | 0x0000000000000001

0x555555554010 | 0x0000000000000003

0x555555554018 | 0x0000000000000005

0x555555554020 | 0x000000000003133A

[코드]

1: add rax, [rbx+rcx*8]

2: add rcx, 2

3. sub rax, [rbx+rcx*8]

4. inc rax

---------------------------------------------------------------------------------

문제

1번. 코드를 1까지 실행했을 때 rax에 저장된 값은?

-> rcx*8은 0x10이다. 그리고 rbx의 값과 이를 더해주면, 0x555555554010이다. 이 주소에 들어가있는 값은 0x3이다. 마지막으로 rax의 값과 이를 더해주면 0x3133A이다.

2번. 코드를 3까지 실행했을 떄 rax에 들어있는 값은?

-> 코드 2에서 rcx의 값은 0x4가 된다. 그리고 코드 3에서 [rbx+rcx*8]을 계산하면,  '0x555555554000 + 0x20 = 0x555555554020 ' 이기 때문에 값은, 0x3133A이다. 마지막으로 rax 0x3133A가 저장되어있었기 때문에 여기에서 이를 빼주면, 0이 된다.

3번. 코드를 4까지 실행했을 때, rax에 들어있는 값은?

-> inc는 op의 값을 1증가시키는 연산이다. rax에는 0이 저장되어있기 때문에 1을 증가해주면 1이된다.

 

-> 논리연산 : and, or, xor, neg 등의 비트 연산을 지시한다. 이 연산은 비트 단위로 이루어진다.

예1) and dst, src : dst와 src의 비트가 모두 1일때만 1, 그 밖은 0

예2) or dst, src : dst와 src의 비트 중 하나라도 1이면 1, 아니면 0

(16진수를 2진수로 고쳐 계산가능하다.)

=> and eax, ebx의 결과는 '0xcafe0000'이다.

=> or eax, ebx의 결과는 '0xffffbabe'이다. 

예3) xor dst, src : dst와 src의 비트가 서로 다르면 1, 같으면 0

=> xor연산을 동일한 값으로 두 번 실행하면, 원래 값으로 돌아간다는 특징이 있다.

예4) not op : op의 비트 전부 반전

 

-> 비교연산 : 두 피연산자의 값을 비교하고, 플래그를 설정한다.

예1) cmp op1, op2 : op1에서 op2를 빼고, 플래그 설정

cmp는 두 피연산자를 빼서 대소를 비교하고, 연산의 결과는 op1에 대입하지 않는다.

-mov rax, 0xA

-mov rbx, 0xB

-cmp rax, rbx // rax와 rbx는 같다. 따라서 zf = 1

예2) test op1, op2 : op1에서 op2에 AND 연산을 하고, 플래그 설정

test는 두 피연산자에 AND 연산을 취하고, 연산의 결과는 op1에 대입하지 않는다.

-xor rax, rax // rax=0

-test rax, rax // zf = 1

 

-> 분기 : rip를 이동시켜 실행 흐름을 바꾼다. 분기문은 굉장히 많은 수가 존재하여 여러 코드를 보면서 익혀야 한다.

예1) jmp addr : addr로 rip를 이동시킴

- xor rax, rax

- jmp 1 // jump to 1

예2) je addr : 직전에 비교한 두 피연산자 같으면 점프(jump if equal)

-mov rax, 0xcafebabe

-mov rbx, 0xcafebabe

-cmp rax, rbx // rax == rbx

-je 1 // jump to 1

예3) jg addr : 직전에 비교한 두 연산자 중 전자가 더 크면 점프(jump if greater)

-mov rax, 0x31337

-mov rbx, 0x13337

-cmp rax, rbx // rax > rbx

-jg 1 // jump to 1

 

->스택 

예1) push val : rsp를 8만큼 빼고, 스택의 최상단에 val를 쌓기

예2) pop reg : 스택 최상단의 값을 꺼내서 reg에 넣고, rsp를 8만큼 더하기

 

->프로시저 : 특정 기능을 수행하는 코드 조각이다. 이를 사용하면 반복되는 연산을 프로시저 호출로 대체할 수 있어서 코드의 크기를 줄일 수 있다. 또한 기능별로 코드 조각에 이름을 붙일 수 있게되어 코드의 가독성을 높인다. 프로시저를 부르는 행위를 '호출(call)'이라고 하고, 프로시저에서 돌아오는 것을 '반환(return)'이라고 한다.

예1) call addr : addr에 위치한 프로시저 호출

call 다음의 명령어 주소인 return address를 스택에 저장하고 프로시저로 rip를 이동시킨다.

예2) leave : 스택프레임 정리

예3) ret : return address로 반환

 

->시스템 콜 :  현대 운영체제는 컴퓨터 자원의 효율적인 사용과 사용자에게 편리한 경험을 제공하기 위해 내부적으로 매우 복잡한 동작을 한다. 운영체제는 연결된 모든 하드웨어와 소프트웨어에 접근하고 제어할 수 있다. 또한 해킹으로부터 막강한 권한을 보호하기 위해 '커널모드'와 '유저모드'로 권한을 나눈다.

 커널모드란, 운영체제가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한이다. 파일 시스템, 입/출력, 네트워크 통신, 메모리 관리 등 저수준 작업은 사용자 모르게 커널 모드에서 진행된다. 만약 해커가 커널 모드까지 진입하게 되면 시스템은 거의 무방비 상태가 된다.

 유저모드란, 운영체제가 사용자에게 부여하는 권한이다. 브라우저를 이요하여 드림핵 보기, 유투브 시청, 게임하고 프로그래밍을 하는 것 등은 모두 유저모드에서 이루어진다. 유저모드에서는 해킹이 발생해도 해커가 유저모드의 권한까지밖에 획득하지 못하기 때문에, 해커로부터 커널의 막강한 권한을 보호할 수 있다.

 시스템 콜은 유저모드에서 커널모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용된다. 예를 들어, 사용자가 'cat flag'를 실행하면, cat은 flag라는 파일을 읽어서 사용자 화면에 출력해줘야한다. 그런데 flag는 파일 시스템에 존재하여 이를 읽으려면, 파일 시스템에 접근할 수 있어야 한다. 유저모드에서는 이를 할 수 없으므로 시스템 콜을 하여 커널에게 도움을 요청한다. 즉, 유저모드의 소프트웨어가 필요한 도움을 요청하면, 커널이 요청한 동작을 수행하여 결과를 반환한다. 

더보기

<리눅스 계층>

예1) syscall : 커널에 필요한 동작을 요청한다.

시스템 콜은 함수이다. 필요한 기능과 인자에 대한 정보를 레지스터로 전달하면, 커널이 이를 읽어서 요청을 처리한다. 리눅스에서는 x64아키텍처에서 rax로 무슨 요청인지 나타낸다. 그리고 아래의 순서대로 필요한 인자를 전달한다. 

-요청 : rax

-인자순서 : rdi -> rsi -> rdx -> rcx -> r8 -> r9 -> stack

 

 

 

'해킹 > 시스템해킹' 카테고리의 다른 글

[드림핵 시스템해킹]Background: Computer Science  (0) 2022.01.07