c++-STL容器逆向学习

查看 76|回复 9
作者:lyfff   
c++-STL容器逆向学习
前言
在比赛中遇到了利用stl容器嵌套的ctf题目,能力有限做不出来,只好赛后跟着大佬的wp复现,顺便学习相关知识
string
主要结构是由一个指针ptr和字符串长度string_length构成。当字符串长度小于0x10时,会在栈上分配一个长度为0x10的缓冲区用于存放字符串,且此时指针指向该缓冲区;当字符串长度大于等于0x10时,会将字符串存放到堆空间中
测试代码
#include
using namespace std;
int main(int argc, const char * argv[]) {
    // 15
    string s1 = "aaaaaaaaaaaaaaa";
    // 16
    string s2 = "bbbbbbbbbbbbbbbb";
    // 17
    string s3 = "ccccccccccccccccc";
                // 5
                string s4 = "ddddd";
    return 0;
}
内存布局,可以发现字符串长度小于0x10时是储存在栈上的,且始终会分配16个字节给字符串,大于等于0x10时会将字符串存在堆上。


Untitled.png (46.3 KB, 下载次数: 0)
下载附件
2022-11-30 13:57 上传

deque
deque主要由三部分构成。第一部分是描述map的指针及map大小,第二部分是指向开头的迭代器,第三部分是指向结尾的迭代器。
首先,map用于存放指针,指向真正存放数据的空间的首地址,实现整体连续的效果。如果需要增加储存空间,便会申请一段新的内存,同时在map数组的开头或者结尾添加对应的指针。
此外,因为数据实际上存放在了多段不连续的空间,所以迭代器必须满足以下条件:
[ol]
  • 迭代器在遍历 deque 容器时,必须能够确认各个连续空间在 map 数组中的位置;
  • 迭代器在遍历某个具体的连续空间时,必须能够判断自己是否已经处于空间的边缘位置。如果是,则一旦前进或者后退,就需要跳跃到上一个或者下一个连续空间中。
    [/ol]
    所以,迭代器包含了4个指针,用于实现上述功能
  • cur:指向当前正在遍历的元素;
  • first:指向当前连续空间的首地址;
  • last:指向当前连续空间的末尾地址;
  • node:它是一个二级指针,用于指向 map 数组中存储的指向当前连续空间的指针。



    Untitled 1.png (41.86 KB, 下载次数: 0)
    下载附件
    2022-11-30 13:57 上传

    测试代码
    #include
    #include
    using namespace std;
    int main(int argc, const char * argv[]) {
        //创建10个值为5的deque
        std::deque d0(10,5);
        std::deque d1(10,"aaaaaa");
        // cout


    Untitled 2.png (68.23 KB, 下载次数: 0)
    下载附件
    2022-11-30 13:57 上传

    rb_tree
    一般节点由颜色,parent,left,right和所存放的数据构成,其中color红色为0,黑色为1。header的末尾会有node_count来计算当前树的节点个数。


    Untitled 3.png (34.65 KB, 下载次数: 0)
    下载附件
    2022-11-30 13:57 上传

    STL中的set,map都是由红黑树实现,下面以map为例进行测试
    #include
    #include
    #include
    using namespace std;
    int main()
    {
            map mapTemp;
            mapTemp.insert({ 5,"55555" });
            mapTemp.insert({ 3, "333333"});
            mapTemp.insert({ 4, "44444" });
            map::iterator it;
            for (it = mapTemp.begin(); it != mapTemp.end(); it++)
            {
                    printf("%d,%s\n", (*it).first, (*it).second.c_str());
            }
            return 0;
    }
    刚开始,树没有节点,故header的parent为0,left和right都为他本身


    Untitled 4.png (18.87 KB, 下载次数: 0)
    下载附件
    2022-11-30 14:01 上传

    插入第一个节点后(即5,“55555”),parent指向根节点,left和right分别指向最左和最右的节点(此时仍为根节点)


    Untitled 5.png (25.33 KB, 下载次数: 0)
    下载附件
    2022-11-30 13:58 上传

    根节点如下,且left和right都为0,数据(5,”55555”)直接存放在最后


    Untitled 6.png (27.8 KB, 下载次数: 0)
    下载附件
    2022-11-30 13:58 上传

    再插入3,“333333”后,发现header和根节点的left都指向新的节点


    Untitled 7.png (29.49 KB, 下载次数: 0)
    下载附件
    2022-11-30 14:01 上传



    Untitled 8.png (22.2 KB, 下载次数: 0)
    下载附件
    2022-11-30 13:58 上传

    插入(4,“4444”)后发现根节点变为了(4,“4444”),再此不做详细叙述
    hashtable
    哈希表是unordered_map和unordered_set的底层实现。会申请一段空间用来储存链表的头指针,储存数据的链表称之为桶。每个桶都有对应的编号,一般最开始bucket_count=13,之后如果负载因子过大,会增加桶的数量,重新进行哈希。


    Untitled 9.png (39.21 KB, 下载次数: 0)
    下载附件
    2022-11-30 14:01 上传

    测试代码如下
    #include  
    #include  
    #include
    using namespace std;  
    int main()
    {
            unordered_map  dict; // 声明unordered_map对象
            dict.insert({ 30, "44444" });
            dict.insert({ 13, "666" });
            dict.insert({ 14, "11" });
      dict.insert({ 15,"55555" });
            dict.insert({ 16, "333333"});
            dict.insert({ 17, "22"});
            dict.insert({ 18, "7777777"});
            // dict.insert({ 19, "88888888"});
            // dict.insert({ 20, "999999999"});
            // dict.insert({ 21, "aaa"});
            // dict.insert({ 22, "bbb"});
            // dict.insert({ 23, "ccc"});
            // dict.insert({ 24, "ddd"});
            // cout
    如下图,0x0D是当前桶的数量,7是当前储存的节点数量。field_1始终指向上一个插入的键值对的地址


    Untitled 10.png (27.82 KB, 下载次数: 0)
    下载附件
    2022-11-30 13:58 上传

    buckets如下


    Untitled 11.png (32.96 KB, 下载次数: 0)
    下载附件
    2022-11-30 14:01 上传

    当我们查看第一个桶的内容时,你以为存放的是(14,“11”),但是实际上存放的是(13,“666”)


    Untitled 12.png (26.9 KB, 下载次数: 0)
    下载附件
    2022-11-30 13:58 上传



    Untitled 13.png (24.9 KB, 下载次数: 0)
    下载附件
    2022-11-30 13:57 上传

    此外,当哈希冲突时,会在桶中利用头插法插入节点,如(30, "44444”)和(17, "22”),都位于编号为4的桶内


    Untitled 14.png (23.84 KB, 下载次数: 0)
    下载附件
    2022-11-30 13:57 上传



    Untitled 15.png (24.82 KB, 下载次数: 0)
    下载附件
    2022-11-30 13:57 上传

    cppmaster
    动态调试找到输入函数,查看第二个参数如下,可以看出这是一个deque,跟进qword_55BD1608BF30,即存放的第一个元素,可以发现其中存放了两个红黑树,继续跟进可以看到数的节点存放的是一个哈希表,哈希表中有两个桶存放着deque数组……


    Untitled 16.png (25.43 KB, 下载次数: 0)
    下载附件
    2022-11-30 13:57 上传



    Untitled 17.png (42.08 KB, 下载次数: 0)
    下载附件
    2022-11-30 13:57 上传

    可以推测出,我们需要输入下标,最后得到的取值要与"8850a16d-e427-446e-b4df-5f45376e20e4”相等。通过对输入函数的交叉引用可以看到,共需要我们输入28次,也就是共有28层嵌套,可以手动搜索字符串从下往上提取数据,注意搜索时按照大端序搜索十六进制字符,可以找到


    Untitled 18.png (38.66 KB, 下载次数: 0)
    下载附件
    2022-11-30 13:57 上传

    之后再搜索此地址,逐层往上排查即可。
    不过手动搜索的方式未免有点丢人,费时费力,寻找代替方法。看到有大佬用idapython,照搬却跑不起来,尝试选择用frida代替,脚本如下
    import frida
    device = frida.get_local_device()
    def on_message(message, data):
        global bruteforce_index
        global bruteforce_success
        # print(bruteforce_index)
        # print(message['payload'])
        # print("[%s] => %s" % (message, data))
        # print(message['payload'][bruteforce_index],m[bruteforce_index])
        # if((message['payload'][bruteforce_index]) == m[bruteforce_index]):
        #         bruteforce_success = True
        print("[%s] => %s" % (message, data))
    handle = open("printf.js", "r",encoding='utf-8')
    script_code = handle.read()
    handle.close()
    moduleName = 'cppmaster'
    pid = device.spawn ( './'+moduleName,stdio='pipe')
    print("pid, ", pid)
    session = frida.attach(pid)
    script = session.create_script(script_code)
    script.on('message', on_message)
    script.load()
    device.resume(pid)
    device.input(pid,b'1111')
    input()
    // Find base address of current imported jvm.dll by main process fledge.exe
        var moduleName = 'cppmaster'
        var baseAddr = Module.findBaseAddress(moduleName);
        console.log(moduleName + ' baseAddr: ' + baseAddr);
        var size_table = {
            'rb_tree' : 48,
            'deque' : 80,
            'string' : 32,
            'hashtable' : 16,
        }
        var types_ordered = ["deque","rb_tree","hashtable","deque","rb_tree","rb_tree","hashtable",
                            "hashtable","rb_tree","rb_tree","hashtable","rb_tree","rb_tree","deque",
                            "deque","rb_tree","rb_tree","rb_tree","rb_tree","rb_tree","rb_tree",
                            "rb_tree","rb_tree","rb_tree","rb_tree","rb_tree","deque","deque","string"];
        var monitorFuncAddr = resolveAddress('0x1e470'); // Here we use the function address as seen in our disassembler
        function resolveAddress(addr) {
            var idaBase = ptr('0x0'); // Enter the base address of jvm.dll as seen in your favorite disassembler (here IDA)
            var offset = ptr(addr).sub(idaBase); // Calculate offset in memory from base address in IDA database
            var result = baseAddr.add(offset); // Add current memory base address to offset of function to monitor
            console.log('[+] New addr=' + result); // Write location of function in memory to console
            console.log(Memory.readU64(ptr(parseInt(result))));
            console.log(Memory.readByteArray(ptr(parseInt(result)+1),64));
            return result;
        }
        function dump_rbtree_node(addr) {
            return {
                'color' : Memory.readU64(ptr(addr)),
                'parent' : Memory.readU64(ptr(parseInt(addr) + 8)),
                'left' : Memory.readU64(ptr(parseInt(addr) + 16)),
                'right' : Memory.readU64(ptr(parseInt(addr) + 24)),
            }
        }
        function dump_rbtree_map(addr){
            var key = Memory.readU64(ptr(addr));
            var node_addr = parseInt(addr) + 8;
            var node_num = Memory.readU64(ptr(parseInt(addr)+40));
            var result = {};
            function visit(node){
                var info = dump_rbtree_node(node);
                var value_key = Memory.readU64(ptr(parseInt(node)+32));
                var data_addr = parseInt(node) + 40;
                result[value_key] = data_addr;
                if(info["left"]!=0){
                    visit(info["left"]);
                }
                if(info["right"]!=0){
                    visit(info["right"]);
                }
            }
            var d = dump_rbtree_node(node_addr);
            visit(d['parent']);
            return result;
        }
        function dump_deque_iterator(addr){
            var a = {
                'cur' : Memory.readU64(ptr(addr)),
                'first' : Memory.readU64(ptr(parseInt(addr) + 8)),
                'last' : Memory.readU64(ptr(parseInt(addr) + 16)),
                'map' : Memory.readU64(ptr(parseInt(addr) + 24))
            };
            return a;
        }
        function dump_deque(addr,delta){
            var deque_map = Memory.readU64(ptr(addr));
            var map_size = Memory.readU64(ptr(parseInt(addr) + 8));
            if(map_size!=8){
                console.log("[+] map_size Wrong!");
            }
            // console.log(addr);
            var start = dump_deque_iterator(parseInt(addr) + 16);
            var finish = dump_deque_iterator(parseInt(addr) + 16 + 32);
            var ptr1 = start['cur'];
            var index = 0;
            var result = {};
            while(ptr1!=finish['cur']){
                result[index] = ptr1;
                index += 1;
                ptr1 += delta;
            }
            return result;
        }
        function dump_string(addr){
            var data = Memory.readU64(ptr(addr));
            var length = Memory.readU64(ptr(parseInt(addr) + 8));
            return Memory.readCString(ptr(data),length);
        }
        function dump_hashtable_map(addr){
            var addrs = [];
            var hash_table = Memory.readU64(ptr(addr));
            var table_num = Memory.readU64(ptr(parseInt(addr) + 8));
            for(var i=0;i[table]
    运行py结果如下


    Untitled 19.png (110.16 KB, 下载次数: 0)
    下载附件
    2022-11-30 13:57 上传

    最后在得到的表中dfs即可
    参考文章:
    本文部分文字图片摘自下列文章,侵删
    https://bbs.pediy.com/thread-275133.htm#msg_header_h1_3
    https://nese.team/posts/n1ctf/
    http://c.biancheng.net/view/6908.html
    http://c.biancheng.net/view/7235.html
    https://blog.csdn.net/tjcwt2011/article/details/127729271

    下载次数, 下载附件

  • lyfff
    OP
      


    cao777 发表于 2022-11-30 17:29
    写的不错 楼主用的是什么编译器
    但是没有#include string头文件 能正常编译吗?

    用的是gcc version 12.2.0,当时还真没注意到,不过是没问题的
    cao777   

    写的不错 楼主用的是什么编译器
    但是没有#include string头文件 能正常编译吗?
    xiaoliang0011   

    有点深度了,难
    鹤舞九月天   

    谢谢分享
    78zhanghao87   

    学习了学习了,很棒的代码解析
    dofu05jj7uu   

    感谢分享谢谢!
    gotolala   

    感谢分享!
    ytfrdfiw   

    感谢分享!
    lfordch   

    感谢分享,收藏学习
    您需要登录后才可以回帖 登录 | 立即注册