BROP - Blind Return Oriented Programming
기본 정보
이 공격의 목표는 취약한 이진 파일에 대한 어떠한 정보도 없이 버퍼 오버플로우를 통해 ROP를 남용하는 것입니다. 이 공격은 다음 시나리오에 기반을 두고 있습니다:
스택 취약점 및 그것을 트리거하는 방법에 대한 지식.
충돌 후 서버 응용 프로그램이 다시 시작되는 서버 응용 프로그램.
공격
1. 취약한 오프셋 찾기 서버의 오작동이 감지될 때까지 한 글자 더 보내기
2. 캐너리 무차별 대입하여 누출하기
3. 저장된 RBP 및 RIP 무차별 대입하여 누출하기
이러한 프로세스에 대한 자세한 정보는 여기 (BF Forked & Threaded Stack Canaries) 및 여기 (BF Addresses in the Stack)에서 찾을 수 있습니다.
4. 중지 가젯 찾기
이 가젯은 기본적으로 ROP 가젯에 의해 실행된 흥미로운 작업이 충돌하지 않았기 때문에 확인할 수 있도록 합니다. 일반적으로 이 가젯은 실행을 중지하고 특정 ROP 가젯이 실행되었음을 확인하기 위해 ROP 가젯을 찾을 때 ROP 체인의 끝에 위치합니다.
5. BROP 가젯 찾기
이 기술은 ret2csu 가젯을 사용합니다. 이는 이 가젯에 액세스하면 중간에 일부 명령이 있기 때문에 rsi
및 **rdi
**를 제어할 수 있는 가젯을 얻을 수 있기 때문입니다:
다음은 이러한 가젯들입니다:
pop rsi; pop r15; ret
pop rdi; ret
이러한 가젯을 사용하면 함수의 2개의 인수를 제어할 수 있음을 주목하세요.
또한, ret2csu 가젯은 매우 독특한 서명을 가지고 있습니다. 이는 스택에서 6개의 레지스터를 팝할 것입니다. 따라서 다음과 같은 체인을 보내면:
'A' * 오프셋 + 캐너리 + rbp + 주소 + 0xdead * 6 + 중지
만약 STOP이 실행된다면, 이는 기본적으로 스택에서 6개의 레지스터를 팝하는 주소가 사용되었음을 의미합니다. 또는 사용된 주소가 또한 STOP 주소였음을 의미합니다.
이 마지막 옵션을 제거하기 위해 다음과 같은 새로운 체인을 실행하고 이전 체인이 6개의 레지스터를 팝했음을 확인하기 위해 STOP 가젯이 실행되지 않아야 합니다:
'A' * 오프셋 + 캐너리 + rbp + 주소
ret2csu 가젯의 주소를 알면 rsi
및 rdi
를 제어하는 가젯의 주소를 추론할 수 있습니다.
6. PLT 찾기
PLT 테이블은 0x400000 또는 스택에서 누출된 RIP 주소 (PIE가 사용 중인 경우)부터 검색할 수 있습니다. 테이블의 엔트리는 16B(0x10B)로 구분되며 함수가 호출될 때 인수가 올바르지 않아도 서버가 충돌하지 않습니다. 또한, PLT + 6B의 주소를 확인해도 충돌하지 않습니다. 이는 첫 번째로 실행되는 코드이기 때문입니다.
따라서 다음과 같은 동작을 확인하여 PLT 테이블을 찾을 수 있습니다:
'A' * 오프셋 + 캐너리 + rbp + 주소 + 중지
-> 충돌 없음'A' * 오프셋 + 캐너리 + rbp + (주소 + 0x6) + 중지
-> 충돌 없음'A' * 오프셋 + 캐너리 + rbp + (주소 + 0x10) + 중지
-> 충돌 없음
7. strcmp 찾기
strcmp
함수는 비교되는 문자열의 길이를 나타내는 레지스터 **rdx
**를 설정합니다. **rdx
**는 세 번째 인수이며 나중에 프로그램을 누출하기 위해 write
를 사용하려면 0보다 큰 값이어야 합니다.
이제 우리는 이제 함수의 처음 2개의 인수를 제어할 수 있기 때문에 **strcmp
**의 위치를 PLT에서 찾을 수 있습니다. 이를 통해 다음과 같은 동작을 사용하여 **strcmp
**의 위치를 찾을 수 있습니다:
strcmp(<읽을 수 없는 주소>, <읽을 수 없는 주소>) -> 충돌
strcmp(<읽을 수 없는 주소>, <읽을 수 있는 주소>) -> 충돌
strcmp(<읽을 수 있는 주소>, <읽을 수 없는 주소>) -> 충돌
strcmp(<읽을 수 있는 주소>, <읽을 수 있는 주소>) -> 충돌 없음
PLT 테이블의 각 엔트리를 호출하거나 PLT slow path를 사용하여 이를 확인할 수 있습니다. 이는 기본적으로 PLT 테이블의 엔트리 + 0xb (즉, **dlresolve
**를 호출)를 호출한 다음 스택에서 조사하려는 엔트리 번호 (0부터 시작)를 따릅니다:
strcmp(<읽을 수 없는 주소>, <읽을 수 있는 주소>) -> 충돌
b'A' * 오프셋 + 캐너리 + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0x300) + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + 중지
-> 충돌 발생strcmp(<읽을 수 있는 주소>, <읽을 수 없는 주소>) -> 충돌
b'A' * 오프셋 + 캐너리 + rbp + (BROP + 0x9) + p64(0x300) + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + 중지
strcmp(<읽을 수 있는 주소>, <읽을 수 있는 주소>) -> 충돌 없음
b'A' * 오프셋 + 캐너리 + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + 중지
기억하세요:
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가 발견됨
자동 활용
참고 자료
Last updated