gdb 분석을 위해 알아야할 지식
1. 프로세스 구조
프로세스 : 프로그램이 메모리 상으로 올라와 실행된 상태
위의 사진은 가상메모리의 레이아웃 상태이다. (물리적인 메모리는 저 가상메모리와 다르게 섞여있을 수 있다. 이유는 운영체제 과목에서 나온다.)
가상메모리는 프로세스들에게 각각의 비어있는 메모리 영역을 부여한다.(보안, 편의성을 위해)
STACK - 지역변수, 매개변수
HEAP - 동적메모리 할당 (MALLOC 같은 것)
DATA - 전역변수/ STATIC 변수
TEXT - CODE
여기서 stack영역은 BOF, DEP,SEH
HEAP영역은 HEP OVERFLOW, USE AFTER FREE(UAF)
TEXT영역은 ROP,ASLR
등의 해킹방법들을 적용할 수 있다.
프로세스 제어 블록(PCB)의 구조도 알아두면 좋다.
2. 어셈블리
어셈블리어는 기계어와 일대일 대응이 되는 컴퓨터 프로그래밍의 저급 언어이다. 컴퓨터 구조에 따라 사용하는 기계어가 달라진다. 문법은 아키텍쳐마다 다르며, 대표적인 표기방식은 intel형과 AT&T 두가지가 있다.
어셈블리코드=(opcode + operand)
컴파일러는 c소스코드를 어셈블리 코드로 바꿔주는데 컴파일을 하면 이 어셈블리코드들을 gdb같은 것들로 관찰할 수 있다.
cpu에는 다양한 레지스터들이 많이 존재하지만 아래 8개의 범용 레지스터정도는 외워두는 것이 좋다
정확한 용도가 아닐순 있지만 대부분 목적은 통상적으로 예약해서 쓴다.
rax (accumulator register) | 함수의 반환 (산하고난 뒤 마지막 최종값 |
rbx (base register) | x64에서는 주된 용도 없음 |
rcx (counter register) | 반복문의 반복 횟수, 각종 연산의 시행 횟수 |
rdx (data register) | x64에서는 주된 용도 없음 |
rsi (source index) | 데이터를 옮길 때 원본을 가리키는 포인터 |
rdi (destination index) | 데이터를 옮길 때 목적지를 가리키는 포인터 |
rsp (stack pointer) | 사용중인 스택의 위치를 가리키는 포인터 |
rbp (stack base pointer) | 스택의 바닥을 가리키는 포인터 |
Rsp (중요) - 스택의 가장 위에 들어있는 데이터를 가리키고 있는 포인터.
Intel CPU에서는 스택이 거꾸로 (높은 주소에서 낮은 주소로) 쌓이므로 데이터가 Push될 때마다 ESP 값은 감소
함수 호출하면 프로그램이 흘러가다가 점프를 하면 다른 위치에서 코드 실행하다가 돌아온다.
그럼 돌아오는 주소가 리턴 주소다.
실수로 return 주소 바꿔서 덮어지면 프로그램 실행흐름이 바뀌어 버릴 수 있어서 보안상 문제가 될 위험이 많기 때문이다.
프로그램 카운터 - EIP 레지스터(프로그램 카운터)
기능, 용도 : cpu입장에서 내가 현재 수행하는 곳을 기록
줌, 크롬 같이 사용한다면 eip는 둘다 여러곳 왔다갔다 해서 가르킬 것임.
가상주소로 되어있어도 운영체제가 변환한다.
레지스터를 쪼개면 rax(64비트)->eax(32비트) -> ax(16비트) -> ah,al(8비트) 레지스터로 절반의 사이즈씩 쪼개진다.
이렇게 쪼개는 이유는 64비트환경에 있던 것들이 32비트같이 더 낮은 비트의 아키텍처 환경으로 가면 문제가 일어나기 때문에 쪼개진다
명령어(opcode) at&t 문법이면 왼쪽에서 오른쪽으로 옮기고, intel은 오른쪽에서 왼쪽으로 메모리를 이동한다.
mov - 데이터값 이동
예시 )
mov dest src(오른쪽 소스를 왼쪽 데스티네이션으로 옮겨라.) intel 문법
mov ebp esp (esp를 ebp로 옮겨라)
mov BYTE PTR [edi],0x41(바이트 피티알은 1바이트 단위의 메모리를 뜻하고 이 안에 괄호가 메모리 주소로 생각하고 있겠다, edi 레지스터 주소가 그 메모리 주소라고 생각하겠고, 이 메모리 위치에 0x41이라는 1바이트의 숫자를 이동시킨다.)
mov eax, DWORD PTR(4바이트) [esp+4] (eax에 있는 레지스터 값을 esp+4가 가리키는 메모리 위치에 eax에 있는 값을 쓰겠다. ? )
lea - 데이터 주소 이동
예시 ) intel 기준
lea dest src(src의 주소를 dest로 이동한다)
lea eax, DWORD PTR [esp+4](eax에 esp+4에 있는 주소를 넣는다)
add/sub - 더하기, 빼기
예시 )
add/sub des src(src의 값과 des의 값을 더하거나 뺀다.)
sub esp, 0x18(이 레지스터에서 값을 0x18 만큼 뺀다.)
add Dword ptr [edi], edx(4바이트 메모리를 주고, edi가 가리키고있는 메모리 주소 속에 숫자에다가 edx의 값을 더해서 집어 넣어라.)
and / or / xor - 비트 연산
예시)
and / or / xor dest src
and dl, 11101100b
or dl, 0010000b
xor eax, eax(같은 값 xor하면 0이되는 거잖아 ? mov eax 0도 되긴하는데 xor이 연산이 더 빨라서 이렇게 하는 것임)
push/pop - 집어넣기, 빼기
예시)
push eax(esp레지스터가 가리키는 스택위치에 eax에 있는 것을 집어넣어주는 역할)
pop esi(esi 레지스터를 가져오는 역할)
jmp- 실행흐름을 바뀌게 만드는 명령어
예시)
jmp 0x8048436 이라면
eip(현재 실행중인 레지스터)의 위치가 0x8048436로 바뀐다.
cmp, test - 비교
예시)
cmp dest src
cmp eax 3
systemcall (중요) - 함수호출이나 시스템호출같은것들을 다룬다.
call write_msg
call 0x80488097 <write_msg>
이면 eip가 0x80488097이다.
추가적인 설명 ) 심볼
0x80488097 <write_msg> 이런식으로 이 번지속에 있는 심볼이름이 write msg 다 라는 의미.
+)악성코드는 이런게 없는경우가 많다. 알기 어렵게 하고 악성코드 사실을 숨겨야 하니까
여기서 콜해서 eip가 write_msg로 점프(text 영역) 그러면 원래 위치였던 곳의 주소가 스택에 업데이트됨(ret, eip)
ret이라는 명령어가 esp인 스택 주소를 참조하게 되는 건데
esp가 덮어쓰거나 버그로 위치 바뀌면 악의적인 행동이 가능해서 문제가 되니 조심조심
ret 주소로 eip 주소를 바꿔주는 것.
+)system call : 운영체제 호출 중요
EAX 시스템 콜 번호 저장
EBX ECX EDXX ESI EDI 전달할 파라미터 정보 저장
EAX 되돌아 나온 return 값도 eax에 있다.
인텔 32비트는 int 0x80이라는 명령어가 있긴한데 시스템콜을 직접 부르기보단 라이브러리 함수를 가져와서 쓰기 때문에 라이브러리 함수를 콜해서 불러도 int 0x80해서 진입하는거 보는건 많이 어려움
시스템 콜을 하는 이유 = 보안
실행중인 프로세스가 다른 프로세스에 접근해서 멋대로 바꾸면 안되기 때문에 cpu 커널의 하드웨어적 권한 2단계인 ring이 있다. 이 권한을 이용하여 실행중인 프로세스와 관련된 권한 만을 준다.
zoom 프로세스가 돌 땐, 줌 프로세스와 관련된 것들에 대한 것만 권한을 주는거야
권한 이상의 프로세스를 수행해야할 땐, user process가 수행하는게 아닌, 믿을 수 있는 운영체제한테 요청해서 운영체제가 대신 수행.
ex) printf();
이걸 실행할 시, 그래픽 메모리와 같은 여러가지를 접근해야하지만
훨씬 복잡한 기능을 어플리케이션이 하는게 아니라 운영체제가 대신 권한을 가지고 수행.
따라서 운영체제를 관장하는 회사가 나쁜마음 먹으면 안된다.
어셈블리 디버깅 : 디버거라는 도구가 있다. ex ) linux 's gdb
코드가 백만개 천만개가 있어도 눈깜짝할 사이에 실행하니 알기 힘듦.
gdb를 이용하여 프로그램을 일시정지를 한 다음에 이걸 들여다보고 레지스터 값 보고, 디버깅할 수 있다.
1. 특정한 어셈블리 명령에서 멈춤 -> 브레이크 포인트
2. 특정 주소의 메모리 내용 덤프
3. 레지스터 값 확인
등의 기능들이 가능.
gdb 이용 명령어
break : b main(메인 함수 시작할 때 멈춰라), b 4, b*0xffffd1d0(정확한 위치에 걸수도있고)
run , r 하면
브레이크 포인트 전까지 하다가 멈출 수 있다.
continue, c하면 이어서 계속 실행
한줄만 실행 ni (nextin)
콜, 함수 속으로 딸려 들어감 si (step in)
step over(콜의 경우 걍 콜이 끝나고 콜 다음까지 가는거.)
x -- 이건 메모리 내용 검사를 위한 메모리 덤프 명령어
x/s 0x80484d0(이 주소의 내용을 문자열로 덤프해라.)
x/32wx $esp(주소대신 레지스터 써도 되는 것임, 워드 헥스코드로 덤프해라)
x/4i 0x21132asd(이 주소의 내용을 명령어로 덤프해라.)
레지스터 값 확인
i b
i r(info register)
i proc map 등
b t (백 트레이서, 함수 역추적)
disass main(디스 어셈블의 약자로 어셈블리 보여주는 코드다.)
x/10gx 0x주소 또는 $레지스터: 80바이트를 8바이트씩 10씩 출력
-> 옵션을 여러개 둘 수 있다.
x/s 0x주소 또는 $레지스터 : 특정주소의 문자열 출력