디버거(Debugger)
버그(bug) : 실수로 발생한 프로그램의 결함.
디버거 : 버그를 없애기 위해 사용하는 도구.
프로그램을 어셈블리 코드 단위로 실행하면서, 실행 결과를 사용자에게 보여줌.
gdb(GNU debugger)
무료 오픈 소스.
우분투에 기본 설치.
debugee.c를 컴파일 해준 뒤 gdb debugee로 디버깅을 해준다.
ELF(Executable and Linkable Format) = 리눅스의 실행파일 형식
헤더와 여러 섹션들로 구성
헤더 : 실행에 필요한 여러 정보
섹션 : 컴파일 된 기계어 코드, 프로그램 문자열 등 여러 데이터 포함
진입점(Entry Point, EP) - 운영체제는 ELF를 실행할 때, 진입점의 값부터 프로그램을 실행한다.
readelf로 확인해본 결과, debugee의 진입점은 0x1050이다.
1. start
진입점부터 프로그램을 분석할 수 있게 해줌.
예제 코드랑 똑같이 debugee.c를 만들었는데 main 부터 시작된 것 같다. 엔트리 포인트도 드림핵에 나와있는 0x400400과 다르다. 왜 이렇게 되는건지는 잘 모르겠다.
2. context
프로그램은 실행되면서 레지스터를 비롯한 여러 메모리에 접근한다.
디버거를 이용하여 프로그램의 실행 과정을 자세히 관찰하려면 컴퓨터의 각종 메모리를 한눈에 파악할 수 있는 게 좋다.
맥락(context) : 주요 메모리들의 상태(pwndbg)
크게 4개 영역으로 구분됨.
1. registers : 레지스터의 상태를 보여줌.
2. disasm : rip부터 여러 줄에 걸쳐 디스어셈블된 결과를 보여줌.
3. stack : rsp부터 여러 줄에 걸쳐 스택의 값들을 보여줌.
4. backtrace : 현재 rip에 도달할 때까지 어떤 함수들이 중첩되어 호출됐는지 보여줌.
3. break & continue
gdb를 이용한 프로그램 분석 -> 전체 프로그램 중 아주 일부분의 동작 관심
분석 대상 : main일 경우
진입점부터 main 함수까지 코드를 한 줄씩 실행시켜가며 main에 도달한다면 디버깅이 비효율적임.
break : 특정 주소에 중단점(breakpoint)을 설정하는 기능
원하는 함수에 중단점을 설정하고, 프로그램을 실행하면 해당 함수까지 멈추지 않고 실행한 다음 중단된다. 그러면 중단된 지점부터 다시 세밀하게 분석이 가능하다.
continue : 중단된 프로그램을 계속 실행시키는 기능
b로 break, c를 입력해서 continue를 하는 것 같은데 예시랑 비교했을 때와는 다른 값이 나온다.
4. run
앞의 start가 진입점부터 프로그램을 분석할 수 있도록 자동으로 중단점을 설정한다면, run은 단순히 실행만 시킨다.
중단점을 설정해놓지 않으면 프로그램이 멈추지 않고 끝까지 실행된다.
지금은 main에 중단점을 설정해놨기 때문에 run명령어를 실행해도, main에서 실행이 멈춘다.
* 자주 사용되는 명령어 단축키
- b : break
- c : continue
- r : run
- si : step into
- ni : next instruction
- i : info
- k : kill
- pd : pdisas
5. disassembly
gdb는 프로그램을 어셈블리 코드 단위로 실행하고, 결과를 보여준다.
디스어셈블(Disassemble) 기능 기본 탑재함.
pwndbg -> 디스어셈블된 결과를 가독성 좋게 출력해주는 기능이 있음.
함수 이름을 인자로 전달하면 해당 함수가 반환될 때 까지 전부 디스어셈블하여 출력함.
6. navigate
중단점 도달 이후 그 지점부터는 명령어를 한 줄씩 자세히 분석해야 함.\
ni, si : 어셈블리 명령어를 한 줄 실행한다.
call로 서브루틴 호출 시
ni -> 서브루틴 내부로 안 들어감.
si -> 서브루틴 내부로 들어감.
처음에는 그냥 예시로 나와있는데로 b *main+57을 했다가 inferior 어쩌고 하면서 아무 결과도 없이 끝나길래 이상해서 구글링을 해보니까 중단점을 제대로 안 설정해서 그냥 코드가 끝나버린거다 이런 식의 설명이 있었다.
그래서 DISASM 부분을 확인해보니 내 리눅스에서는 printf 함수를 호출하는 지점이 main+60 지점이어서 여길 중단점으로 설정하고 continue 했더니 원하는 화면이 나왔다.
7. next instruction
ni 를 입력하면, 위와 같이 printf 함수 바로 다음으로 rip가 이동한 것을 알 수 있다. 저 화살표가 rip다.
* 왜 printf를 실행했는데 아무 문자열도 출력되지 않을까?
printf가 출력하고자 하는 문자열은 stdout의 버퍼에서 잠시 대기 후 출력된다.
버퍼 : 데이터가 목적지로 이동 전에 잠시 저장되는 장소
특정 조건 만족 시에만 데이터를 목적지로 이동시킨다.
1. 프로그램이 종료될 때
2. 버퍼가 가득 찼을 때
3. fflush와 같은 함수로 버퍼를 비우도록 명시했을 때
4. 개행 문자가 버퍼에 들어왔을 때
8. step into
printf 함수를 호출하는 지점까지 다시 프로그램을 실행시킨 뒤, si를 입력하면 아래와 같이 printf 함수 내부로 rip가 이동한 것을 확인할 수 있다.
프로그램을 분석하다가 어떤 함수의 내부까지 궁금하면 si, 그렇지 않을 때는 ni를 사용한다.
Backtrace를 보면, main 함수에서 printf를 호출했으므로 main 함수 위에 printf 함수가 쌓인 것을 볼 수 있다.
9. finish
step into로 함수 내부에 들어가서 필요한 부분을 모두 분석했는데, 함수의 규모가 커서 ni로는 원래 실행 흐름으로 돌아가기 어려울 수 있다. 이럴 때는 finish 명령어로 함수의 끝까지 한 번에 실행할 수 있다.
10. examine
프로그램 분석 중 가상 메모리에 존재하는 임의 주소의 값을 관찰해야 할 때 존재함.
x : 특정 주소에서 원하는 길이만큼의 데이터를 원하는 형식으로 인코딩하여 볼수 있음.
1. rsp부터 80바이트를 8바이트씩 hex 형식으로 출력
2. rip부터 10줄의 어셈블리 명령어 출력
3. 특정 주소의 문자열 출력
11. telescope
telescope - 강력한 메모리 덤프 기능.
특정 주소의 메모리 값들을 보여주고, 메모리가 참조하고 있는 주소를 재귀적으로 탐색하여 값을 보여준다.
12. vmmap
가상 메모리의 레이아웃을 보여준다.
어떤 파일이 매핑된 영역일 경우, 해당 파일의 경로까지 보여준다.
* 파일 매핑 *
어떤 파일을 메모리에 적재하는 것.
위에서 /home/kchabin/debugee
/usr/lib/x86_64-linux-gnu/libc-2.33.so
가 매핑된 파일이다.
리눅스에서 ELF 실행 시,
먼저 ELF의 코드와 여러 데이터를 가상 메모리에 매핑하고,
해당 ELF에 링크된 공유 오브젝트(Shared Object, so)를 추가로 메모리에 매핑한다.
공유 오브젝트 : 윈도우의 DLL과 대응되는 개념.
자주 사용되는 함수들을 미리 컴파일해둔 것.
C언어의 printf, scanf 등이 리눅스에서는 libc(library C)에 구현돼있다.
공유 오브젝트에 이미 구현된 함수를 호출할 때는 매핑된 메모리에 존재하는 함수를 대신 호출한다.
gdb / python
gdb를 통해 디버깅 시 숫자와 알파벳이 아닌 값을 입력하는 상황처럼 직접 입력할 수 없을 때가 있다.
-> 이용자가 직접 입력할 수 없는 값이기 때문 => 파이썬으로 입력값을 생성하고, 사용해야 함.
gdb / python argv
gdb / python input
$()와 함께 파이썬 코드를 입력하면 값을 입력할 수 있음.
'<<<' : 입력값 전달
명령어 정리
- start: 진입점에 중단점을 설정하고, 실행
- break(b): 중단점 설정
- continue(c): 계속 실행
- disassemble: 디스어셈블 결과 출력
- u, nearpc, pd: 디스어셈블 결과 가독성 좋게 출력
- x: 메모리 조회
- run(r): 프로그램 처음부터 실행
- context: 레지스터, 코드, 스택, 백트레이스의 상태 출력
- nexti(ni): 명령어 실행, 함수 내부로는 들어가지 않음
- stepi(si): 명령어 실행, 함수 내부로 들어감
- telescope(tele): 메모리 조회, 메모리값이 포인터일 경우 재귀적으로 따라가며 모든 메모리값 출력
- vmmap: 메모리 레이아웃 출력
pwntools
파이썬으로 잇스플로잇 스크립트를 작성하다 보면, 자주 사용하게 되는 함수들이 존재함.
패킹함수(정수 -> 리틀 엔디언 바이트 배열), 언패킹 함수 등
함수들을 반복적으로 구현하는 것은 비효율적이기 때문에 시스템 해커들이 pwntools라는 파이썬 모듈을 제작함.
익스플로잇의 대부분이 pwntools를 이용하여 제작 및 공유됨.
pwntools API 사용법
1. process & remote
process : 로컬 바이너리 대상 익스플로잇 함수 -> 익스플로잇 테스트 및 디버깅
remote : 원격 서버 대상 익스플로잇 함수
from pwn import *
p = process('./test') #로컬 바이너리 'test'를 대상으로 익스플로잇 수행
p = remote('example.com', 31337) #'example.com'의 31337 포트에서 실행 중인 프로세스를 대상으로 익스플로잇 수행
2. send
데이터를 프로세스에 전송하기 위해 사용함. pwntools에는 관련된 다양한 함수가 정의되어있음.
from pwn import *
p = process('./test')
p.send('A') # ./test에 'A'를 입력
p.sendline('A') #./test에 'A'+'\n'을 입력
p.sendafter('hello', 'A') #./test가 'hello'를 출력하면 'A'를 입력
p.sendlineafter('hello', 'A') #./test가 'hello'를 출력하면, 'A'+'\n'을 입력
3. recv
프로세스에서 데이터를 받기 위해 사용.
* recv()와 recvn()의 차이점
recv(n)은 최대 n바이트를 받는 것으로, 그만큼을 받지 못해도 에러를 발생시키지 않지만, recvn(n)의 경우 정확히 n바이트의 데이터를 받지 못하면 계속 기다림.
from pwn import *
p = process('./test')
data = p.recv(1024) #p가 출력하는 데이터를 최대 1024바이트까지 받아서 data에 저장
data = p.recvline() #p가 출력하는 데이터를 개행문자를 만날 때까지 받아서 data에 저장
data = p.recvn(5) #p가 출력하는 데이터를 5바이트만 받아서 data에 저장
data = p.recvntil('hello') #p가 출력하는 데이터를 'hello'가 출력될 때까지 받아서 data에 저장
data = p.recvall() #p가 출력하는 데이터를 프로세스가 종료될 떄까지 받아서 data에 저장
4. packing & unpacking
익스플로잇을 작성할 때 어떤 값을 리틀 엔디언의 바이트 배열로 변경하거나 역과정을 거쳐야 하는 경우가 자주 존재함.
#!/usr/bin/python3
#Name: pup.py
from pwn import *
s32 = 0x41424344
s64 = 0x4142434445464748
print(p32(s32))
print(p64(s64))
s32 = "ABCD"
s64 = "ABCDEFGH"
print(hex(u32(s32)))
print(hex(u64(s64)))
5. interactive
셀을 획득했거나, 익스플로잇의 특정 상황에 직접 입력을 주면서 출력을 확인하고 싶을 때 사용하는 함수.
호출하고 나면 터미널로 프로세스에 데이터를 입력하고, 프로세스의 출력을 확인할 수 있다.
from pwn import *
p = process('./test')
p.interactive()
6. ELF
ELF 헤더에는 익스플로잇에 사용될 수 있는 각종 정보가 기록되어있음. pwntools를 사용하면 이 정보들을 쉽게 참조할 수 있음.
from pwn import *
e= ELF('./test')
puts_plt = e.plt['puts'] #./test에서 puts()의 PLT 주소를 찾아서 puts_plt에 저장
read_got = e.got['read'] #./test에서 read()의 GOT 주소를 찾아서 read_got에 저장
7. context.log
익스플로잇에 버그가 발생하면 익스플로잇도 디버깅 해야 함. pwntools에는 디버스의 편의를 돕는 로깅 기능이 있으며, 로그 레벨은 context.log_level 변수로 조절할 수 있음.
from pwn import *
context.log_level = 'error' # 에러만 출력
context.log_level = 'debug' # 대상 프로세스와 익스플로잇 간에 오가는 모든 데이터를 화면에 출력
context.log_level = 'info' # 비교적 중요한 정보들만 출력
8. context.arch
pwntools는 셸 코드를 생성하거나, 코드를 어셈블, 디스어셈블하는 기능 등을 갖고 있는데, 이들은 공격 대상의 아키텍처에 영향을 받는다. 그래서 pwntools는 아키텍처 정보를 프로그래머가 지정할 수 있게 하며, 이 값에 따라 몇몇 함수들의 동작이 달라진다.
from pwn import *
context.arch = "amd64" # x86-64 아키텍처
context.arch = "1386 # x86 아키텍처
context.arch = "arm" # arm 아키텍처
9. shellcraft
pwntools에는 자주 사용되는 셸 코드들이 저장되어있어서, 공격에 필요한 셸 코드를 쉽게 꺼내 쓸 수 있게 해준다. 그러나 정적으로 생성된 셸 코드는 셸 코드가 실행될 때의 메모리 상태를 반영하지 못한다. 또한, 프로그램에 따라 입력할 수 있는 셸 코드의 길이나, 구성 가능한 문자의 종류 제한이 있을 수 있는데 이런 조건들도 반영하기 어렵다.
-> 제약 조건이 존재 => 직접 셸 코드 작성
pwnlib.shellcraft.amd64 — Shellcode for AMD64 — pwntools 4.8.0 documentation
#!/usr/bin/python3
#Name: shellcraft.py
from pwn import *
context.arch = 'amd64' # 대상 아키텍처 x86-64
code = shellcraft.sh() # 셸을 실행하는 셸 코드
print(code)
10. asm
pwntools는 어셈블 기능을 제공한다. 이 기능도 대상 아키텍처가 중요하기때문에 아키텍처를 미리 지정해야한다.
#!/usr/bin/python3
#Name: asm.py
from pwn import *
context.arch = 'amd64' # 익스플로잇 대상 아키텍처 'x86-64'
code = shellcraft.sh() # 셸을 실행하는 셸 코드
code = asm(code) # 셸 코드를 기계어로 어셈블
print(code)
pwntools 실습
rao 익스플로잇
rao.py를 작성하고 rao.c를 컴파일 해준 뒤 python3 rao.py를 예제에 나와있는대로 진행해봤다. id를 입력했을 때 잘 되지 않았다.
정확히 어떤 실습을 하라는건지 제대로 된 설명이 없어서 당황했었다. 그래서 해당 실습을 진행한 다른 블로그 글을 좀 뒤져보기도 하고, 일단 배운 pwntools API 들이 어떤게 사용된건지 Figure.17 이미지를 확인했다.
p = process('./rao') -> 로컬 바이너리 rao를 대상으로 익스플로잇 수행
get_shell = 0x4005a7 -> 예제 코드의 get_shell() 함수 주소를 말해주는 것 같다. 이걸 보고 pwngdb로 get_shell()이 호출되는 부분을 보고 함수 주소가 0x4005a7인지 확인해보기로 했다.
readelf로 rao의 진입점 주소가 0x401060인 것을 확인했다.
왜 get_shell 함수가없을까 했는데 생각해보니 rao.c에서 get_shell을 호출하지를 않았다.
셸 코드를 다음 시간에 공부하고 난 다음에 다시 공부해야할 것 같다.
일단은 마저 코드에서 api가 어떤 식으로 사용됐는지만 확인해보고 끝낸다.
p.sendline(payload) -> ./rao에 payload와 \n을 입력
p.interactive() -> 셸을 얻고나서 터미널로 프로세스에 데이터를 입력하고 데이터 출력을 확인하는 communication이 이루어지게 하는 역할
'Dreamhack' 카테고리의 다른 글
시스템 해킹 quiz.c (0) | 2022.07.22 |
---|---|
웹 해킹 Stage 2 (0) | 2022.07.22 |
시스템 해킹 Stage 2 (0) | 2022.07.18 |
시스템 해킹 Stage 1 (0) | 2022.07.18 |
리버싱 09 - 혼자 실습 (0) | 2022.07.12 |