Python 과 파이프( | )를 이용하여 간단한 스택 오버플로우 익스플로잇
Python 으로 페이로드 생성
Pipe 통해 프로그램에 전달
익스플로잇이 조금만 복잡해져도 아래와 같은 이유로 위와 같은 방식 이용 어려움
- 페이로드 생성하기 위해 복잡한 연산 필요
- 프로세스와 반복적으로 데이터 주고 받아야 함
그래서 펄, Python, C 등으로 익스플로잇 스크립트 또는 바이너리 제작하여 사용
socket 모듈을 사용한 초기 파이썬 익스플로잇 스크립트의 예
Python 으로 여러 개의 익스플로잇 스크립트 작성하다보면, 자주 사용하게 되는 함수 존재
Ex. 아래의 예시들은 익스플로잇 과정에 거의 항상 필요
- 정수를 리틀 엔디언의 바이트 배열로 바꾸는 패킹 함수
- 그 역을 수행하는 언패킹 함수 등
- 지난 코스에 사용한 get_shell() 함수의 주소를 리틀 엔디언을 적용하여 변환
위와 같은 함수들을 반복적으로 구현하는 것은 비효율적.
pwntools 라는 파이썬 모듈에 이런 것들을 모음
용어 정리
- exploit : 보안 취약점을 이용한 공격.
- payload : 사용에 있어서 전송되는 데이터. 보안에서는 멀웨어의 일부분.
- 멀웨어 : 컴퓨터 또는 설치한 모든 소프트웨어에 해를 끼치려 고안한 소프트웨어
민감한 정보를 훔치거나 사용자 모르게 사용자의 이메일 계정에서 가짜 메일 전송 가능- 엔디언 : 컴퓨터의 메모리와 같은 1차원 공간에 여러 개의 연속된 대상을 배열하는 방법
바이트를 배열하는 방법을 특히 Byte Order 라 함.
큰 단위가 앞에 나오는 Big-endian
작은 단위가 앞에 나오는 Little-endian
둘 모두 지원 & 두 경우에 속하지 않는 Middle-endian- 아키텍처 : 컴퓨터 시스템의 하드웨어 구조. CPU, Register, Memory, I/O 등에 대한
전반적인 기계적 구조와 이를 설계하는 방법
파일 종류
종류 | 설명 |
---|---|
텍스트 파일 | 1. 문자로 구성된 파일 |
2. 대부분 ASCII 문자로 이뤄진 파일 또는 사람이 사용하는 한글 영문 등과 같은 문장들로 이뤄진 파일 | |
3. 소스코드 파일(.cpp), README.txt 등 | |
바이너리 파일 | 1. 데이터로 구성된 파일 |
2. 모든 파일은 0과 1로 이루어짐 | |
3. 이미지파일(.png), 데이터파일(.dat), 실행파일(.exe) 등 |
공식 메뉴얼 pwntools
로컬 프로세스를 실행하여 통신할 때 사용되는 클래스
p = process("binary file")
process 클래스는 로컬에서 바이너리를 실행할 때 환경 변수를 직접 설정할 수 있고,
프로그램을 실행할 때 인자를 전달해야할 경우 전달 가능
exploit 을 테스트하고 디버깅하기 위해 사용
< Dreamhack 예시 >
< haerinn 블로그 예시 >
이 예시는 로컬 파일시스템에 존재하는 /home/theori/binary 바이너리를 실행
Remote
원격 서비스에 접속하여 통신할 때 사용되는 클래스
p = remote("IP", port)
특정 주소에 열려있는 특정 포트에 TCP 연결을 맺는다.
연결이 성공적으로 맺어지면 remote 객체를 리턴
대상 서버를 실제로 공격하기 위해 사용
< Dreamhack 예시 >
< haerinn 블로그 예시 >
이 예시는 127.0.0.1 주소에 열려있는 5000번 포트에 TCP 연결을 맺음.
언제 사용하는가?
소켓에 연결하거나 프로그램을 실행할 때 데이터를 보내고 읽어들이는 작업이 필요하다.
이때 사용하는 것이 send 와 recv
조건
연결이 맺어진 객체가 존재해야 함
연결이 맺어진 객체에 데이터를 보내는 메소드
p = remote("IP", port)
p.send("Data")
+ sendline : 연결이 맺어진 객체에 개행을 포함하는 데이터를 보내는 메소드
< haerinn 블로그 예시 >
위 예시는 127.0.0.1 의 22번 포트에 연결한 후 AAAA 를 전송
연결이 맺어진 객체로부터 수신한 데이터를 리턴하는 메소드
p = remote("IP", Port)
print p.recv(byte)
< haerinn 블로그 예시 >
+ recvline : 연결이 맺어진 객체로부터 개행까지 수신하여 리턴하는 메소드
이때, 읽을 바이트 수를 지정해주지 않고 개행까지 읽음
+ recvuntil : 연결이 맺어진 객체에서 원하는 문자 혹은 문자열까지 읽는 메소드
< haerinn 블로그 예시 >
recv(n) : 최대 n 바이트를 받는 것 = n 만큼 받지 못해도 no error
recvn(n) : 정확히 n 바이트의 데이터를 받지 못하면 계속 대기
< Dreamhack 예시 >
Exploit 작성하다 보면 어떤 값을 리틀 엔디언의 바이트 배열로 변경하거나, 역의 과정을 거쳐야 하는 경우가 존재함. 이때 packing 과 unpacking 사용
즉, Packing 과 Unpacking 은 각각의 데이터 크기에 맞게 데이터를 변환할 때 사용
두 함수는 리틀 엔디언 혹은 빅 엔디언 형태로 지정해줄 수 있고,
지정하지 않는다면 리틀 엔디언 형태로 변환
데이터 크기에 따라 함수가 존재하기 때문에 데이터에 맞게 사용해야 한다
< Dreamhack 예시 >
직접 실행
위와 같은 결과를 얻었다.
패킹을 했을 때, little endian 의 형태로 나옴을 확인할 수 있었고, endian='big'을 통해
big endian 의 형태로 반환하는 방법을 적용해봤다.
재밌는 사실은
0x41424344 를 packing 한 결과는 ABCD 의 역순이었고,
ABCD 를 unpacking 한 결과는 1424344 의 역순이었다.
unpacking 에서는 hex 함수를 사용하는데 이는 unpacking 결과가 정수형이기 때문이다
의문
< haerinn 블로그 예시 >
1. p8 : 1 바이트의 데이터를 패킹
0x41을 문자 형태로 변환하여 A를 리턴한 것을 확인
< haerinn 블로그 예시 >
1. u8 : 1 바이트의 데이터를 언패킹
"A" 를 정수 형태로 변환하여 65 리턴 확인
2. u16 : 2 바이트의 데이터를 언패킹
"AB" 를 정수형태로 변환하여 16961 리턴 확인
hex 함수 사용하여 16진수로 변환하면 0x4241 인 것을 알 수 있다.
3. u32 : 4 바이트의 데이터를 언패킹
"ABCD" 를 정수형태로 변환하여 1145258561 를 리턴한 것 확인
hex 함수를 사용하여 16 진수로 변환하면 0x44434241
4. u64 : 8 바이트의 데이터를 언패킹
셀을 획득했거나, exploit 의 특정 상황에 직접 입력을 주면서 출력을 확인하고 싶을 때 사용
호출하면 터미널로 프로세스에 데이터 입력 -> 프로세스의 출력 확인
ELF 헤더에는 exploit 에 사용될 수 있는 각종 정보가 기록
pwntools 사용하면 쉽게 참조 가능
exploit 에 버그가 발생하면 exploit 도 디버깅해야 함.
pwntools 에는 디버그의 편의를 돕는 로깅 기능이 존재
로그 레벨은 context.log_level 변수로 조절 가능
pwntools 는 셀코드를 생성하거나, 코드를 어셈블, 디스어셈블하는 기능 등을 가지고 있음.
이들은 공격 대상의 아키텍처에 영향을 받음
따라서, pwntools 는 아키텍처 정보를 프로그래머가 지정할 수 있게 하며,
이 값에 따라 몇몇 함수들의 동작이 달라짐.
pwntools 에는 자주 사용되는 셀 코드들이 저장 => 공격에 필요한 셀 코드를 쉽게 사용 가능
한계
여러 제약 조건이 존재하는 상황에서는 직접 셀 코드를 작성하는 것이 좋다
pwntools 는 어셈블 기능을 제공
대상 아키텍처가 중요 => 아키텍처를 미리 지정
위 내용이 rao 코드다.
내용은 이후 커리큘럼에서 배운다고하니 그냥 넘어감
이 실습의 결과로 나와야할 것은 아래와 같다.
처음 주어진 코드는 c 파일이다. 이를 컴파일해서 get_shell의 주소를 얻어야하며,
얻은 주소를 통해 아래의 py 코드를 수정해야함.
- gcc -o rao.c rao 를 이용하여 rao 실행 파일을 만들어준다.
- gdb rao 를 통해 pwndbg 에 접속
- pwndbg 에서 print get_shell 을 하고, 이를 통해 shell 의 주소를 얻는다.
- 얻은 주소의 값을 rao.py 의 'get_shell' 변수에 저장해준다.
( 즉, 위 예시에서 변경되는 값은 0x4005a7 이다 )