#dokydoky

[Pwnerable.tw] Ghostparty(450pts) - reversing C++ string, vector 본문

System Hacking

[Pwnerable.tw] Ghostparty(450pts) - reversing C++ string, vector

dokydoky 2017. 5. 24. 13:28

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에 할당됩니다.

String Structure
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

 

 

case 1: string constructor, copy constructor, destructor
[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.

 

 

case 2: string initialization(Assignment operators)
[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);

 

 

case 3: trace malloc - getline
[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 10x0000555555559caf 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 반복, 실제 할당 크기보다 더 크게 할당됨.

 

 

case 4: trace malloc - copy constructor, Assignment operator
[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  |      
                    

Vector Structure
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

 

 

case 1: vector.push_back
[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



case 2: vector.erase
[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

 

 

case 3: vector::iterator
[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

 



Comments