BROP - Blind Return Oriented Programming
Last updated
Last updated
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
이 공격의 목표는 취약한 바이너리에 대한 정보 없이 버퍼 오버플로우를 통해 ROP를 악용할 수 있는 능력입니다. 이 공격은 다음 시나리오를 기반으로 합니다:
스택 취약점과 이를 유발하는 방법에 대한 지식.
충돌 후 재시작되는 서버 애플리케이션.
이 프로세스에 대한 더 많은 정보는 여기 (BF Forked & Threaded Stack Canaries)와 여기 (BF Addresses in the Stack)에서 찾을 수 있습니다.
이 가젯은 기본적으로 ROP 가젯에 의해 흥미로운 무언가가 실행되었음을 확인할 수 있게 해줍니다. 실행이 충돌하지 않았기 때문입니다. 일반적으로 이 가젯은 실행을 중지하는 것이며, 특정 ROP 가젯이 실행되었음을 확인하기 위해 ROP 체인의 끝에 위치합니다.
이 기술은 ret2csu 가젯을 사용합니다. 이는 이 가젯에 접근하면 **rsi
**와 **rdi
**를 제어할 수 있는 가젯을 얻기 때문입니다:
이것들이 가젯입니다:
pop rsi; pop r15; ret
pop rdi; ret
이 가젯을 사용하면 함수 호출의 2개의 인자를 제어할 수 있음을 주목하세요.
또한, ret2csu 가젯은 매우 독특한 서명을 가지고 있습니다. 왜냐하면 스택에서 6개의 레지스터를 팝하기 때문입니다. 따라서 다음과 같은 체인을 보내면:
'A' * offset + canary + rbp + ADDR + 0xdead * 6 + STOP
STOP이 실행되면, 이는 기본적으로 스택에서 6개의 레지스터를 팝하는 주소가 사용되었다는 것을 의미합니다. 또는 사용된 주소가 또한 STOP 주소였다는 것입니다.
이 마지막 옵션을 제거하기 위해 다음과 같은 새로운 체인이 실행되며, 이전 체인이 6개의 레지스터를 팝했음을 확인하기 위해 STOP 가젯을 실행하지 않아야 합니다:
'A' * offset + canary + rbp + ADDR
ret2csu 가젯의 주소를 알면 rsi
와 rdi
를 제어할 가젯의 주소를 유추할 수 있습니다.
PLT 테이블은 0x400000 또는 스택에서 유출된 RIP 주소에서 검색할 수 있습니다 (만약 PIE가 사용되고 있다면). 테이블의 항목은 16B (0x10B)로 구분되어 있으며, 하나의 함수가 호출될 때 서버는 인자가 올바르지 않더라도 충돌하지 않습니다. 또한, PLT의 항목 주소 + 6B도 충돌하지 않습니다. 이는 첫 번째 코드가 실행되기 때문입니다.
따라서 다음 동작을 확인하여 PLT 테이블을 찾을 수 있습니다:
'A' * offset + canary + rbp + ADDR + STOP
-> 충돌 없음
'A' * offset + canary + rbp + (ADDR + 0x6) + STOP
-> 충돌 없음
'A' * offset + canary + rbp + (ADDR + 0x10) + STOP
-> 충돌 없음
strcmp
함수는 비교되는 문자열의 길이를 rdx
레지스터에 설정합니다. **rdx
**는 세 번째 인자이며, 나중에 프로그램을 유출하기 위해 0보다 커야 합니다.
이제 함수의 첫 두 인자를 제어할 수 있는 사실을 바탕으로 PLT에서 **strcmp
**의 위치를 찾을 수 있습니다:
strcmp(<읽지 않는 주소>, <읽지 않는 주소>) -> 충돌
strcmp(<읽지 않는 주소>, <읽는 주소>) -> 충돌
strcmp(<읽는 주소>, <읽지 않는 주소>) -> 충돌
strcmp(<읽는 주소>, <읽는 주소>) -> 충돌 없음
이것은 PLT 테이블의 각 항목을 호출하거나 PLT 느린 경로를 사용하여 확인할 수 있습니다. 이는 기본적으로 PLT 테이블의 항목 + 0xb (이는 **dlresolve
**를 호출함) 후 스택에 탐색하고자 하는 항목 번호 (0부터 시작)를 배치하여 모든 PLT 항목을 스캔합니다:
strcmp(<읽지 않는 주소>, <읽는 주소>) -> 충돌
b'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0x300) + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
-> 충돌
strcmp(<읽는 주소>, <읽지 않는 주소>) -> 충돌
b'A' * offset + canary + rbp + (BROP + 0x9) + p64(0x300) + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
strcmp(<읽는 주소>, <읽는 주소>) -> 충돌 없음
b'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
기억하세요:
BROP + 0x7는 **pop RSI; pop R15; ret;
**를 가리킵니다.
BROP + 0x9는 **pop RDI; ret;
**를 가리킵니다.
PLT + 0xb는 dl_resolve 호출을 가리킵니다.
strcmp
를 찾으면 **rdx
**를 0보다 큰 값으로 설정할 수 있습니다.
일반적으로 rdx
는 이미 0보다 큰 값을 가지고 있으므로 이 단계는 필요하지 않을 수 있습니다.
마지막으로, 바이너리를 유출하기 위해 데이터를 유출하는 가젯이 필요합니다. 이 시점에서 2개의 인자를 제어하고 rdx
를 0보다 크게 설정할 수 있습니다.
이를 위해 악용할 수 있는 일반적인 함수는 3개가 있습니다:
puts(data)
dprintf(fd, data)
write(fd, data, len(data)
그러나 원본 논문에서는 write
함수만 언급하므로 이에 대해 이야기하겠습니다:
현재 문제는 PLT 내에서 write 함수의 위치를 모른다는 것과 데이터를 소켓으로 전송할 fd 번호를 모른다는 것입니다.
하지만 PLT 테이블의 위치는 알고 있으며, 그 행동을 기반으로 write를 찾을 수 있습니다. 그리고 우리는 서버와 여러 연결을 생성하고 높은 FD를 사용하여 우리의 연결 중 하나와 일치하기를 희망할 수 있습니다.
이 함수들을 찾기 위한 행동 서명:
'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0) + p64(0) + (PLT + 0xb) + p64(ENTRY) + STOP
-> 데이터가 출력되면, puts가 발견된 것입니다.
'A' * offset + canary + rbp + (BROP + 0x9) + FD + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb) + p64(ENTRY) + STOP
-> 데이터가 출력되면, dprintf가 발견된 것입니다.
'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + (RIP + 0x1) + p64(0x0) + (PLT + 0xb ) + p64(STRCMP ENTRY) + (BROP + 0x9) + FD + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb) + p64(ENTRY) + STOP
-> 데이터가 출력되면, write가 발견된 것입니다.
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)