// gcc -o master master.c -pthread
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
char *global_buffer;
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(60);
}
void get_shell() {
system("/bin/sh");
}
void *thread_routine() {
char buf[256];
global_buffer = buf;
}
void read_bytes(char *buf, size_t size) {
size_t sz = 0;
size_t idx = 0;
size_t tmp;
while (sz < size) {
tmp = read(0, &buf[idx], 1);
if (tmp != 1) {
exit(-1);
}
idx += 1;
sz += 1;
}
return;
}
int main(int argc, char *argv[]) {
size_t size;
pthread_t thread_t;
size_t idx;
char leave_comment[32];
initialize();
while(1) {
printf("1. Create thread\n");
printf("2. Input\n");
printf("3. Exit\n");
printf("> ");
scanf("%d", &idx);
switch(idx) {
case 1:
if (pthread_create(&thread_t, NULL, thread_routine, NULL) < 0)
{
perror("thread create error");
exit(0);
}
break;
case 2:
printf("Size: ");
scanf("%d", &size);
printf("Data: ");
read_bytes(global_buffer, size);
printf("Data: %s", global_buffer);
break;
case 3:
printf("Leave comment: ");
read(0, leave_comment, 1024);
return 0;
default:
printf("Nope\n");
break;
}
}
return 0;
}
got overwrite 가능, code, data영역 고정
pthread_create 함수를 실행하면 새로운 쓰레드를 실행하는데 argument로 전달된 thread_routine 함수가 새로운 쓰레드로 실행이 된다. 쓰레드 함수에 선언된 변수는 TLS와 인접한 영역에서 할당이 된다. 따라서 BOF가 발생하면 master canary 값을 덮을 수 있다.
익스 시나리오는 CASE1을 통해 global 변수가 TLS 영역을 가리키게 만들고 CASE2을 통해 master canary 값을 덮고
CASE3을 통해 카나리 값을 덮고 ret을 get_shell 함수로 바꿔보자
thread_routine 함수에 진입해 master canary 위치와 buf의 위치를 알아냈다.
차이는 2376이다. 따라서 CASE2에서 size을 2376주고 8byte를 더 입력하면 master canary 값이 덮인다.
master canary 값을 B*8로 덮어본다.
[그림 1-3]을 실행하면 master canary값이 B로 덮여야하는데 [그림 1-4]을 보면 안 덮여있다...
master canary 주소도 달라서.. 이를 확인하기 위해 다시 gdb로 read_bytes 함수를 분석했다.
[그림 1-2]는 thread_routine 함수 내부이고 [그림 1-5]는 read_bytes 함수 내부이다. 차이점은 master canary 주소가 다르다는 거다. 이를 통해 master canary는 메모리에 여러 개 존재하는데 함수마다 카나리 값을 가져오는 주소가 다르다는 걸 알았다.
[그림 1-6]을 통해 main 함수와 read_bytes 함수는 같은 주소에서 카나리 값을 가져오는 걸 확인했다.
따라서 0x7ffff7fda768 주소에 있는 master canary 값을 변조해야한다.
하지만 global_buffer 주소와 0x7ffff7fda768 주소는 차이가 너무 심했다.. 0x818988 만큼 차이가 난다....
따라서 master canary 값을 변조하는 건 안된다고 생각이 들었다...
다시 익스를 생각하다가 CASE2를 보면 [그림 1-7] 코드를 확인할 수 있는데 내가 입력한 data를 출력해 준다. 따라서 카나리 값을 leak 해야겠다 생각했다. 메모리에 있는 카나리 값은 동일하기 때문에 2377로 카나리 1byte를 덮어 카나리를 leak 해보자
카나리 leak이 되었다.
이제 CASE3을 통해 ret 값을 덮어보자
[그림 2-1] 빨간색 - read가 들어가는 주소, 파란색 - 카나리, 노란색 - ret이다.
[그림 2-1] 참조해서 익스를 짜면 [그림 2-2]가 된다.
EOF가 뜬다... gdb로 분석하면 ret에 get_shell 주소가 잘 들어갔는데 안된다..
찾아보니까 익스가 맞는데 안되면 stack alignment가 깨져있어서 안되는 거란다.
따라서 ret 가젯을 추가하고 익스를 하면 성공한다.
remote로 해보면 안 된다 ㅋㅋㅋ..... local에서는 되는데 remote에서는 안된다..? -> 버전을 확인해 보자
local 버전은 18.04인데 문제 버전은 16.04이다.
따라서 16.04 버전에서의 카나리 offset을 구해서 익스해야한다.
나는 docker을 이용했다.
thread_routine에서 global_buffer 값을 알아오고 read_bytes에서 카나리 값을 find 명령어로 찾아본다.
아마 0x7fad10fe3728 주소에서 카나리 값을 가져올 것이다. 0x7fad10fe3728 주소와 buf의 주소를 빼면 0x8e8이 나온다.
카나리를 leak 해야 하므로 0x8e9로 바꾸고 익스를 해보자
성공!
local 익스까지는 빨리했지만 docker 설치하고 gdb 버전 업데이트 하려다가 삽질을 너무했다.. 그래도 docker 사용법을 알아서 낫 배드
알게 된 점
1. 쓸모없는 코드는 없다.... 코드가 왜? 있을까?? 생각하자!
2. payload에 이상이 없는데 익스가 안되면 system을 호출하기 전에 ret instruction을 추가하여 스택 alignment를 맞춰주자
3. 스레드 스택에서 사용하는 마스터 카나리와 메인 스레드 스택에서 사용하는 마스터 카나리는 서로 위치가 다르다. 하지만 값은 같다.
4. x/gx $fs_base+0x28은 해당 함수에서 카나리 값을 가져오는 위치만 알려준다. find 카나리 값은 메모리에 있는 모든 카나리 위치를 보여준다.
5. 익스가 안되면 버전도 확인하자..!
'Dreamhack - pwnable' 카테고리의 다른 글
rtld (0) | 2023.03.12 |
---|---|
environ (0) | 2023.03.06 |
seccomp (0) | 2023.02.13 |
cmd_center (0) | 2023.02.03 |
sint (0) | 2023.02.03 |