시스템 해킹/dream hack

[dream hack] Linux Exploitation & Mitigation Part 1 - 1. ELF 동적 분석

ruming 2021. 2. 8. 15:43

강의 링크

 

ELF 동적 분석

 

Intro

바이너리를 분석할 때, 바이너리가 실행되며 변화하는 상태를 관찰하기 위해 동적 디버깅이 필요한 경우가 있다. 리눅스의 실행 파일인 ELF파일을 동적으로 디버깅하는 방법에 대해 알아보자. 

 

도구

gdb, strade, Itrade, IDA 등이 있다. 

gdb의 디스어셈블리 문법에는 AT&T와 intel 두 종류가 있다. 널리 쓰이는 디스어셈블리 문법은 intel이다. gdb 기본 설정에서의 디스어셈블리 문법은 AT&T이기 때문에 intel로 바꿔줘야 한다. .gdbinit는 gdb를 시작할 때 자동적으로 실행할 gdb 명령어들을 저장하고 있는 파일이다.

 

.gdbinit에 gdb의 디스어셈블리 문법을 intel로 바꿔주는 명령어인 set disassembly-flavor intel을 추가

.gdbinit 파일 수정

$echo "set disassembly-flavor intel" >> ~/.gdbinit

disassemble-flavor 설정 확인

$ gdb -q
(gdb) show disassembly-flavor
The disassembly flavor is "intel".

 

ELF 동적 분석 실습

example0.c - 1과 2의 덧셈 결과를 출력하는 예제

#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;
}

 

example0을 gdb의 인자로 전달한 결과

$ gdb ./example0
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./example0...(no debugging symbols found)...done.
(gdb) 

gdb는 커맨드 라인 기반으로 동작하는 디버거로, 명령어 입력을 통해 다양한 기능을 수행할 수 있다.

disassemble 혹은 disas : gdb에서 함수의 디스어셈블리 결과를 출력해주는 명령어

 

main함수의 디스어셈블리 결과

(gdb) disas main
Dump of assembler code for function main:
   0x0804840b <+00>:	push   ebp
   0x0804840c <+01>:	mov    ebp,esp
   0x0804840e <+03>:	sub    esp,0xc
   0x08048411 <+06>:	mov    DWORD PTR [ebp-0x4],0x0
   0x08048418 <+13>:	mov    DWORD PTR [ebp-0x8],0x1
   0x0804841f <+20>:	mov    DWORD PTR [ebp-0xc],0x2
   0x08048426 <+27>:	mov    edx,DWORD PTR [ebp-0x8]
   0x08048429 <+30>:	mov    eax,DWORD PTR [ebp-0xc]
   0x0804842c <+33>:	add    eax,edx
   0x0804842e <+35>:	mov    DWORD PTR [ebp-0x4],eax
   0x08048431 <+38>:	push   DWORD PTR [ebp-0x4]
   0x08048434 <+41>:	push   0x80484d0
   0x08048439 <+46>:	call   0x80482e0 <printf@plt>
   0x0804843e <+51>:	add    esp,0x8
   0x08048441 <+54>:	mov    eax,0x0
   0x08048446 <+59>:	leave  
   0x08048447 <+60>:	ret    
End of assembler dump.
(gdb) 

 

0x8048439 주소에 브레이크포인트를 설정해 printf함수의 인자들 살펴보기

continue(c) : 프로세스가 멈춰있는 상태에서 프로세스를 이어서 실행시켜주는 명령어

브레이크포인트가 설정되어 있다면 다음 브레이크포인트 지점까지 프로세스를 실행시킨다.

(gdb) b*0x8048439
Breakpoint 2 at 0x8048439
(gdb) c
Continuing.

Breakpoint 2, 0x08048439 in main ()
(gdb)

 

스택 메모리 살펴보기

x 명령어를 사용하면 인자로 주어진 주소의 메모리를 볼 수 있다. 또한 출력 타입을 지정해줄 수 있다.

esp 레지스터의 메모리를 word 타입으로 2개만큼(x/2wx) 출력해보자.

(gdb) x/2wx $esp
0xffffd544:	0x080484d0	0x00000003

x86 아키텍처의 호출 규약에 의해, printf 함수가 호출되는 시점의 스택 메모리에 함수의 인자들이 순서대로 저장되어 있다.

 

첫번째 인자인 1 + 2 = %d\n 문자열의 주소 0x80484d0을 문자열 형태로(x/s) 출력해보자.

(gdb) x/s 0x080484d0
0x80484d0:	"1 + 2 = %d\n"
(gdb) 

 

마지막으로 함수의 다음 인스트럭션까지 실행해주는 nexti(ni) 명령어를 이용해 printf 함수를 실행해보자.

(gdb) x/i $eip
=> 0x8048439 <main+46>:	call   0x80482e0 <printf@plt>
(gdb) ni
1 + 2 = 3
0x0804843e in main ()
(gdb) x/i $eip
=> 0x804843e <main+51>:	add    esp,0x8
(gdb) 

printf함수가 실행되어 "1 + 2 = 3" 문자열이 출력되었다.

 

 

Process Attach

gdb를 이용하면 실행중인 프로세스를 디버깅할 수 있다.

read_write.c - 사용자의 입력을 받아 문자열을 출력해주는 프로그램

//gcc -o read_write read_write.c -m32
//read_write.c
#include <stdio.h>
int main(void){
  char buf[64] = {};
  printf("Input : ");
  scanf("%63s", buf);
  printf("Your input : %s", buf);
  
}

 

$ ./read_write
Input : abcd1234
Your input : abcd1234

gdb에 -p PID 혹은 --pid=PID를 인자로 전달하면 PID에 해당하는 프로세스에 gdb를 attach할 수 있다.

 

read_write를 실행하여 실행 중인 프로세스의 PID를 알아보자.

[실행 중인 프로세스의 PID를 확인하는 방법]

1. ps 프로그램을 이용

$ ./read_write
Input : 

read_write 바이너리를 실행한 후

$ ps -aux | grep read_write
theori    50353  0.0  0.0   2204   520 pts/28   S+   21:50   0:00 ./read_write

read_write 프로세스의 PID(50353)를 구할 수 있다.

 

2. pidof나 pgrep 프로그램 사용

$ pidof read_write
50353
$ pgrep read_write
50353

인자로 바이너리 이름을 전달하면 해당 바이너리의 PID를 구할 수 있다.

 

PID를 이용해 gdb 프로세스 디버깅을 해보자.

$ ./read_write
Input : 

프로세스가 사용자의 입력을 받기 위해 실행 대기중일 때, gdb를 프로세스에 attach 시킨다.

# gdb -q -p 50353
Attaching to process 50353
Reading symbols from /Linux_Exploitation_Mitigation/read_write...(no debugging symbols found)...done.
Reading symbols from /lib/i386-linux-gnu/libc.so.6...(no debugging symbols found)...done.
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
0xf7f8ffd9 in __kernel_vsyscall ()
...
(gdb)

scanf 함수가 호출된 이후인 0x804850a 주소에 브레이크포인트를 설정해 입력한 데이터를 메모리에서 확인해보자.

(gdb) b *0x0804850a
Breakpoint 1 at 0x804850a
(gdb) c
Continuing.
$ ./read_write
Input : abcd1234
Breakpoint 1, 0x0804850a in main ()
(gdb) x/4wx $ebp-0x4c
0xffaf0acc:	0x64636261	0x34333231	0x00000000	0x00000000
(gdb) 

입력한 문자열 "abcd1234"가 buf의 주소인 0xffaf0acc 주소에 저장되어 있다.

 

gdb 명령어와 실행 보기

 

 

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

[dream hack] ASLR  (0) 2021.02.26
[dream hack] NX bit  (0) 2021.02.20
[dream hack] NOP Sled  (0) 2021.02.18
[dream hack] Return address overwrite  (0) 2021.02.18
[dreamhack] Memory Corruption - C (I) 스택 버퍼 오버플로우  (0) 2021.02.08