발자취
시스템 보안 04. gdb 디버거 본문
gdb란?
- gcc로 컴파일한 C 프로그램을 디버깅하는 툴
- gdb를 사용하기 위해서는 gcc 컴파일시 -g 옵션을 사용해야 함.
- C 프로그램에 대응되는 어셈블리 코드 분석에 유용함
- 프로그램 실행시, 레지스터, 스택 세그먼트, 코드 세그먼트의 내용 변화를 파악하는데 유용함
1. gdb를 이용한 firstprog.c 분석
간단한 동작을 하는 firstprog.c 프로그램을 gdb의 다양한 명령어를 통해 분석해볼 것이다.
#include <stdio.h>
int main()
{
int i;
for(i=0; i < 10; i++)
{
printf("Hello, World!\n");
}
return 0;
}
firstproc.c
gdb로 분석할 C 코드를 작성해주었다.

-g 옵션을 사용하여 컴파일한 후, gdb 명령어를 통해 gdb 모드를 실행시켰다.
(참고로 gdb 명령어와 함께 사용한 -q 옵션은 불필요한 메시지가 뜨지 않도록 만들어주는 옵션이다)

set disassembly intel 명령어를 사용하여 어셈블리 코드 분석을 위해 intel 문법을 사용하도록 설정했다.
이 프로그램이 intel 문법으로 만들어졌기 때문에 gdb를 통해 디버깅할 때 intel 문법을 사용하겠다고 선언하는 개념이라고 할 수 있다.
break main 명령어는 main 함수에 break를 걸어주는 명령어이다. b main을 입력해줘도 같은 동작을 한다.
보통 특정 위치에 break를 걸고 실행시키면 break가 걸린 위치의 바로 직전 라인까지 실행해준다.
근데 이렇게 break 명령어 뒤에 함수명을 적어주면, 해당 함수가 호출된 후 break가 걸린다.
run 명령어는 프로그램을 실행해주는 명령어이다. r을 입력해줘도 같은 동작을 한다.
6번 라인인 for문 전까지 실행된 것을 화면에 띄워진 메시지를 통해 확인할 수 있다.

list 명령어는 현재 수행중인 프로그램 코드를 보여준다.

disassemble main 명령어는 main 함수에 해당하는 어셈블리 코드를 보여준다.
disass main으로 입력해도 같은 동작을 한다.
이렇게 디스어셈블리를 하는 이유는 다음과 같다.
1. 본인이 짠 코드가 어떻게 돌아가는지 확인할 수 있다
2. 실행 파일만 존재하고 코드의 내용은 없는 경우, 원래의 소스코드와 유사한 어셈블리 코드를 얻어내서 어떻게 동작하는지를 알아낼 수 있다.
보통 악성코드의 코드 내용까지 주어지는 경우는 거의 없기 때문에(주어진다면 분석이 쉬워 차단이 쉽기 때문에 공격자에게 불리하기 때문) 이런 식으로 코드의 동작 내용을 알아내 분석하는 방식을 쓴다.

info registers 명령어는 레지스터들의 정보를 보여준다.
info register ___ 명령어는 특정 레지스터의 정보를 보여준다.
eip 레지스터는 다음에 수행될 명령어의 주소를 포함하는 레지스터이다.

x 명령어를 사용하면 메모리의 상태를 확인할 수 있다.
/ 뒤에 오는 x는 16진수를 나타낸다.
따라서 x/x $eip를 입력하면 eip 레지스터의 메모리 정보를 16진수로 확인할 수 있다.
/ 뒤에 오는 x의 앞에 숫자를 입력하면 그 숫자만큼의 메모리 블록을 확인할 수 있다.
지금은 2가 들어갔기 때문에 eip 메모리 블록과 그 다음 블록까지 총 2개의 메모리 블록을 확인할 수 있다.

/ 뒤에 오는 i는 Instruction(명령어)의 약자로, eip가 가리키는 명령어를 보여준다.
i의 앞에 숫자가 오는 경우, 그 숫자만큼의 메모리 블록을 확인할 수 있게 해준다.

print ______ 명령어는 해당 주소 번지의 내용을 출력한다.
print $esp + 0x1c 명령어는 esp에 저장된 주소번지 + 0x1c를 출력한다.
nexti 명령어는 eip가 가리키는 명령어를 실행하고, eip가 다음 명령어를 가리키도록 재설정한다.
(위에서 말했듯, eip는 다음에 수행될 명령어의 주소를 포함하는 레지스터이다.)
nexti 명령어를 입력한 뒤, x/i $eip를 입력해보면 위에서 확인했던 eip의 내용과 다르게 바뀐 것을 확인할 수 있다.
2. gdb를 이용한 stack_example.c 분석
함수 호출 전과 후에 스택이 어떻게 변화하는지 확인하는 실습이다.
void test_function(int a, int b, int c, int d)
{
int flag;
char buffer[10];
flag = 31337;
buffer[0] = 'A';
}
int main(){
test_function(1, 2, 3, 4);
}
stack_example.c

컴파일 후, gdb 실행해 intel 문법으로 설정해줬다.
list 명령어로 main 함수의 라인을 확인해주었다.

main 함수에 해당하는 어셈블리 코드를 확인해보았다.

break 포인트를 총 두 군데에 걸어주었다.
11번 라인은 main 함수에서 test_function을 호출하는 라인이다. 즉, test_function이 호출되기 전까지만 실행되도록 break 포인트를 설정해준 것이다.
test_function에도 break 포인트를 걸어주었다. break 명령어 뒤에 함수명이 오면 해당 함수의 내부에 break가 걸린다.
즉, test_function이 호출된 후에 break 포인트를 설정해준 것이다.
이렇게 test_function 함수의 호출 전, 호출 후 두 시점에 브레이크 포인트를 걸어준 뒤, 실행했다.

test_function 함수 호출 전의 esp, ebp, eip의 모습이다.
x/5i $eip로는 eip부터 시작하여 그 뒤 4개의 명령어들을 확인할 수 있다.
push 명령어를 통해 4, 3, 2, 1을 순서대로 스택에 넣고 있다.
먼저 사용할 인자가 1이므로 함수 실행 시 pop 했을 때 가장 먼저 나올 수 있도록 인자를 역순으로 넣고 있는 모습이다.
인자를 넣은 뒤 test_function을 call 하고 있다.
이를 통해 함수 호출 전에는 인자를 넣고, test_function을 호출하는 작업을 하는 것을 확인할 수 있다.

cont 명령어를 사용하면 두번째 브레이크 포인트를 실행해준다.
즉, test_function을 호출한 후의 시점으로 가는 것이다.
i r esp ebp eip를 입력했을 때의 결과가 함수 호출 전과 달라진 것을 확인할 수 있다.
test_function도 디스어셈블리하여 동작을 확인해보았다.

위에서 i r 명령어로 확인했을 때 ebp의 주소는 oxbffff3e0이었다.
이 ebp 주소에서 -0x4, -0xe한 주소는 다음과 같다.

원리는 다음과 같다.
한 블럭에서 오른쪽에서 왼쪽으로 주소가 증가하고, 다음 블럭으로 가서 다시 증가하기 시작한다.

esp가 가리키는 스택의 내용 16개를 출력했다.
빨간 네모로 표시한 네 값이 test_function 호출 후 스택에 들어간 인자들이다.

x/i $eip 명령어를 통해 다음 실행 명령어를 확인한 뒤, nexti로 eip가 가리키는 명령어를 실행하고 eip가 다음 명령어를 가리키도록 설정한다.
이 과정을 반복하면서 ebp-0xe 위치(=esp의 주소번지)에 0x41(='A'의 아스키코드값)을 할당해주는 라인까지 실행해준다.
x/16x $esp를 확인해보면 빨간 네모 위치에 '41'이 들어간 것을 확인할 수 있다.
즉, 함수 호출 후 esp의 내용이 바뀐 것이다.

오늘은 gdb를 통해 두 가지 프로그램을 분석해보는 실습을 진행해보았다.
실습을 하면서 알게된 명령어 사용법들을 통해 추후 버퍼 오버플로우 등의 실습도 진행해볼 수 있을 것 같다!
'3-2 > 시스템보안' 카테고리의 다른 글
| 시스템 보안 06. 쉘코드 기본 개념 및 연습문제 (0) | 2023.11.21 |
|---|---|
| 시스템 보안 05-1. 버퍼 오버플로우 공격 (0) | 2023.11.03 |
| 시스템 보안 03. 리눅스 시스템 보안 (0) | 2023.10.04 |
| 시스템보안 02. 패스워드 (0) | 2023.09.30 |
| 시스템보안 01. 리눅스 시스템 (0) | 2023.09.30 |