是否有可能恢复单个链表的头指针?

时间:2018-06-11 07:07:03

标签: c linux pointers gcc linked-list

如果可以进行以下假设,是否可以恢复链表的头节点。

  1. 链接列表是使用malloc创建的,可以从堆区域访问。
  2. 堆起始和结束地址可以在/ proc / self / maps中找到(至少在Linux中)。
  3. 可以访问原始链接列表中的至少一个节点。
  4. 指向上一个节点的指针将在某处的堆中找到。
  5. 可以递归搜索,直到找到实际的头部。
  6. 为了更好地说明,请使用以下程序,该程序可以在默认配置下至少在Ubuntu / WSL下使用gcc成功编译。

    程序

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    
    typedef struct node
    {
        int val;
        struct node *next;
    } node_t;
    node_t *head = NULL;
    unsigned long start_address = 0;
    unsigned long end_address = 0;
    
    node_t *getLastNode()
    {
        node_t *iter = head;
        for (; iter->next != NULL; iter = iter->next)
            ;
        return iter;
    }
    void addToLinkedList(int value)
    {
        node_t *data = malloc(sizeof(node_t));
        data->val = value;
        data->next = NULL;
    
        if (head == NULL)
            head = data;
        else
            getLastNode()->next = data;
    }
    
    void createLinkedList()
    {
        // Add 10 nodes to the linked list
        int start_val = 0x10101010;
        for (int i = 1; i <= 10; i++)
            addToLinkedList(start_val * i);
    }
    
    void printLinkedList()
    {
        printf("Head pointer of Linked List : %p\n", head);
        for (node_t *iter = head; iter != NULL; iter = iter->next)
            printf("%p -> value = %X, next = %p \n", iter, iter->val, iter->next);
        printf("\n");
    }
    
    void resetHeadPtr()
    {
        // Lets make head point to the last node
        head = getLastNode();
    }
    
    void findHeapBoundary()
    {
        // Code inspired from https://unix.stackexchange.com/a/251769/152334
        char mapsFilename[1024];
        char line[256];
    
        char area[1024];
        sprintf(mapsFilename, "/proc/%d/maps", getpid());
        FILE *pMapsFile = fopen(mapsFilename, "r");
    
        while (fgets(line, 256, pMapsFile) != NULL)
        {
            // Dirty hack to get the heap start and end address
            sscanf(line, "%08lx-%08lx%*[^[]%s\n", &start_address, &end_address, area);
            if (strcmp(area, "[heap]") == 0)
                break;
        }
        fclose(pMapsFile);
        printf("Heap memory start address : %p\n", (int *)start_address);
        printf("Heap memory end address   : %p\n", (int *)end_address);
    }
    
    node_t *findPointerInMemory()
    {
        for (int *ptr = (int *)start_address; ptr < (int *)(end_address - sizeof(node_t)); ptr++)
        {
            if (((node_t *)ptr)->next == head)
                return (node_t *)ptr;
        }
        return NULL;
    }
    
    void recoverHeadPtr()
    {
        node_t *ptr = findPointerInMemory();
        if (ptr == NULL)
        {
            printf("Cannot find %p in heap memory\nStopping Search\n\n", head);
            return;
        }
        printf("Found %p at %p\n", head, ptr);
        head = ptr;
        recoverHeadPtr();
    }
    
    int main(int argc, char const *argv[])
    {
        createLinkedList();
        printf("Original Linked List Contents\n*****************************\n");
        printLinkedList();
    
        resetHeadPtr();
        printf("Linked List Contents after reset\n********************************\n");
        printLinkedList();
    
        findHeapBoundary();
        recoverHeadPtr();
    
        printf("Recovered Linked List Contents\n******************************\n");
        printLinkedList();
        return 0;
    }
    

    输出

    Original Linked List Contents
    *****************************
    Head pointer of Linked List : 0x1db6010
    0x1db6010 -> value = 10101010, next = 0x1db6030
    0x1db6030 -> value = 20202020, next = 0x1db6050
    0x1db6050 -> value = 30303030, next = 0x1db6070
    0x1db6070 -> value = 40404040, next = 0x1db6090
    0x1db6090 -> value = 50505050, next = 0x1db60b0
    0x1db60b0 -> value = 60606060, next = 0x1db60d0
    0x1db60d0 -> value = 70707070, next = 0x1db60f0
    0x1db60f0 -> value = 80808080, next = 0x1db6110
    0x1db6110 -> value = 90909090, next = 0x1db6130
    0x1db6130 -> value = A0A0A0A0, next = (nil)
    
    Linked List Contents after reset
    ********************************
    Head pointer of Linked List : 0x1db6130
    0x1db6130 -> value = A0A0A0A0, next = (nil)
    
    Heap memory start address : 0x1db6000
    Heap memory end address   : 0x1dd7000
    Found 0x1db6130 at 0x1db6110
    Found 0x1db6110 at 0x1db60f0
    Found 0x1db60f0 at 0x1db60d0
    Found 0x1db60d0 at 0x1db60b0
    Found 0x1db60b0 at 0x1db6090
    Found 0x1db6090 at 0x1db6070
    Found 0x1db6070 at 0x1db6050
    Found 0x1db6050 at 0x1db6030
    Found 0x1db6030 at 0x1db6010
    Cannot find 0x1db6010 in heap memory
    Stopping Search
    
    Recovered Linked List Contents
    ******************************
    Head pointer of Linked List : 0x1db6010
    0x1db6010 -> value = 10101010, next = 0x1db6030
    0x1db6030 -> value = 20202020, next = 0x1db6050
    0x1db6050 -> value = 30303030, next = 0x1db6070
    0x1db6070 -> value = 40404040, next = 0x1db6090
    0x1db6090 -> value = 50505050, next = 0x1db60b0
    0x1db60b0 -> value = 60606060, next = 0x1db60d0
    0x1db60d0 -> value = 70707070, next = 0x1db60f0
    0x1db60f0 -> value = 80808080, next = 0x1db6110
    0x1db6110 -> value = 90909090, next = 0x1db6130
    0x1db6130 -> value = A0A0A0A0, next = (nil)
    

    背景

    我的一位朋友在接受采访时被问到以下问题。 “如果给出最后一个节点,你能找到单个链表的头指针吗?”。他回答“不”,即使面试官对答案并不完全满意,他也得到了这份工作。这让我开始思考,是否有可能这样做。

    所以,真正的问题是。

    1. 这种做法是否正确?
    2. 我们是否需要搜索其他内存段?
    3. 这种方法可以推广到Windows上吗?
    4. ASLR会对这种方法产生什么影响吗?

2 个答案:

答案 0 :(得分:5)

正确答案是:

不干净并且尝试这样做对于糟糕的设计来说将是一个冒险的解决方法 如果你确实试图在内存中搜索任何内容的地址(如果它是有问题的指针,那么它不会保证只包含地址),那么你可能会发现任何一块似乎意外包含的内存一个看起来像有问题的地址的数字 如果你继续使用它,假设它是指针,你就会引发各种各样的问题 如果你反复这样做以倒退单链表,那么你几乎可以保证找到至少一个废话。

简而言之:否。

一个简单的“号”太短了,可能因此而让审查员皱眉头。

答案 1 :(得分:0)

在标准C中,它是不可能的。

访问堆而不是通过malloc给出的指针(或那些指针的副本等)将获得未定义的行为。

但是,如果您了解分配器并且可以遍历已分配的内存块的结构,则可以找到候选列表节点。其中一人将成为首脑。