1. Register Mandatory Class 2. Register Elective Class 3. See Class Detail 4. Write Memo 5. Exit >
처음에 name, id, major를 입력하고 시간표로 보이는 것을 출력한 후, exit을 제외하고 4가지의 기능을 실행할 수 있도록 되어있다. 먼저 register와 관련된 기능을 살펴봤을 때 mandatory_subject와 elective_subject 구조체가 보였다. 그리고 4번 메뉴가 수상해 보여서 해당 함수도 함께 봤다.
voidregister_elective_class() { int i; elective_subject choice; print_table(timetable); printf("-----Elective Class List-----\n"); print_elective_list(); printf(">"); scanf("%d", &i); choice = elective_list[i]; if (choice.IsAvailable(&user) == 1) { timetable[choice.time[0]][choice.time[1]].name = choice.name; // The type of timetable is 0 by default since it is a global value. timetable[choice.time[0]][choice.time[1]].detail = &elective_list[i]; } else { printf("You can't register this class\n"); } }
register_elective_class 함수에서 choice.IsAvailable(&user)를 실행하는 부분이 있다. user는 프로그램 맨 처음 나의 입력값이다. elective_subject 구조체 안에 함수포인터가 있음을 확인했다. (만약 이 함수포인터를 덮을 수 있다면, system 함수로 덮고, 인자로 들어간 나의 입력값 user는 /bin/sh를 넣어야겠다.)
1 2 3 4 5 6 7 8 9 10
voidwrite_memo() { comma *choice = choose_time(timetable); printf("WRITE MEMO FOR THE CLASS\n");
choice->type을 기준으로 memo에 30글자 입력한다. 여기서 만약 Type confusion이 발생한다면 본래는 mandatory_subject인데 elective_subject으로 착각해서 elective_subject 구조체에 입력을 하게 될 것이다. 구조체 offset을 살펴보고 유의미한 위치에 쓰기를 할 수 있는지 생각해보자.
주석으로 offset을 적어놨다. mandatory_subject 구조체의 memo는 48번째에 위치한다. elective_subject 구조체의 48번째는 *proffesor이다. write_memo 함수에서 30바이트를 쓸 수 있었으니 뒤의 함수포인터를 덮을 수 있다.
elective_subject 구조체를 mandatory_subject 구조체로 헷갈리고 memo를 작성하게 된다면 elective_subject 구조체의 함수 포인터를 덮어 register_elective_class 함수에서 함수 포인터를 실행시켜 흐름을 변경할 수 있을 것이다.
그 전에 libc leak을 해야하는데 leak도 Type confusion을 이용해서 할 수 있다. 반대로 Type confusion을 일으키면 된다. 구조를 print하는 기능이 있어서 leak이 가능하다.
mandatory_subject 구조체를 elective_subject 구조체로 헷갈리고 memo를 작성하게 된다면 mandatory_subject 구조체에서 *target[4]을 덮게 된다. 여기에 GOT가 저장된 주소를 적고 구조를 print해주는 print_mandatory_subject 함수에서 GOT가 출력될 것이다.
하지만 난 이 방법은 사용하지 않고 함수 포인터를 덮는 과정에서 _IO_2_1_stdout_전까지 입력값을 주어 print_elective_subject 함수에서 memo를 출력할 때 GOT가 같이 출력되도록 했기 때문에 보다 편하게 libc address를 구할 수 있었다.
마지막으로 사실 register_elective_class 함수를 실행시키기 위해서는 조건이 하나 필요하다. 바로 맨 처음에 입력하는 name, id, major에서 id와 major가 특정 값 이상이거나 이하여야 한다는 조건이 있다. 이 조건을 가장 먼저 맞춰주어야 한다.
Solve
name에는 /bin/sh, id에는 2000, major에는 100을 입력
libc leak : write_memo를 이용하여 stdout 전까지 입력 후, print_elective_subject 함수에서 구조 출력하여 _IO_2_1_stdout_ 구함
함수 포인터 덮기 : write_memo를 이용하여 함수 포인터 offset 자리에 system 함수 주소 넣기
# elective_subject를 mandatory_subject로 헷갈리게 해서 # memo[32]를 적을 때 함수 포인터를 넣어서 # elective_subject.student 함수 포인터를 수정할 수 있도록 하기 p.sendlineafter('name :', '/bin/sh') p.sendlineafter('id :', '2000') p.sendlineafter('major :', '100')