BROP - Blind Return Oriented Programming

htARTE (HackTricks AWS Red Team 전문가)를 통해 **제로부터 영웅까지 AWS 해킹 배우기**!

HackTricks를 지원하는 다른 방법:

기본 정보

이 공격의 목표는 취약한 이진 파일에 대한 어떠한 정보도 없이 버퍼 오버플로우를 통해 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' * offset + canary + rbp + ADDR + 0xdead * 6 + STOP

만약 STOP이 실행된다면, 이는 기본적으로 스택에서 6개의 레지스터를 팝하는 주소가 사용되었음을 의미합니다. 또는 사용된 주소가 또한 STOP 주소였음을 의미합니다.

이 마지막 옵션을 제거하기 위해 다음과 같은 새로운 체인을 실행하고 이전 체인이 6개의 레지스터를 팝했음을 확인하도록 STOP 가젯이 실행되지 않아야 합니다:

'A' * offset + canary + rbp + ADDR

ret2csu 가젯의 주소를 알면 rsirdi를 제어하는 가젯의 주소를 추론할 수 있습니다.

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보다 큰 값이어야 합니다.

이제 우리는 함수의 처음 2개의 인수를 제어할 수 있기 때문에 PLT를 기반으로 strcmp의 위치를 찾을 수 있습니다:

  • strcmp(<읽을 수 없는 주소>, <읽을 수 없는 주소>) -> 충돌

  • strcmp(<읽을 수 없는 주소>, <읽을 수 있는 주소>) -> 충돌

  • strcmp(<읽을 수 있는 주소>, <읽을 수 없는 주소>) -> 충돌

  • strcmp(<읽을 수 있는 주소>, <읽을 수 있는 주소>) -> 충돌 없음

이를 확인하기 위해 PLT 테이블의 각 엔트리를 호출하거나 PLT slow path를 사용할 수 있습니다. 이는 PLT 테이블 + 0xb (즉, **dlresolve**를 호출)를 호출한 후 스택에서 조사하려는 엔트리 번호 (0부터 시작)를 따르는 것으로 구성됩니다:

  • 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보다 큰 값이 들어 있기 때문에 이 단계는 필요하지 않을 수 있습니다.

### 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