如何通过控制流劫持与缓冲区溢出攻击C语言双向链表程序?
攻击目标
攻击目标是一个使用双向链表的C语言程序,代码如下:
#include <stdio.h> #include <string.h> #include <stdlib.h> typedef struct node { struct node *prev; struct node *next; char data[32]; } Node; Node *list_insert(Node *after) { Node *node = calloc(1, sizeof(struct node)); if (after) { node->prev = after; if (after->next) { node->next = after->next; node->next->prev = node; } after->next = node; } return node; } void list_delete(Node *node) { if (node->prev) { node->prev->next = node->next; } if (node->next) { node->next->prev = node->prev; } } int _main(int argc, char *argv[]) { if (argc != 4) { fprintf(stderr, "Error: Need three command-line arguments\n"); return 1; } Node *a = list_insert(0); Node *b = list_insert(a); Node *c = list_insert(b); strcpy(a->data, argv[1]); strcpy(b->data, argv[2]); strcpy(c->data, argv[3]); list_delete(c); list_delete(b); list_delete(a); return 0; }
环境设置
OS = Linux ISA = x86_32 addr_of_a = 0x080002e0 addr_of_b = 0x08000320 addr_of_c = 0x08000350 ret = 0xfffe8764 # list_delete函数的返回地址(返回至_main)
当前进展与问题
程序的漏洞在于每个链表节点的char data[32]缓冲区存在溢出。我的攻击计划如下:
- 溢出
b->data到c的内存区域,将c->prev覆写为ret-0x4,c->next覆写为c->data的地址(指向注入shellcode的区域); - 调用
list_delete(c)时,执行node->prev->next = node->next,将list_delete的返回地址替换为c->data的地址; - 遇到瓶颈:后续执行
node->next->prev = node->prev时,shellcode的前4字节会被ret-0x4覆写。若将node->next设为null则无法覆盖栈上的返回地址,请问该如何解决?
解决方案
方案1:偏移shellcode入口点,避开被覆写区域
这是最可靠的解决思路:
- 向
c->data中写入:4字节NOP(0x90)指令 + 完整shellcode,让shellcode从c->data + 0x4的位置开始; - 溢出
b->data时,将c->prev设为ret-0x4,c->next设为c->data + 0x4; - 执行
list_delete(c)时,node->prev->next = node->next会把返回地址修改为c->data + 0x4,直接跳转到未被覆写的shellcode起始位置; - 此时
node->next->prev = node->prev会修改c->data + 0x4的前4字节?可以进一步调整:在c->data中写入8字节以上的NOPsled,再放shellcode,返回地址设为c->data。即使前4字节被覆写,后续的NOP指令依然会引导执行流滑到有效的shellcode区域。
方案2:让node->next指向无关内存区域
既然node->next->prev = node->prev会修改node->next指向地址的前4字节,我们可以让node->next指向不影响shellcode的区域:
- 溢出
b->data时,c->prev设为ret-0x4,c->next设为栈上的临时区域(比如ret + 0x4,该位置的修改不会影响shellcode); - 这里需要调整逻辑:先通过溢出修改
c->prev为ret-0x4,同时单独构造node->prev->next的赋值目标——比如在栈上伪造一个Node结构,让node->prev指向这个伪造结构,其next成员设为c->data。不过这个方案需要更复杂的内存布局构造,不如方案1直接。
方案3:构造“可容忍覆写”的shellcode前缀
在shellcode最前面放置一条短跳转指令(比如jmp 0x4,跳转到当前地址+4字节),这样即使前4字节被覆写成ret-0x4,只要跳转指令仍能生效,就能跳到后续的有效shellcode。但ret-0x4是栈地址,对应的机器码不一定是有效指令,可靠性较低,不推荐。
内容的提问来源于stack exchange,提问作者Patrick
相关产品推荐
相关产品推荐

