1
2
3
4
5
6
7
8
9
10
11
创建: 2024-12-01 19:55
 
目录:
 
    ☆ 背景介绍
    ☆ dumpclass.cpp
    ☆ dumptarget.cpp
    ☆ 用dumpclass.so处理dumptarget.cpp
    ☆ pahole
    ☆ clang -Xclang -fdump-record-layouts
    ☆ VC有隐藏选项

☆ 背景介绍

有次因故需要了解std::string类型内存布局,简单折腾一番,分享了一篇

1
2
《GDB查看结构或类的内存布局及分离终端》
https://scz.617.cn/unix/202411151604.txt

bluerust随即让我看下面这篇

1
2
STL容器逆向与实战 - [2023-02-07]
https://bbs.kanxue.com/thread-275133.htm

他的原话是,主要看"llvm pass dump data type"。看了这篇,于我而言,属于"每个字都认识"系列,大概明白其基本原理是啥,但完全不了解所涉及的"LLVM Pass"技术,看过之后,老虎吃天、无处下爪。我不会C++编程,基本未碰上过C++ STL容器逆向需求,不在意上文中那些具体容器的实现细节。我感兴趣的是,如何转储类或结构的内存布局,也就是上文第一部分的内容。原作者有句话,随便简单写个pass来dump,深深刺激了我,别人随便简单弄的东西,代码都给了,我还是不知如何实践。或许有些同道遭遇类似囧境,本文面向"LLVM Pass"小白提供完整可操作示例,聚焦"转储内存布局",是上文降阶后的狗尾续貂、画蛇添足。

☆ dumpclass.cpp

参看

1
2
3
4
5
Writing an LLVM Pass (legacy PM version)
https://llvm.org/docs/WritingAnLLVMPass.html
 
Writing an LLVM Pass
https://llvm.org/docs/WritingAnLLVMNewPMPass.html

看雪那篇是Legacy格式的"LLVM Pass",此处dumpclass.cpp改写成New格式。支持两个命令行参数,允许成员名中包含相对偏移或绝对偏移,允许过滤类或结构名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"
 
#define DEFAULTSUBSTR   "<default>"
 
using namespace llvm;
 
namespace {
 
static cl::opt<int> passmode
(
"passmode",
cl::desc("absolute offset or not"),
cl::value_desc("int"),
cl::init(0)
);
 
static cl::opt<std::string> substr
(
"substr",
cl::desc("part of struct name"),
cl::value_desc("std::string"),
cl::init(DEFAULTSUBSTR)
);
 
struct DumpClass : PassInfoMixin<DumpClass>
{
 
    std::string getTypeName ( Type *type, const DataLayout &data )
    {
        if ( type->isIntegerTy() )
        {
            IntegerType    *i   = cast<IntegerType>( type );
 
            return "uint" + std::to_string( i->getBitWidth() ) + "_t";
        }
        else if ( type->isPointerTy() )
        {
            PointerType    *ptr = cast<PointerType>( type );
 
            return getTypeName( ptr->getPointerElementType(), data ) + "*";
        }
        else if ( type->isArrayTy() )
        {
            ArrayType      *arr = cast<ArrayType>( type );
 
            return getTypeName( arr->getArrayElementType(), data ) + "[" + std::to_string( arr->getArrayNumElements() ) + "]";
        }
        else if ( type->isFloatTy() )
        {
            return "float";
        }
        else if ( type->isStructTy() )
        {
            StructType     *stc = cast<StructType>( type );
 
            return std::string( stc->getStructName() );
        }
        else
        {
            return "unknown_" + std::to_string( data.getTypeAllocSizeInBits( type ) );
        }
    }
 
    void dumpType ( int depth, Type *type, const std::string &suffix, const DataLayout *data, unsigned base, int mode )
    {
        std::string blank( depth * 4, ' ' );
 
        if ( type->isStructTy() )
        {
            StructType         *stc = cast<StructType>( type );
            const StructLayout *sl  = data->getStructLayout( stc );
 
            errs() << blank + stc->getStructName() + "\n" + blank + "{\n";
            for ( size_t i = 0; i < stc->getStructNumElements(); i++ )
            {
                Type       *subType = stc->getStructElementType( i );
                unsigned    offset  = sl->getElementOffset( i );
                unsigned    size    = data->getTypeAllocSize( subType );
 
                if ( mode > 0 )
                {
                    offset += base;
                    dumpType( depth+1, subType, std::to_string(offset)+"_"+std::to_string(size), data, offset, mode );
                }
                else
                {
                    dumpType( depth+1, subType, std::to_string(offset)+"_"+std::to_string(size), data, 0, mode );
                }
            }
            errs() << blank + "} field_" + suffix + ";\n";
        }
        else
        {
            errs() << blank + getTypeName( type, *data ) + " field_" + suffix + ";\n";
        }
    }
 
    void visitor ( Function &F )
    {
        if ( F.getName() != "main" )
        {
            return;
        }
 
        std::set<StructType*>   types;
        const DataLayout       &data    = F.getParent()->getDataLayout();
 
        for ( auto &B : F )
        {
            for ( auto &I : B )
            {
                if ( auto *A = dyn_cast<AllocaInst>( &I ) )
                {
                    Type   *type    = A->getAllocatedType();
                    if ( type->isStructTy() )
                    {
                        StructType *stc = cast<StructType>( type );
 
                        if ( stc->isOpaque() )
                        {
                            continue;
                        }
                        std::string struct_name
                                        = std::string( stc->getStructName() );
                        if ( substr != DEFAULTSUBSTR && struct_name.find( substr ) == std::string::npos )
                        {
                            continue;
                        }
                        types.insert( stc );
                    }
                }
            }
        }
 
        int                     index = 0;
 
        for ( StructType *type : types )
        {
            dumpType( 0, type, std::to_string( index++ ), &data, 0, passmode );
        }
    }
 
    PreservedAnalyses run ( Function &F, FunctionAnalysisManager &FAM )
    {
        visitor( F );
        return PreservedAnalyses::all();
    }
 
};
 
}
 
PassPluginLibraryInfo getDumpClassPluginInfo ()
{
    const auto  callback = []( PassBuilder &PB )
    {
        PB.registerPipelineParsingCallback
        (
            [](
                StringRef               Name,
                FunctionPassManager    &FPM,
                ArrayRef<PassBuilder::PipelineElement>
            )
            {
                if ( Name == "DumpClass" )
                {
                    FPM.addPass( DumpClass() );
                    return true;
                }
                return false;
            }
        );
        PB.registerPipelineStartEPCallback
        (
            [&]( ModulePassManager &MPM, auto )
            {
                FunctionPassManager FPM;
 
                FPM.addPass( DumpClass() );
                MPM.addPass( createModuleToFunctionPassAdaptor( std::move( FPM ) ) );
                return true;
            }
        );
    };
 
    return { LLVM_PLUGIN_API_VERSION, "DumpClass", LLVM_VERSION_STRING, callback };
}
 
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo ()
{
    return getDumpClassPluginInfo();
}

从dumpclass.cpp生成dumpclass.so

1
2
3
4
5
6
7
clang-14 \
-I"/usr/include/llvm-14" \
-I"/usr/include/llvm-c-14" \
-Wall -pipe \
-fPIC -shared -Wl,-soname,dumpclass.so \
-O3 -s \
-o dumpclass.so dumpclass.cpp

后面会演示如何将dumpclass.so用作"LLVM Pass"来转储类或结构的内存布局。

☆ dumptarget.cpp

dumptarget.cpp是假想的目标程序,将来根据dumptarget.cpp转储其中的类或结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <deque>
#include <map>
#include <unordered_map>
#include <string>
#include <iostream>
 
class TargetClass
{
private:
    std::string                                                     unused;
public:
    std::deque<std::map<int, std::unordered_map<std::string, int>>> myDeque;
    std::map<int, std::unordered_map<std::string, int>>             myMap;
};
 
int main ( int argc, char * argv[] )
{
    TargetClass obj;
 
    obj.myMap[1]["one"] = 1;
    obj.myMap[2]["two"] = 2;
 
    obj.myDeque.push_back( obj.myMap );
 
    for ( const auto &d : obj.myDeque )
    {
        for ( const auto &pair : d )
        {
            std::cout << "Key : " << pair.first << " -> Value : ";
            for ( const auto &innerpair : pair.second )
            {
                std::cout << innerpair.first << " -> " << innerpair.second;
            }
            std::cout << std::endl;
        }
    }
 
    return 0;
}

☆ 用dumpclass.so处理dumptarget.cpp

有多种办法加载dumpclass.so,此处演示其中之一,依次执行这两条命令

1
2
3
4
clang-14 \
-Wall -pipe -S -emit-llvm \
-Xclang -disable-O0-optnone \
-o dumptarget.ll dumptarget.cpp

1
2
3
4
5
opt-14 \
-disable-output \
-load ./dumpclass.so -load-pass-plugin ./dumpclass.so \
-passes=DumpClass -passmode=1 -substr="::basic_string" \
dumptarget.ll 2>&1 | less

先从dumptarget.cpp生成dumptarget.ll,再用dumpclass.so处理dumptarget.ll。正常情况下会得到

1
2
3
4
5
6
7
8
9
10
11
12
13
class.std::__cxx11::basic_string
{
    struct.std::__cxx11::basic_string<char>::_Alloc_hider
    {
        uint8_t* field_0_8;
    } field_0_8;
    uint64_t field_8_8;
    union.anon
    {
        uint64_t field_16_8;
        uint8_t[8] field_24_8;
    } field_16_16;
} field_0;

尝试不给opt指定passmode、substr参数,观察输出,加强理解。

☆ pahole

pahole也能转储类或结构的内存布局,不如dumpclass.cpp,出于完备性写在此处。

1
g++ -Wall -pipe -std=c++11 -O0 -g -o dumptarget_dbg dumptarget.cpp

1
pahole --hex -E -M -C TargetClass dumptarget_dbg | grep -A 25 "class basic_string"

正常情况下会得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* typedef string */ class basic_string<char, std::char_traits<char>, std::allocator<char> > {
        struct _Alloc_hider : allocator<char> {
                /* class allocator<char> : public new_allocator<char> {
                public:
 
                        /* class new_allocator<char> {
                        public:
 
                        }<ancestor>; */ /*     0     0 */
 
                        /* XXX last struct has 1 byte of padding */
                }<ancestor>; */ /*     0   0x1 */
 
                /* XXX last struct has 1 byte of padding */
                /* XXX 65535 bytes hole, try to pack */
 
                /* typedef pointer -> pointer -> pointer */ char *     _M_p;     /*     0   0x8 */
        }_M_dataplus; /*     0   0x8 */
        /* typedef size_type -> size_type -> size_type -> size_t */ long unsigned int  _M_string_length; /*   0x8   0x8 */
        union {
                char       _M_local_buf[16];                                     /*  0x10  0x10 */
                /* typedef size_type -> size_type -> size_type -> size_t */ long unsigned int _M_allocated_capacity; /*  0x10   0x8 */
        };                                                                       /*  0x10  0x10 */
public:
 
} unused; /*     0  0x20 */

☆ clang -Xclang -fdump-record-layouts

1
clang -Xclang -fdump-record-layouts dumptarget.cpp 2> /dev/null | grep -A 10 "0 | class std::basic_string" | less

正常情况下会得到

1
2
3
4
5
6
7
8
9
10
11
12
0 | class std::basic_string<char>
 0 |   struct std::basic_string<char>::_Alloc_hider _M_dataplus
 0 |     class std::allocator<char> (base) (empty)
 0 |       class __gnu_cxx::new_allocator<char> (base) (empty)
 0 |     std::basic_string<char>::pointer _M_p
 8 |   std::basic_string<char>::size_type _M_string_length
16 |   union std::basic_string<char>::(anonymous at /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/basic_string.h:179:7)
16 |     char[16] _M_local_buf
16 |     std::basic_string<char>::size_type _M_allocated_capacity
   | [sizeof=32, dsize=32, align=8,
   |  nvsize=32, nvalign=8]
...

☆ VC有隐藏选项

假设VirtualBaseClass.cpp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <windows.h>
 
class Base
{
public:
    int x;
};
 
class Derived1 : virtual public Base
{
public:
    int y;
};
 
class Derived2 : virtual public Base
{
public:
    int z;
};
 
class Multiple : public Derived1, public Derived2
{
public:
    int w;
};
 
int __cdecl main ( int argc, char * argv[] )
{
    Multiple    m;
 
    m.x = 10;
    return 0;
}

VC编译时有隐藏选项,查看C++类的内存布局

1
2
3
4
5
6
cl /d1reportSingleClassLayoutBase VirtualBaseClass.cpp
cl /d1reportSingleClassLayoutDerived1 VirtualBaseClass.cpp
cl /d1reportSingleClassLayoutDerived2 VirtualBaseClass.cpp
cl /d1reportSingleClassLayoutDerived VirtualBaseClass.cpp (子串匹配)
cl /d1reportSingleClassLayoutMultiple VirtualBaseClass.cpp
cl /d1reportAllClassLayout VirtualBaseClass.cpp (输出太多,慎用)

用ASCII图显示内存布局,向stdout输出,不影响其他编译选项。

最新回复:
IamHuskar 2024-12-02 09:22:37

现在大量都是c/c++的尤其是std 容器以及template,包括Boost的template。rolf做了一个ida还原,针对特定版本的std template识别,但是他想商业化这个插件。没找到合适的买家。


再说了大佬。c++的核心特性 和c结构体+函数指针差别也不大啊

mb_ldbucrik 2024-12-02 13:04:32
x86的结构体还原插件还挺多的,arm的没有
IamHuskar 2024-12-02 19:56:30
mb_ldbucrik x86的结构体还原插件还挺多的,arm的没有
是吗?除了hexrayscodexplorer和HexRaysPyTools 还有牛逼的推荐吗?我没见过了