BROP - Blind Return Oriented Programming
Basic Information
이 공격의 목표는 취약한 바이너리에 대한 정보 없이 버퍼 오버플로우를 통해 ROP를 악용할 수 있는 능력입니다. 이 공격은 다음 시나리오를 기반으로 합니다:
스택 취약점과 이를 유발하는 방법에 대한 지식.
충돌 후 재시작되는 서버 애플리케이션.
Attack
1. 취약한 오프셋 찾기 서버의 오작동이 감지될 때까지 한 문자를 더 보내기
2. 카나리 무차별 대입 이를 유출하기 위해
3. 스택에서 저장된 RBP 및 RIP 주소를 무차별 대입하여 유출하기
이 프로세스에 대한 더 많은 정보는 여기 (BF Forked & Threaded Stack Canaries)와 여기 (BF Addresses in the Stack)에서 찾을 수 있습니다.
4. 정지 가젯 찾기
이 가젯은 기본적으로 ROP 가젯에 의해 흥미로운 무언가가 실행되었음을 확인할 수 있게 해줍니다. 실행이 충돌하지 않았기 때문입니다. 일반적으로 이 가젯은 실행을 중지하는 무언가가 될 것이며, 특정 ROP 가젯이 실행되었음을 확인하기 위해 ROP 체인의 끝에 위치합니다.
5. BROP 가젯 찾기
이 기술은 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
를 제어할 가젯의 주소를 유추할 수 있습니다.
6. PLT 찾기
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
-> 충돌 없음
7. strcmp 찾기
strcmp
함수는 비교되는 문자열의 길이를 rdx
레지스터에 설정합니다. **rdx
**는 세 번째 인자이며, 나중에 write
를 사용하여 프로그램을 유출하기 위해 0보다 커야 합니다.
이제 함수의 첫 두 인자를 제어할 수 있는 사실을 이용하여 PLT에서 **strcmp
**의 위치를 찾을 수 있습니다:
strcmp(<non read addr>, <non read addr>) -> 충돌
strcmp(<non read addr>, <read addr>) -> 충돌
strcmp(<read addr>, <non read addr>) -> 충돌
strcmp(<read addr>, <read addr>) -> 충돌 없음
이것은 PLT 테이블의 각 항목을 호출하거나 PLT 느린 경로를 사용하여 확인할 수 있습니다. 이는 기본적으로 PLT 테이블의 항목 + 0xb(이는 **dlresolve
**를 호출함) 다음에 스택에 탐색하고자 하는 항목 번호(0부터 시작)를 배치하여 모든 PLT 항목을 스캔하는 것입니다:
strcmp(<non read addr>, <read addr>) -> 충돌
b'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0x300) + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
-> 충돌할 것입니다strcmp(<read addr>, <non read addr>) -> 충돌
b'A' * offset + canary + rbp + (BROP + 0x9) + p64(0x300) + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
strcmp(<read addr>, <read addr>) -> 충돌 없음
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보다 큰 값을 가지고 있으므로 이 단계는 필요하지 않을 수 있습니다.
8. Write 또는 동등한 것 찾기
마지막으로, 바이너리를 유출하기 위해 데이터를 유출하는 가젯이 필요합니다. 이 시점에서 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가 발견된 것입니다.
Automatic Exploitation
References
Original paper: https://www.scs.stanford.edu/brop/bittau-brop.pdf
Last updated