// gcc -o baby-bof baby-bof.c -fno-stack-protector -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
void proc_init ()
{
setvbuf (stdin, 0, 2, 0); setvbuf (stdout, 0, 2, 0);
setvbuf (stderr, 0, 2, 0);
}
void win ()
{
char flag[100] = {0,};
int fd;
puts ("You mustn't be here! It's a vulnerability!");
fd = open ("./flag", O_RDONLY);
read(fd, flag, 0x60);
puts(flag);
exit(0);
}
long count;
long value;
long idx = 0;
int main ()
{
char name[16];
// don't care this init function
proc_init ();
printf ("the main function doesn't call win function (0x%lx)!\n", win);
printf ("name: ");
scanf ("%15s", name);
printf ("GM GA GE GV %s!!\n: ", name);
printf ("| addr\t\t| value\t\t|\n");
for (idx = 0; idx < 0x10; idx++) {
printf ("| %lx\t| %16lx\t|\n", name + idx *8, *(long*)(name + idx*8));
}
printf ("hex value: ");
scanf ("%lx%c", &value);
printf ("integer count: ");
scanf ("%d%c", &count);
for (idx = 0; idx < count; idx++) {
*(long*)(name+idx*8) = value;
}
printf ("| addr\t\t| value\t\t|\n");
for (idx = 0; idx < 0x10; idx++) {
printf ("| %lx\t| %16lx\t|\n", name + idx *8, *(long*)(name + idx*8));
}
return 0;
}
어떤식으로 bof를 발생시켜야하는지 모르겠어요 ㅠㅠ
먼저 -> https://dreamhack.io/lecture/courses/440, https://dreamhack.io/lecture/courses/43 : 이 강의 링크에서 컴퓨터 구조(Computer Architecture)를 배워야 합니다. 시스템 해킹 로드맵(https://dreamhack.io/lecture/roadmaps/2)을 따라가면 좋습니다
char name[16];
은 크기가 16바이트인 배열입니다. 이 배열은 지역변수로써 스택에 위치합니다.
printf ("the main function doesn't call win function (0x%lx)!\n", win);
에서 win
함수의 주소값을 알려줍니다.
for (idx = 0; idx < 0x10; idx++) {
printf ("| %lx\t| %16lx\t|\n", name + idx *8, *(long*)(name + idx*8));
}
위 코드를 보시면 포인터 name+idx*8
을 타입 long *
으로 강제 변환시키고 참조해서 8바이트 정수를 보여주고 있습니다. idx가 0~15로 16개의 숫자를 가지니까 총 8*16=128
바이트를 보여주고 있는 것입니다. 이것은 16바이트 짜리 name을 넘어서는 부분을 다 보여주고 있습니다.
nc로 접속해서 실행시켜 보겠습니다.
user@user-VirtualBox:~/Desktop/DH$ nc host3.dreamhack.games 12272
the main function doesn't call win function (0x40125b)!
name: AAAAAAAABBBB
GM GA GE GV AAAAAAAABBBB!!:
| addr | value |
| 7ffdd9905710 | 4141414141414141 |
| 7ffdd9905718 | 42424242 |
| 7ffdd9905720 | 1 |
| 7ffdd9905728 | 7fe3c1be9d90 |
| 7ffdd9905730 | 0 |
| 7ffdd9905738 | 401325 |
| 7ffdd9905740 | 1d9905820 |
| 7ffdd9905748 | 7ffdd9905838 |
| 7ffdd9905750 | 0 |
| 7ffdd9905758 | 61e23cd513e063c7 |
| 7ffdd9905760 | 7ffdd9905838 |
| 7ffdd9905768 | 401325 |
| 7ffdd9905770 | 403e18 |
| 7ffdd9905778 | 7fe3c1e26040 |
| 7ffdd9905780 | 9e198ff5bd8263c7 |
| 7ffdd9905788 | 9e25bfa8296a63c7 |
보시면 A를 8개, B를 4개 써서 AAAAAAAABBBB
를 입력하니까
첫 줄에 7ffdd9905710 | 4141414141414141
가 뜨는데 이것은 A
의 아스키코드 65의 16진수(헥스)값 0x41이 8개가 잘 입력된 모습입니다.
둘째 줄에 7ffdd9905718 | 42424242
는 이어서 B
의 아스키코드 0x42가 4개 있는 모습입니다. 여기까지가 name 배열입니다.
이후에는 여러가지 스택에 있는 값들이 보이는데 특히 4번째에 | 7ffdd9905728 | 7fe3c1be9d90 |
는 메인함수의 리턴 주소가 보이고 있습니다.
인텔이나 암드같은 CPU의 아키텍처에서는 함수(서브루틴)을 호출(CALL)하면 호출후 원래 실행 흐름으로 다시 돌아가기 위해서 서브 루틴으로 점프하기 직전 읽고 있는 명령어의 주소값(명령어 포인터, 프로그램 카운터)을 스택에 넣고 나중에 반환(RET)할 때 스택의 꼭대기(top)에 저장된 주소로 돌아갑니다. 그러므로 저 4번째 값을 수정하면 메인함수가 종료될 때 실행 흐름을 원하는 곳으로 이동시킬 수 있게 됩니다.
따라서 저 리턴주소(401325
)을 win함수의 주소 0x40125b
로 바꾼다면 메인함수가 종료될 때 저 함수로 실행 흐름이 이동할 것입니다.
for (idx = 0; idx < count; idx++) {
*(long*)(name+idx*8) = value;
}
여기서는 입력받은 count개 만큼 name 이후의 값들을 8바이트(long)씩 value로 수정하고 있습니다. count가 2를 넘으면 name의 16바이트를 넘어서 값이 수정되는 버퍼 오버플로우(bof)가 발생하는 지점입니다.
참고; https://youtu.be/moVR0t8TQnM
(오후 6:34 2023-11-03) 12번째 값이라고 써있는 것을 4번째로 올바르게 고쳤습니다.