[Pwnerable.tw] Ghostparty(450pts) - reversing C++ string, vector
0. Intro
ghostparty binary기준으로 C++ string, vector 분석 내용을 정리합니다.
c++ reversing에 익숙하지 않으신 분은 Reversing C++ programs with IDA pro and Hex-rays 을 먼저 읽어보시는걸 추천합니다.
reversing하면서 정의한 구조체는 맨 아래에 적어두겠습니다.
c++코드와 IDA에서 decompile된 코드를 비교하면서 정리하겠습니다. (테스트 환경은 64bit Ubuntu 16.04)
1. string
string은 dynamic char 배열입니다. 표현되는 방식이 여러개 있지만, 여기서는 ghostparty 바이너리에서 사용된 방법을 기준으로 설명합니다.
string의 member는 아래와 같습니다.
주의해야 할 것은 size + 1(null byte)이 DWORD size*2(여기서는 16)이하일 경우는 char 배열이 String.str에 저장되고,
16을 넘을 경우는 char 배열이 heap에 할당됩니다.
00000000 String struc ; (sizeof= 0x20 , mappedto_12) 00000000 pChar dq ? // char 배열의 주소(size+1가 16 이하면 String.str을, 16 초과면 heap 주소를 가리킴) 00000008 size dq ? // char 배열의 크기 00000010 str db 16 dup(?) // char 배열 00000020 String ends |
[C++ Code] class Alan : public Ghost { public : ... void addlightsaber(string str){ lightsaber = ( char *)str.c_str(); } ... private : char *lightsaber ; }; case 10: { Ghost *ghost = new Alan() string lightsaber ; ... getline(cin,lightsaber); ghost->addlightsaber(lightsaber); } [Hexray] ... // string constructor // string은 초기화하지 않아도 내부적으로 초기화함을 알 수 있다. lightsaber.pChar = ( __int64 )lightsaber.str; lightsaber.size = 0LL; lightsaber.str[0] = 0; // getline std::getline< char ,std::char_traits< char >,std::allocator< char >>( ( __int64 )std::cin, ( __int64 )&lightsaber, (unsigned int )v105); // Copy constructor in 'addlightsaber' function(Call-by-value) lightsaber_param.pChar = ( __int64 )lightsaber_param.str; std::__cxx11::basic_string< char ,std::char_traits< char >,std::allocator< char >>::_M_construct< char *>( &lightsaber_param, ( char *)lightsaber.pChar, ( char *)(lightsaber.size + lightsaber.pChar)); pchar_ = (String *)lightsaber_param.pChar; insAlan->Alan_members.pchr_lightsaber = lightsaber_param.pChar; // Destructor when escaping from 'addlightsaber' function scope. if ( pchar_ != (String *)lightsaber_param.str ) operator delete (pchar_); .. // Destructor when escaping from the 'case 5:' scope. if ( ( char *)lightsaber.pChar != lightsaber.str ) operator delete (( void *)lightsaber.pChar); => "string문자열의 길이+1(null) <= DWORD Size*2" 일 경우 stack에 할당 > DWORD size*2일 경우 heap에 할당 후 pChar가 가리킴. 그래서 String 소멸자에서 lightsaber.pChar != lightsaber.str를 확인 후 delete . |
[C++] class Ghost{ ... protected : int age ; char *name ; string type ; string msg ; }; class Alan : public Ghost { public : ... Alan( int ghostage,string ghostname,string ghostmsg){ type = "Alan" ; ... msg = ghostmsg ; }; ... private : char *lightsaber ; }; [Hexray] //(Alan의 생성자부분) std::__cxx11::basic_string< char ,std::char_traits< char >,std::allocator< char >>::_M_replace( &insAlan->Alan_members.Ghost_members.str_type, 0LL, insAlan->Alan_members.Ghost_members.str_type.size, "Alan" , 4LL); ... std::__cxx11::basic_string< char ,std::char_traits< char >,std::allocator< char >>::_M_assign( (__int64)&insAlan->Alan_members.Ghost_members.str_msg, (__int64)&msg_param_3); |
[C++] string lightsabar; ... getline(cin, lightsabar); [C++] std::getline< char ,std::char_traits< char >,std::allocator< char >>( (__int64)std::cin, (__int64)&lightsaber, (unsigned int )v105); // "a"*0x100 입력 후 trace malloc Breakpoint 1 , 0x0000555555559caf in ?? () gdb-peda$ c Continuing. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa malloc( 0x1f ) = 0x5555557780c0 malloc( 0x3d ) = 0x5555557780f0 free( 0x5555557780c0 ) (size = 0x30 ) malloc( 0x79 ) = 0x555555778140 free( 0x5555557780f0 ) (size = 0x50 ) malloc( 0xf1 ) = 0x5555557781d0 free( 0x555555778140 ) (size = 0x90 ) malloc( 0x1e1 ) = 0x5555557782d0 << free( 0x5555557781d0 ) (size = 0x100 ) malloc( 0x101 ) = 0x555555778140 free( 0x555555778140 ) (size = 0x110 ) => getline은 증가하면서 malloc/free 반복, 실제 할당 크기보다 더 크게 할당됨. |
[C++] class Ghost{ ... private : string msg } class Alan : public Ghost { ... Alan( int ghostage,string ghostname,string ghostmsg){ ... msg = ghostmsg ; }; ... }; cout << "Message : " ; ... getline(cin,message); ... Alan *ghost = new Alan(age,name,message); [Hexray] std::__ostream_insert< char ,std::char_traits< char >>((__int64)&std::cout, (__int64) "Message : " , 10LL); ... std::getline< char ,std::char_traits< char >,std::allocator< char >>((__int64)std::cin, (__int64)&msg, (unsigned int )v1); ... std::__cxx11::basic_string< char ,std::char_traits< char >,std::allocator< char >>::_M_construct< char *>( &msg_param_3, ( char *)msg.pChar, ( char *)(msg.size + msg.pChar)); std::__cxx11::basic_string< char ,std::char_traits< char >,std::allocator< char >>::_M_assign( (__int64)&insAlan->Alan_members.Ghost_members.str_msg, (__int64)&msg_param_3); // "1"*0x14개 입력 Message : 11111111111111111111 malloc( 0x1f ) = 0x555555778030 // getline => 0x1f ... malloc( 0x15 ) = 0x555555778060 // _M_construct => 0x14 + 1 malloc( 0x60 ) = 0x555555778080 // new Alan .. malloc( 0x1f ) = 0x555555778110 // _M_assign => 0x1f free( 0x555555778060 ) (size = 0x20 ) => 복사생성자가 호출될 때는 "입력한 문자열길이 + 1(null)" 크기로 malloc 호출 => 대입연산자가 호출될 때는 getline에서 생성된 block size와 동일한 크기로 malloc 호출 |
2. Vector
Vector는 동적인 배열로, heap에 할당되고 _M_start, _M_finish, _M_end_of_storage 을
_M_start ------------------> | Heap chunk1 addr |
_M_finish --------------- | Heap chunk2 addr |
_M_end_of_storage ---|--> | Heap chunk3 addr |
00000000 vector struc ; (sizeof= 0x18 , mappedto_13) 00000000 _M_start dq ? //vector::begin() 00000008 _M_finish dq ? //vector::end() 00000010 _M_end_of_storage dq ? 00000018 Vector ends |
[C++] ghostlist.push_back(ghost); [Hexray] insVampire_param = insVampire; v10 = (_QWORD *)ghostlist._M_finish; if ( v10 == (_QWORD *)ghostlist._M_end_of_storage ) { sub_9C3C((__int64)&ghostlist, v10, &insVampire_param); //더 큰 크기의 vector를 할당 후 복사 } else { if ( v10 ) *v10 = insVampire; ghostlist._M_finish += 8LL; } => push_back()할 때 if (_M_finish == _M_end_of_storage) : 새로운 vector를 할당 후 복사 else : _M_finish의 위치에 삽입 후 _M_finish += 8 |
[C++] cin >> ghostindex ; if (ghostindex >= ghostlist.size()){ cout << "\033[31mInvaild index\033[0m" << endl ; return 0 ; } delete ghostlist[ghostindex]; ghostlist.erase(ghostlist.begin()+ghostindex); [Hexray] std::istream::_M_extract<unsigned int >(std::cin, &idx); if ( (unsigned __int64)idx < (ghostlist._M_finish - ghostlist._M_start) >> 3 ) { delNode = *(Ghost **)(ghostlist._M_start + 8LL * idx); if ( delNode ) (( void (__fastcall *)(Ghost *, unsigned int *))delNode->vtable->delete)(delNode, &idx); pdelNode = ( char *)(ghostlist._M_start + 8LL * idx); pfinishNode = ghostlist._M_finish; v10 = pdelNode + 8 ; if ( ( char *)pfinishNode != pdelNode + 8 ) { v11 = pfinishNode - (_QWORD)v10; if ( v11 >> 3 ) memmove(pdelNode, v10, v11); } ghostlist._M_finish -= 8LL; result = 1LL; } else { std::__ostream_insert< char ,std::char_traits< char >>( (__int64)&std::cout, (__int64) "\x1B[31mInvaild index\x1B[0m" , 22LL); ... } => erase시에는 지우려는 노드가 마지막 노드라면 _M_finish -= 8 을 하고, 마지막 노드가 아니라면 지우려는 노드 뒤의 값들을 지우려는 노드의 위치로부터 복사를 한 뒤, _M_finish -= 8 |
[C++] void listghost(){ vector<Ghost*>::iterator iter; int i = 0 ; for (iter = ghostlist.begin() ; iter != ghostlist.end() ; iter++,i++){ cout << i << ". " ; cout << (**iter).gettype() << " : " << (**iter).getname() << endl; } cout << endl ; } [Hexray] iter = ghostlist._M_start; if ( ghostlist._M_start != ghostlist._M_finish ) { i = 0 ; do { v5 = std::ostream::operator<<(&std::cout, i); std::__ostream_insert< char ,std::char_traits< char >>(v5, (__int64) ". " , 2LL); memAddr = *(_QWORD *)iter; pName = *( const char **)(*(_QWORD *)iter + 0x10LL); type.pChar = (__int64)type.str; std::__cxx11::basic_string< char ,std::char_traits< char >,std::allocator< char >>::_M_construct< char *>( &type, *( char **)(memAddr + 0x18 ), // str_type ( char *)(*(_QWORD *)(memAddr + 0x20 ) + *(_QWORD *)(memAddr + 0x18 ))); outStream = (std::ostream *)std::__ostream_insert< char ,std::char_traits< char >>( (__int64)&std::cout, type.pChar, type.size); std::__ostream_insert< char ,std::char_traits< char >>((__int64)outStream, (__int64) " : " , 3LL); if ( pName ) std::__ostream_insert< char ,std::char_traits< char >>((__int64)outStream, (__int64)pName, strlen(pName)); else std::basic_ios< char ,std::char_traits< char >>::clear( ( char *)outStream + *(_QWORD *)(*(_QWORD *)outStream - 0x18LL), *(_DWORD *)(( char *)outStream + *(_QWORD *)(*(_QWORD *)outStream - 0x18LL) + 32 ) | 1u); v9 = *(_BYTE **)(( char *)outStream + *(_QWORD *)(*(_QWORD *)outStream - 0x18LL) + 240 ); if ( !v9 ) std::__throw_bad_cast(); if ( v9[ 56 ] ) { v10 = v9[ 67 ]; } else { std::ctype< char >::_M_widen_init(v9); v10 = (*(__int64 (__fastcall **)(_BYTE *, signed __int64))(*(_QWORD *)v9 + 48LL))(v9, 10LL); } v11 = (std::ostream *)std::ostream::put(outStream, v10); std::ostream::flush(v11); if ( ( char *)type.pChar != type.str ) operator delete(( void *)type.pChar); iter += 8LL; ++i; } while ( ghostlist._M_finish != iter ); } => iterator는 간단하다. _M_start : vector:begin() _M_finish : vector:finish() |
3. custom structure definition
00000000 ; Ins/Del : create/delete structure 00000000 ; D/A/* : create structure member (data/ascii/array) 00000000 ; N : rename structure or structure member 00000000 ; U : delete structure member 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Ghost struc ; (sizeof= 0x58 , mappedto_6) ; XREF: night/r 00000000 vtable dq ? ; XREF: night+B5/w night+ 176 /w ; offset 00000008 Ghost_members Ghost_members ? ; XREF: night+ 35 /o night+3A/o ... 00000058 Ghost ends 00000058 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Ghost_members struc ; (sizeof= 0x50 , mappedto_7) ; XREF: Ghost/r 00000000 ; Vampire_members/r ... 00000000 i_age dq ? ; XREF: night+11C/w night+17A/w ... 00000008 pchr_name dq ? ; XREF: night+FC/w night+1A4/r ... 00000010 str_type String ? ; XREF: night+ 35 /o night+3F/o ... 00000030 str_msg String ? ; XREF: night+3A/o night+CC/w ... 00000050 Ghost_members ends 00000050 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Ghost_vtable struc ; (sizeof= 0x28 , mappedto_8) ; XREF: Vampire_vtable/r 00000000 ; Werewolf_vtable/r ... 00000000 speak dq ? 00000008 changemsg dq ? 00000010 ghostinfo dq ? 00000018 dtor dq ? 00000020 delete dq ? 00000028 Ghost_vtable ends 00000028 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Vampire struc ; (sizeof= 0x60 , mappedto_9) ; XREF: smalllist_Vampire/r 00000000 ; smalllist_Vampire/r ... 00000000 vtable dq ? ; XREF: smalllist_Vampire+ 363 /w 00000000 ; smalllist_Vampire+ 429 /w ... ; offset 00000008 Vampire_members Vampire_members ? ; XREF: smalllist_Vampire+ 368 /o 00000008 ; smalllist_Vampire+36D/w ... 00000060 Vampire ends 00000060 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Vampire_members struc ; (sizeof= 0x58 , mappedto_10) ; XREF: Vampire/r 00000000 Ghost_members Ghost_members ? ; XREF: smalllist_Vampire+ 368 /o 00000000 ; smalllist_Vampire+36D/w ... 00000050 pchr_blood dq ? ; XREF: smalllist_Vampire+ 432 /w 00000050 ; smalllist_Vampire+ 451 /r 00000058 Vampire_members ends 00000058 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Vampire_vtable struc ; (sizeof= 0x28 , mappedto_11) 00000000 base_vtable Ghost_vtable ? 00000028 Vampire_vtable ends 00000028 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Werewolf struc ; (sizeof= 0x60 , mappedto_14) 00000000 vtable dq ? ; offset 00000008 Werewolf_members Werewolf_members ? 00000060 Werewolf ends 00000060 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Werewolf_members struc ; (sizeof= 0x58 , mappedto_15) ; XREF: Werewolf/r 00000000 Ghost_members Ghost_members ? 00000050 i_trans dq ? 00000058 Werewolf_members ends 00000058 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Werewolf_vtable struc ; (sizeof= 0x28 , mappedto_16) 00000000 base_vtable Ghost_vtable ? 00000028 Werewolf_vtable ends 00000028 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Mummy struc ; (sizeof= 0x78 , mappedto_24) 00000000 vtable dq ? ; offset 00000008 Mummy_members Mummy_members ? 00000078 Mummy ends 00000078 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Mummy_members struc ; (sizeof= 0x70 , mappedto_25) ; XREF: Mummy/r 00000000 Ghost_members Ghost_members ? 00000050 str_bandage String ? 00000070 Mummy_members ends 00000070 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Mummy_vtable struc ; (sizeof= 0x28 , mappedto_26) 00000000 base_vtable Ghost_vtable ? 00000028 Mummy_vtable ends 00000028 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Kasa struc ; (sizeof= 0x80 , mappedto_17) 00000000 vtable dq ? ; offset 00000008 Kasa_members Kasa_members ? 00000080 Kasa ends 00000080 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Kasa_members struc ; (sizeof= 0x78 , mappedto_18) ; XREF: Kasa/r 00000000 Ghost_members Ghost_members ? 00000050 i_foot dq ? 00000058 str_eyes String ? 00000078 Kasa_members ends 00000078 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Kasa_vtable struc ; (sizeof= 0x28 , mappedto_19) 00000000 base_vtable Ghost_vtable ? 00000028 Kasa_vtable ends 00000028 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Alan struc ; (sizeof= 0x60 , mappedto_23) 00000000 vtable dq ? 00000008 Alan_members Alan_members ? 00000060 Alan ends 00000060 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Alan_members struc ; (sizeof= 0x58 , mappedto_21) ; XREF: Alan/r 00000000 Ghost_members Ghost_members ? 00000050 pchr_lightsaber dq ? 00000058 Alan_members ends 00000058 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Alan_vtable struc ; (sizeof= 0x28 , mappedto_22) 00000000 base_vtable Ghost_vtable ? 00000028 Alan_vtable ends 00000028 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 String struc ; (sizeof= 0x20 , mappedto_12) ; XREF: listghost/r 00000000 ; addghost/r ... 00000000 pChar dq ? ; XREF: night+B9/w night+CC/w ... 00000008 size dq ? ; XREF: night+BE/w night+D1/w ... 00000010 str db 16 dup(?) ; XREF: night+ 35 /o night+3A/o ... 00000020 String ends 00000020 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 Vector struc ; (sizeof= 0x18 , mappedto_13) 00000000 _M_start dq ? 00000008 _M_finish dq ? 00000010 _M_end_of_storage dq ? 00000018 Vector ends 00000018 |