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

MrP*_*ayi 1 c linux gcc pointers linked-list

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

  1. 链接列表是使用malloc创建的,可以从堆区域访问.
  2. 堆起始和结束地址可以在/ proc/self/maps中找到(至少在Linux中).
  3. 可以访问原始链接列表中的至少一个节点.
  4. 指向前一个节点的指针将在某处的堆中找到.
  5. 并且可以递归搜索它,直到找到实际的头部.

为了更好地说明,请使用以下程序,该程序可以在默认配置下至少在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;
}
Run Code Online (Sandbox Code Playgroud)

产量

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)
Run Code Online (Sandbox Code Playgroud)

背景

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

所以,真正的问题是.

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

Yun*_*sch 5

正确的答案是:

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

简而言之:不.

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