Sechack

[시스템 해킹] 3강 - pwntools 본문

Lecture/pwnable

[시스템 해킹] 3강 - pwntools

Sechack 2021. 12. 9. 23:11
반응형

이번 시간에는 시스템 해킹을 하는데 매우 강력한 python모듈인 pwntools에 대해서 알아보겠습니다.

pwntools는 현재 Wargame이나 CTF에서 pwnable분야의 ELF exploit 문제를 풀이할때 거의 필수적으로 사용되는 모듈입니다. 다른 사람의 풀이를 보더라도 대부분 pwntools를 이용해서 exploit을 작성한다는 것을 알 수 있습니다.

 

따라서 해킹 대회를 준비하거나 Wargame으로 시스템 해킹 실력을 늘리고 싶은 분들께서는 pwntools사용법은 필수적으로 알아야 합니다. 이 강의에서도 앞으로 쭉 exploit은 pwntools를 이용해 작성할 것이므로 이번 시간에 pwntools의 기본적인 사용법을 알아가시기 바랍니다.

 

pwntools의 본질은 자동화 입니다. 시스템 해킹을 해보면 아시겠지만 키보드로는 입력할 수 없는 입력값을 프로그램에 보내야 하거나 복사 붙여넣기로도 무리가 있는 매우 긴 데이터를 입력값으로 보내야 할 경우가 많습니다. 이럴때는 프로그램과 상호작용 할 수 있는 자동화 스크립트를 짜서 입력을 보내야 하는데 이것을 편하게 할 수 있는 모듈이 pwntools입니다.

pwntools에는 데이터 입력, 출력을 자동화하는것 이외에도 심볼을 이용해서 주소를 구해오거나 shellcode 자동 작성, 간단한 rop chain이나 fsb payload 자동 작성, asm to opcode, 바이너리 디스어셈블, 데이터 가공 함수 제공 등 pwnable을 할 때 있으면 편리한 기능들이 많이 있습니다.

 

sudo apt install python3 python3-pip
pip3 install pwntools
sudo apt install libcapstone-dev

 

제 강의를 처음부터 잘 따라온 분들이라면 pwntools가 설치되어 있겠지만 만약 설치되어 있지 않을 경우에는 위의 명령어를 이용해서 설치해주시면 됩니다.

 

 

from pwn import *	#pwntools모듈 import

p = process("filepath")		#프로그램 실행
#r = remote("host", port)	#nc연결

#example
p.sendlineafter("test", "test") #이후 반환받은 객체를 이용해서 로컬 프로그램이나 nc서버와 상호작용
data = p.recv()

p.interactive()		#원하는 작업을 끝낸 후 프로그램과 사용자가 상호작용 할 수 있게끔 열어줌.
			#주로 exploit이 끝난 후 shell이 따이면 실행함.

 

pwntools의 기본적인 구조는 위와 같습니다. 각 코드의 역할은 주석으로 설명해놨습니다.

기본적인 뼈대는 위와 같고 pwntools에서 가장 많이 쓰이는 기능인 입력을 주고 데이터를 받아오는 기능을 알아보겠습니다.

 

 

데이터 보내기

 

from pwn import *

p.send(data)
p.sendline(data)
p.sendafter("recvdata", data)
p.sendlineafter("recvdata", data)

 

위의 예시처럼 pwntools에서 데이터를 전송하는 함수는 총 4가지가 있습니다.

 

p.send(data)

 

send함수는 데이터를 그대로 프로그램으로 입력하는 함수입니다.

 

p.sendline(data)

 

sendline함수는 데이터에 \n(개행 문자)을 붙여서 프로그램으로 입력하는 함수입니다.

 

p.sendafter("recvdata", data)
p.sendlineafter("recvdata", data)

 

sendafter과 sendlineafter은 첫번째 인자로 받아올 데이터를 주고 두번째 인자로 입력할 데이터를 줍니다.

즉 프로그램에서 특정 데이터가 출력된 이후에 입력을 줄 수 있는 함수입니다. 차이라면 함수 이름에서 보이듯이 뒤에 개행이 추가되느냐 마냐의 차이밖에 없습니다.

프로그램에서 아무것도 출력하지 않을 경우를 제외하고는 특정한 데이터를 받아온 뒤에 입력을 하는 after계열의 입력 함수들이 훨씬 안정적입니다.

그냥 send를 하는 경우에는 exploit을 nc로 날릴 경우에 서버 응답속도보다 입력 속도가 더 빠르게 되면서 입력이 꼬이는 경우가 발생할 수 있습니다. 따라서 웬만해서는 데이터를 입력할때 after계열의 함수를 사용하고 부득이하게 send계열의 함수를 사용해야 할 경우에는 함수 사이사이에 sleep을 조금씩 넣어줘야 합니다.

 

 

출력된 데이터 받아오기

 

from pwn import *

p.recv(recvsize)
p.recvuntil("data")
p.recvline()

 

프로그램이 터미널에 출력한 데이터를 받아오는 recv계열의 함수는 3가지가 있습니다.

 

p.recv(recvsize)

 

recv함수는 인자로 지정한 크기만큼 화면에 출력된 데이터를 받아옵니다. 인자 없이 호출할 경우에는 default로 4096 byte만큼 데이터를 받아옵니다.

 

p.recvuntil("data")

 

recvuntil함수는 특정 데이터가 나올때까지 계속해서 데이터를 받아오는 함수입니다. 예를 들어서 출력값이 abcdefghijk이고 recvuntil함수의 인자로 "gh"를 전달하면 abcdefgh까지 받아오게 됩니다. recv계열 함수중에서 가장 많이 쓰이는 함수입니다.

 

p.recvline()

 

recvline함수는 이름 그대로 한 줄. 정확히 말하면 "\n"을 만날때까지 데이터를 받아오는 함수입니다. 상황에 따라서 유용하게 쓰이는 함수입니다.

반응형

 

데이터 가공

 

from pwn import *

p64(0x10)
p32(0x10)
p16(0x10)
p8(0x10)

 

데이터 가공을 하는 기능도 pwntools에서 가장 많이 쓰이는 기능중에 하나입니다.

p계열 함수는 스택에 직접 입력했을 때 우리가 원하는 값이 들어가도록 우리가 인자로 전달한 정수를 리틀 엔디안으로 bytes형태로 변환합니다. 예를 들어서 p64함수의 인자로 0x1020304050607080을 전달하게 된다면 b"\x80\x70\x60\x50\x40\x30\x20\x10"으로 변환됩니다. 이걸 그대로 메모리에 입력하면 프로그램에서는 해당 주소에 있는 값을 우리가 의도한대로 0x1020304050607080으로 인식하게 됩니다.

함수이름이 p64, p32이런식인데 p뒤에 붙은 숫자는 데이터의 크기를 나타냅니다. 64는 8byte, 32는 4byte, 16은 2byte, 8은 1byte가 됩니다.

 

 

from pwn import *

u64(data)
u32(data)
u16(data)
u8(data)

 

u계열의 함수는 p계열의 함수와 반대로 bytes형태를 int형태로 바꿔주는 함수입니다. 주로 스택에 있는 데이터를 화면에 출력한 후에 값을 알아내기 위해서 u계열의 함수를 이용해서 int형태로 바꿔줍니다. 마찬가지로 u뒤에 숫자는 데이터의 크기를 뜻합니다.

 

 

여기까지가 pwntools에서 가장 많이 쓰이고 가장 핵심적인 기능입니다. 생각보다 별거 없습니다.

이제부터는 몰라도 괜찮지만 알면 자주 쓰게되는 유용한 기능에 대해서 알아보겠습니다.

 

 

ELF

 

from pwn import *

e = ELF("binary or libc path")
symboladdr = e.sym["symbolname"]
disasm = e.disasm(startaddr, disasmsize)

 

ELF함수의 인자로 binary file이나 libc file을 주면 ELF객체를 반환합니다.

ELF객체에서는 위와같이 심볼의 주소를 가져올 수 있고 (libc나 PIE가 걸린 binary는 offset을 가져옴) 원하는 주소부터 원하는 주소까지 디스어셈블을 할 수도 있습니다.

 

 

Shellcode

 

from pwn import *

context.arch = "amd64" #amd64, x86등 아키텍처 명시.

asmcode = shellcraft.execve("/bin/sh", 0, 0)
shellcode = asm(asmcode)

 

위와같이 아키텍처를 명시한 후 shellcraft를 이용해서 원하는 Shellcode를 만들기 위한 어셈블리를 생성할 수 있습니다.

execve이외에도 read, write등 다른 syscall도 모두 가능합니다.

asm함수는 어셈블리 언어를 opcode로 변환해줍니다. 지난 시간에서 했던것과 같이 번거로운 과정을 거치지 않아도 직접 코딩한 어셈블리 코드를 asm함수에 넣기만 하면 Shellcode가 완성됩니다.

 

 

추가로 python3에서는 p계열 함수의 반환타입이 bytes이기 때문에 payload를 만들때는 문자열 앞에 b를 붙여서 bytes타입으로 만들어야 합니다. (예시 : b"Sechack")

 

 

이번 시간에는 시스템 해킹을 하는데 거의 필수적으로 필요한 도구인 pwntools에 대해서 알아보았습니다.

다음 시간에는 Stack Buffer Overflow취약점을 이해하고 실제로 exploit을 실습해보는 시간을 가져보도록 하겠습니다.

반응형
Comments