1. 개념

BROP: 소스코드 및 바이너리가 주어지지 않은 상태에서 프로세스의 상태 또는 출력내용으로 공격을 수행하는 기법

= ROP Chain + Brute Force

무작위 대입 과정이 있어서 시간이 오래걸림..

이 기법을 이해하기 앞서 다음 두 가지 가젯에 대해 이해해야 함

STOP Gadget : main() 함수 또는 취약한 함수를 다시 실행하는 주소 (_start 주소일 수도 있고 정말 함수의 시작주소 일 수도 있으나 결과적으로 복귀주소를 이 가젯으로 변경하면 프로세스는 처음 실행했을 때의 출력값을 보인다.)

BROP Gadget : 보통 libc_csu_init 함수에 있는 pop 6개 + ret 가젯을 의미하는데, 이처럼 레지스터를 많이 쓸 수 있는 희소한 가젯을 일컫는다. 이 가젯을 찾는 이유는 주변에 0x9 그리고 0x7 오프셋으로 pop rdi ; ret 가젯과 pop rsi ; ret 가젯을 찾을 수 있기 때문이다. 두 가젯은 모두 system() 함수에서 첫번째 인자로 /bin/sh 를 넣기 위해 사용된다.

[ 도식화 ]

 

2. 공격 순서

정말 아~~무것도 주어지지 않았기 때문에 메모리 릭부터 가젯 구성에 필요한 주소까지 전부 알아내야 한다.

단계를 나름 나눠봤는데 9단계나 된다;; (무엇?)

 

(1) 스택 오버플로우 크기 확인

복귀주소를 변경하기 때문에 당연히 스택에서 오버플로가 날 것을 예상할 수 있다. 간혹 CTF 에서 이 크기를 알려준다고 한다.

파이썬 스크립트로 brute forcing 해서 프로세스가 죽거나 기존의 출력결과와 다른 출력값을 낸다면 스택 오버플로를 의심할 수 있다.

 

(2) STOP Gadget 찾기

오버플로우 크기를 알고 있기 때문에 "A" * SIZE + CodeAddress 를 페이로드로 공격했을 때, 처음 출력값이 나오면 CodeAddress 가 STOP Gadget 이 된다.

** Code 영역을 알아야 하니 checksec 같은 툴을 써서 No PIE 인지 확인하면 될 것

** 코드 세그먼트 크기가 0x1000 이므로, 보통 오프셋은 0 ~ 0x1000 이다. 

당연히 Code Base Address 부터 오프셋 1씩 늘려주면서 찾아야 한다. 역시 brute forcing 이므로 시간이 걸림

 

(3) BROP Gadget 찾기

pop 6개 + ret 가젯을 찾는 과정이기 때문에 페이로드는 "A" * SIZE + CodeAddress + PARAM * 6 + STOP_GADGET 이 된다. PARAM * 6 은 더미로 채우는데 32비트면 4바이트 * 6 = 24 바이트 일 것이고 64비트면 8바이트 * 6 = 48바이트 가 된다.

공격했을 때 처음 출력내용이 그대로 나오면 CodeAddress 는 BROP Gadget 이 된다.

(pop 6개 + ret 이 성공했으니 처음 출력내용이 나오는거니까)

** pwntools 에 p32() 랑 p64() 쓰면 편하게 계산가능하다.

이 가젯을 찾는 이유는 근처에 pop rdi ; ret (offset: 0x9) 과 pop rsi ; ret (0x7) 가젯이 있어서라 했다.

따라서 여기서 얻은 CodeAddress + 0x9 또는 CodeAddress + 0x7 주소를 실제로 사용한다.

당연히 Code Base Address 부터 오프셋 1씩 늘려주면서 찾아야 한다. 역시 brute forcing 이므로 시간이 걸림

 

(4) Printable Function 주소 찾기

출력함수는 C 언어에서 printf 와 puts 함수가 있는데 둘의 차이는 출력했을 때 개행 문자 "\n"가 출력되는지이다.

printf 일 경우 위에서 설명한 가젯 2개를 모두 사용 --> pop rdi ; ret (BROP Gadget +0x9) 과 pop rsi ; ret (+0x7)

puts 일 경우 하나만 사용 --> pop rdi ; ret (BROP Gadget +0x9)

보통 puts 함수를 쓸거라 생각하고 brute forcing 을 한다.

페이로드는 "A" * SIZE + POP_RDI_GADGET + CodeBase + CodeAddress 이다.

CodeBase 는 코드 영역의 시작주소이고, CodeAddress는 Base 부터 오프셋 범위(0 ~ 0x1000) 내로 1씩 증가시킨다. 위 페이로드로 공격했을 때 출력결과가 "\x7fELF" 문자열을 포함한다면 CodeAddress 는 puts@plt 가 된다.

** 리눅스 실행파일은 ELF 구조라 무조건 코드영역의 첫 4바이트는 "\x7fELF"이다. 그래서 CodeBase를 인자로 주고 puts 인지 아닌지 알아낸다.

당연히 Code Base Address 부터 오프셋 1씩 늘려주면서 찾아야 한다. 역시 brute forcing 이므로 시간이 걸림

 

(5) 메모리 덤프

자 이제 puts@plt 를 알아냈으니 출력함수를 사용할 수 있다. 인자로 메모리 영역만 넣어주면 바이너리 정보를 제대로 얻어낼 수 있는 거다.

코드 영역만 필요하니 CodeBase ~ CodeBase + 0x1000 범위를 출력해서 덤프 파일을 만든다.

페이로드는 "A" * SIZE + POP_RDI_GADGET + CodeAddress + PUTS_PLT 가 된다.

** 계속 출력하다보면 프로세스가 처음 출력하는 문자열이 있는데 그 부분 전까지만 포함해야 한다.

보통 파이썬 코드로 data = response[:response.index(처음출력결과)] 라고 작성한다. 문자열의 index 함수는 발견못하면 -1 이라 끝까지 넣기 때문이다. 아무튼 저런식으로 data 를 계속 쌓아서 한번에 파일 출력을 해준다.

** 덤프 파일을 생성하면 리버싱 툴써서 분석한다. radare 를 많이 쓴다.

당연히 CodeAddress 는 오프셋 1씩 늘려주면서 찾아야 한다. 역시 brute forcing 이므로 시간이 걸림

 

(6) Printable Function 의 GOT 주소 찾기

radare 로 덤프한 메모리를 다음과 같은 명령어로 분석을 진행한다.

$ r2 -B <CodeBase> <DumpFileName>

>> pd 10 @ <puts@plt>

검색하면 plt 영역의 코드가 나오는데, 세그먼트 내에 오프셋으로 jmp 할 주소를 계산하기 때문에 실제 GOT 주소를 찾을 수 있다.

 

(7) 메모리 릭 (leak libc address)

puts@got.plt 주소를 인자로 puts@plt 를 호출한다. 이는 libc 영역의 주소를 릭해서 오프셋 계산을 하기 위함이다.

페이로드는 "A" * SIZE + POP_RDI_GADGET + PUTS_GOT + PUTS_PLT 이다.

출력값은 당연히 puts 의 시작주소일 것이다. 이 주소의 끝 3자리 숫자를 가지고 libc 를 탐색한다.

ASLR 걸려있으면 여기서부터는 아래 단계들이 원자적으로 수행되어야 한다.

 

(8) libc 탐색 후 오프셋 찾기

필요한 도구는 libc-databaseLibcSearcher 이다. 사용하기전에 어떤 라이브러리인지 파악해야 한다.

둘다 설치해서 사용법대로 system() 함수와 /bin/sh 문자열의 오프셋을 알아낼 수 있다.

릭한 주소 그대로 써도 되긴 하는데, puts 와 system 그리고 /bin/sh 의 오프셋을 알아낸 다음 익스를 짜는게 덜 귀찮다. (적어도 나는 그랬다.)

보통 LibcSearcher 모듈을 쓸 때 다음과 같이 쓴다.

from LibcSearcher import *

addr_puts_libc = 0x6f690	# 라이브러리에 따라 오프셋이 바뀔 수 있음.

lib = LibcSearcher('puts', addr_puts_libc)
libcBase = addr_puts_libc - lib.dump('puts')
system_addr = libcBase + lib.dump('system')
binsh_addr = libcBase + lib.dump('str_bin_sh')

print 'libc base : ' + hex(libcBase)
print 'system : ' + hex(system_addr)
print 'binsh : ' + hex(binsh_addr)

 

(9) 최종 익스플로잇

여기는 그냥 ROP 기법대로 하면 된다. 위 과정에서 알아낸 오프셋을 기반으로 libc base 에 더해서 실제 주소를 계산해서 페이로드를 짜면 된다.

페이로드는 "A" * SIZE + POP_RDI_GADGET + STR_BINSH_ADDR + SYSTEM_ADDR 이다.

 

 

Reference

https://www.lazenca.net/pages/viewpage.action?pageId=16810286

 

08.BROP(Blind Return Oriented Programming) - TechNote - Lazenca.0x0

Excuse the ads! We need some help to keep our site up. List BROP(Blind Return Oriented Programming) 소프트웨어를 해킹할 때 대략 3가지의 형태의 공격대상이 있습니다.Open-source (예 :  Apache)Open-binary (예 : Internet Explorer)Closed-binary and source (예 :일부 독점 네트워크 서비스)BROP는 Closed-bin

www.lazenca.net

https://blog.h3x0r.kr/2018-12-30-brop/

 

BROP (Blind ROP)

BROP라는 주제로 글을 쓰게된 ipwn(안건희)입니다. 아직 완벽하게 연구했다고는 생각하지 않지만 지금까지의 연구과정을 예제를 통해서 포스팅하려 합니다. 목차 들어가면서 … 필요한 것 ? stop_gadget ? brop_gadget ? exploit 절차 Example binary Server setting Check overflow Get stop_gadget Get brop_gadget Get printable function Dump memory

blog.h3x0r.kr

 

위 자료는 Write-Up 이 있어서 참고하면 된다. 사용한 문제는 HCTF 2018 과 CodeGate 2018 예선문제이다.

(다른 문제 찾으면 추가예정)

 

 

'Security > System' 카테고리의 다른 글

CTF Summary  (0) 2020.02.07
[Heap Overflow] House Of Orange  (0) 2019.07.06
linux system call table  (0) 2019.07.03
[Linux] Lazy Binding  (0) 2019.06.30
ROP Gadget Dictionary  (0) 2019.06.30

+ Recent posts