articleMar 1, 2021
Linked-List Attack Surface on Intel (Structure Notes)
Singly linked list layout, insert/remove helpers, and Intel disassembly of nodeAlloc/nodeSet/Init/frontInsert/rearInsert/rearRemove with IDA struct recovery and heap traces.
Linked list
Node / element
- The list's data unit.
- Each node holds data and a pointer to the next node.
- The ends are the
Head NodeandTail Node. - The node immediately before another is the
Predecessor Node. - The node immediately after is the
Successor Node.
Building the list
- On insert, allocate a node object.
- On delete, free that object. Insert and delete stay cheap.

// Node
typedef struct __node {
Security data;
struct __node *next;
} Node;- Self-referential type: a structure that holds a pointer to another object of the same type.
data : member that stores the payload
next : pointer to another node of the same struct type- The tail has no next node, so
nextis NULL.
Source
static Node *nodeAlloc(void) {
return calloc(1, sizeof(Node));
}
static void nodeSet(Node *n, const Security *x, const Node *next) {
n->data = *x;
n->next = (Node*)next;
}
void Init(List *list) {
list->head = NULL;
list->crnt = NULL;
}nodeAlloc
- Allocates a
Nodeand returns its pointer.
nodeSet
- Sets the node's member values.
- Writes
dataandnexton aNode. - Copies
*xinto the node pointed to byn. - Stores the third argument in
n->next.
Init
- Initializes the list before use.
- Sets
list->headto NULL so the list starts empty.
head(null)
head->next
heax->next->nextChecking whether the list has any nodes
- After init,
headis NULL and the list is empty.
list->head == NULLChecking for exactly one node
- With one node,
list->headpoints at both the head and the tail. - That node's
nextis NULL. - So a one-node list satisfies:
list->head->next == NULLChecking for exactly two nodes
- With two nodes,
list->head->nextpoints at the second node (list->head->next->next). - The tail's
nextis NULL, so two nodes means:
list->head->next->next == NULLChecking whether a pointer is the head
phas typeNode *and points at some list node.- It is the head when:
p == list->headChecking whether a pointer is the tail
- Same
p, typeNode *. - It is the tail when:
p->next == NULLSearch
- Finds a node that matches a condition.
- Returns a pointer to the node, or NULL on failure.
- Uses linear search from the head.
Search stop conditions
c1. walked past the tail without a match
c2. found a node that satisfies the search conditionNode *search(
List *list,
const data *x,
int compare(const data *x, const data *y)
);
list - pointer to the list being searched
x - pointer to the key data
compare - function pointer comparing x against a node's data; returns 0 on matchNode *search(List *list, const data *x, int compare(const data *x, const data *y)) {
// 1
Node *ptr = list->head;
//2
while (ptr != NULL) {
//3
if (compare(&ptr->data, x) == 0) {
list->crnt = ptr;
return ptr;
}
//4
ptr = ptr->next;
}
//5
return NULL;
}- Seed
ptratlist->head. - Loop while
ptris non-NULL; if it becomes NULL, there is nothing left to search — return NULL. - If
comparereturns 0, setlist->crnt = ptrand returnptr. - Otherwise advance with
ptr = ptr->next. - On failure return NULL.
frontInsert
- Insert a node at the head.
1. Save the current head pointer in ptr
2. Allocate with nodeAlloc and point list->head (and crnt) at the new node
3. Call nodeSet so the new head's next points at the old headvoid fronInsert(List *list, const data *x) {
Node *ptr = list->head;
list->head = list->crnt = nodeAlloc();
nodeSet(list->head , x, ptr);
}rearInsert
- Insert at the tail.
- First check emptiness (
list->head == NULL), then branch.
1. Non-empty list: walk to the tail and append
2. Empty list: same as a head insert — call frontInsertvoid rearInsert(List *list, const data *x) {
if (list->head == NULL) {
frontInsert(list, x);
} else {
Node *ptr = list->head;
while(ptr->next != NULL) {
ptr = ptr->next;
}
ptr->next = list->crnt = nodeAlloc();
nodeSet(ptr->next, x, NULL);
}
}frontRemove
- Remove the head when the list is non-empty (
list->head != NULL). - For A–B–C, free A and point
headat B. A one-node list becomes empty because the old head'snextwas already NULL.
void frontRemove(List *list) {
if (list->head != NULL) {
Node *ptr = list->head->next; // second node
free(list->head); // free head
list->head = list->crnt = ptr; // new head
}
}rearRemove
- Remove the tail; behavior depends on length.
void rearRemove(List *list) {
if (list->head != null) {
if ((list->head)->next == NULL) { // one node
frontRemove(list); // just remove head
} else {
// when the loop ends, ptr is the tail and pre is the node before it
Node *ptr = list->head;
Node *pre;
while(ptr->next != NULL) {
pre = ptr;
ptr = ptr->next;
}
pre->next = NULL;
free(ptr);
list->crnt = pre;
}
}
}1. One node: same as removing the head — call frontRemove
2. Two or more nodes: walk and delete the tailcurrentRemove
- Free the node selected by
list->crnt.
- When
crntis not the head
- Find the predecessor by scanning from the head until
ptr->next == list->crnt. - Relink
ptr->nexttolist->crnt->next, free the selected node, setcrnttoptr.
- When
crntis the head
- Call
frontRemove.
Full source
Headers
LinkedList.h
#ifndef __LinkedList
#define __LinkedList
#include "Security.h"
// Node
typedef struct __node {
Security data;
struct __node *next;
} Node;
typedef struct {
Node *head;
Node *crnt;
} List;
void Init(List *list);
Node *search(List *list, const Security *x, int compare(const Security *x, const Security *y));
void frontInsert(List *list, const Security *x);
void rearInsert(List *list, const Security *x);
void frontRemove(List *list);
void rearRemove(List *list);
void currentRemove(List *list);
void clearAllList(List *list);
void currentPrint(const List *list);
void currentPrintLn(const List *list);
void Print(const List *list);
void Terminate(List *list);
#endifSecurity.h
#ifndef ___Security
#define ___Security
typedef struct
{
int no;
char name[20];
} Security;
#define Security_NAME 2
#define Security_NO 1
int SecurityNoCmp(const Security *x, const Security *y);
int SecurityNameCmp(const Security *x, const Security *y);
void PrintSecurity(const Security *x);
void PrintLnSecurity(const Security *x);
Security ScanSecurity(const char *message, int sw);
#endifSource files
LinkedList.c
#include <stdio.h>
#include <stdlib.h>
#include "Security.h"
#include "LinkedList.h"
static Node *nodeAlloc(void) {
return calloc(1, sizeof(Node));
}
static void nodeSet(Node *n, const Security *x, const Node *next) {
n->data = *x;
n->next = (Node*)next;
}
void Init(List *list) {
list->head = NULL;
list->crnt = NULL;
}
Node *search(List *list, const Security *x, int compare(const Security *x, const Security *y)) {
Node *ptr = list->head;
while (ptr != NULL) {
if (compare(&ptr->data, x) == 0) {
list->crnt = ptr;
return ptr;
}
ptr = ptr->next;
}
return NULL;
}
void frontInsert(List *list, const Security *x) {
Node *ptr = list->head;
list->head = list->crnt = nodeAlloc();
nodeSet(list->head , x, ptr);
}
void rearInsert(List *list, const Security *x) {
if (list->head == NULL) {
frontInsert(list, x);
} else {
Node *ptr = list->head;
while(ptr->next != NULL) {
ptr = ptr->next;
}
ptr->next = list->crnt = nodeAlloc();
nodeSet(ptr->next, x, NULL);
}
}
void frontRemove(List *list) {
if (list->head != NULL) {
Node *ptr = list->head->next;
free(list->head);
list->head = list->crnt = ptr;
}
}
void rearRemove(List *list) {
if (list->head != NULL) {
if ((list->head)->next == NULL) {
frontRemove(list);
} else {
Node *ptr = list->head;
Node *pre;
while(ptr->next != NULL) {
pre = ptr;
ptr = ptr->next;
}
pre->next = NULL;
free(ptr);
list->crnt = pre;
}
}
}
void currentRemove(List *list) {
if (list->head != NULL) {
if (list->crnt == list->head) {
frontRemove(list);
} else {
Node *ptr = list->head;
while(ptr->next != list->crnt) {
ptr = ptr->next;
}
ptr->next = list->crnt->next;
free(list->crnt);
list->crnt = ptr;
}
}
}
void clearAllList(List *list) {
while(list->head != NULL) {
frontRemove(list);
}
list->crnt = NULL;
}
void currentPrint(const List *list) {
if (list->crnt == NULL) {
printf("선택된 노드가 없습니다.\n");
} else {
PrintLnSecurity(&list->crnt->data);
}
}
void currentPrintLn(const List *list) {
currentPrint(list);
putchar('\n');
}
void Print(const List *list) {
if (list->head == NULL) {
puts("노드가 없다.");
} else {
Node *ptr = list->head;
puts("[전체 노드 확인]");
while (ptr != NULL) {
PrintLnSecurity(&ptr->data);
ptr = ptr->next;
}
}
}
void Terminate(List *list) {
clearAllList(list);
}Security.c
#include <stdio.h>
#include <string.h>
#include "Security.h"
int SecurityNoCmp(const Security *x, const Security *y)
{
return x->no < y->no ? -1 : x->no > y->no ? 1 : 0;
}
int SecurityNameCmp(const Security *x, const Security *y)
{
return strcmp(x->name, y->name);
}
void PrintSecurity(const Security *x)
{
printf("%d %s", x->no, x->name);
}
void PrintLnSecurity(const Security *x)
{
printf("%d %s\n", x->no, x->name);
}
Security ScanSecurity(const char *message, int sw)
{
Security temp;
printf("%s하는 보안 코드를 입력하세요.\n", message);
if (sw & Security_NO)
{
printf("번호 : ");
scanf("%d", &temp.no);
}
if (sw & Security_NAME)
{
printf("이름 : ");
scanf("%s", temp.name);
}
return temp;
}main.c
#include <stdio.h>
#include "Security.h"
#include "LinkedList.h"
/*Menu*/
typedef enum
{
TERMINATE,
INS_FRONT,
INS_REAR,
RMV_FRONT,
RMV_REAR,
PRINT_CRNT,
RMV_CRNT,
SRCH_NO,
SRCH_NAME,
PRINT_ALL,
CLEAR,
} Menu;
/*Menu select*/
Menu SelectMenu(void)
{
int i, ch;
char *mstring[] = {
"머리에 노드를 삽입",
"꼬리에 노드를 삽입",
"머리 노드를 삭제",
"꼬리 노드를 삭제",
"선택한 노드를 출력",
"선택한 노드를 삭제",
"번호로 검색",
"이름으로 검색",
"모든 노드를 출력",
"모든 노드를 삭제",
};
do
{
for (i = TERMINATE; i < CLEAR; i++)
{
printf("(%2d) %-40s ", i + 1, mstring[i]);
if ((i % 3) == 2)
putchar('\n');
}
printf("(0) 종료 : ");
scanf("%d", &ch);
} while (ch < TERMINATE || ch > CLEAR);
return (Menu)ch;
}
/*Main*/
int main(void)
{
Menu menu;
List list;
Init(&list);
do
{
Security x;
switch (menu = SelectMenu())
{
case INS_FRONT:
x = ScanSecurity("머리에 삽입", Security_NO | Security_NAME);
frontInsert(&list, &x);
break;
case INS_REAR:
x = ScanSecurity("꼬리에 삽입", Security_NO | Security_NAME);
rearInsert(&list, &x);
break;
case RMV_FRONT:
frontRemove(&list);
break;
case RMV_REAR:
rearRemove(&list);
break;
case PRINT_CRNT:
currentPrint(&list);
break;
case RMV_CRNT:
currentRemove(&list);
break;
case SRCH_NO:
x = ScanSecurity("검색", Security_NO);
if (search(&list, &x, SecurityNoCmp) != NULL)
currentPrintLn(&list);
else
puts("그 번호의 데이터가 없다.");
break;
case SRCH_NAME:
x = ScanSecurity("검색", Security_NAME);
if (search(&list, &x, SecurityNoCmp) != NULL)
currentPrintLn(&list);
else
puts("그 이름의 데이터가 없다.");
break;
case PRINT_ALL:
Print(&list);
break;
case CLEAR:
clearAllList(&list);
break;
default:
break;
}
} while (menu != TERMINATE);
Terminate(&list); /*연결리스트 종료*/
return 0;
}Build (Makefile)
LinkedList
- include
- LinkedList.h
- Security.h
- src
- LinkedList
- LinkedList.c
- Security.c
- main.c
- MakefileLINKEDLISTPATH = LinkedList/
LINKEDLISTFILES = LinkedList.c Security.c
LINKEDLIST = $(addprefix $(LINKEDLISTPATH), $(LINKEDLISTFILES))
SRCPATH = ./src/
SRCFILES = main.c $(LINKEDLIST)
SRCS = $(addprefix $(SRCPATH), $(SRCFILES))
OBJECTS = $(SRCS:.c=.o)
INC = -I./include/
CC = gcc
CFLAGS = -Wall -Werror -Wextra $(INC)
NAME = main
RM = rm -fr
.c .o :
$(CC) $(CFLAGS) -c
all : $(NAME)
$(NAME) : $(OBJECTS)
$(CC) $(CFLAGS) $(OBJECTS) -o $(NAME)
@make clean
clean :
$(RM) $(OBJECTS) core
fclean : clean
$(RM) $(NAME)
re : fclean all
.PHONY : re fclean clean allIntel arch disassembly
nodeAlloc
static Node *nodeAlloc(void) {
return calloc(1, sizeof(Node));
}
Dump of assembler code for function nodeAlloc:
0x00005555555555ff <+0>: endbr64
0x0000555555555603 <+4>: push rbp
0x0000555555555604 <+5>: mov rbp,rsp
0x0000555555555607 <+8>: mov esi,0x20
0x000055555555560c <+13>: mov edi,0x1
0x0000555555555611 <+18>: call 0x555555555110 <calloc@plt>
0x0000555555555616 <+23>: pop rbp
0x0000555555555617 <+24>: retnodeAlloconly allocatessizeof(Node)and returns it, so use that to recover theNodelayout.
Pseudo-C
__int64 nodeAlloc()
{
__asm { endbr64 }
return calloc(1LL, 32LL);
}Node struct in IDA
Struct menu

- No struct is defined yet, which makes analysis painful, so create one in IDA.
- Open the Structures tab, press
Insert, name it, and add the struct.

- Click
ends, then pressdto add a member.

- Define the
Securitystruct first. - Add members as before; click the type on the right and press
dto change it. - Click a member name and press
nto rename. nameis a 20-byte array: set it to byte, then press*and size it to 20.

Struct definitions
// Node
typedef struct __node {
Security data;
struct __node *next;
} Node;
typedef struct {
Node *head;
Node *crnt;
} List;
// Security.h
typedef struct
{
int no;
char name[20];
} Security;

- Press
Yto set the field to your new struct type.
nodeSet
static void nodeSet(Node *n, const Security *x, const Node *next) {
n->data = *x;
n->next = (Node*)next;
}
Dump of assembler code for function nodeSet:
0x0000555555555618 <+0>: endbr64
0x000055555555561c <+4>: push rbp
0x000055555555561d <+5>: mov rbp,rsp
0x0000555555555620 <+8>: mov QWORD PTR [rbp-0x8],rdi
0x0000555555555624 <+12>: mov QWORD PTR [rbp-0x10],rsi
0x0000555555555628 <+16>: mov QWORD PTR [rbp-0x18],rdx
0x000055555555562c <+20>: mov rcx,QWORD PTR [rbp-0x8]
0x0000555555555630 <+24>: mov rsi,QWORD PTR [rbp-0x10]
0x0000555555555634 <+28>: mov rax,QWORD PTR [rsi]
0x0000555555555637 <+31>: mov rdx,QWORD PTR [rsi+0x8]
0x000055555555563b <+35>: mov QWORD PTR [rcx],rax
0x000055555555563e <+38>: mov QWORD PTR [rcx+0x8],rdx
0x0000555555555642 <+42>: mov rax,QWORD PTR [rsi+0x10]
0x0000555555555646 <+46>: mov QWORD PTR [rcx+0x10],rax
0x000055555555564a <+50>: mov rax,QWORD PTR [rbp-0x8]
0x000055555555564e <+54>: mov rdx,QWORD PTR [rbp-0x18]
0x0000555555555652 <+58>: mov QWORD PTR [rax+0x18],rdx
0x0000555555555656 <+62>: nop
0x0000555555555657 <+63>: pop rbp
0x0000555555555658 <+64>: ret- Before the call, three args (Node, Security, next Node) land on the stack 8 bytes apart at
[rbp-0x8]through[rbp-0x18].
0x0000555555555630 <+24>: mov rsi,QWORD PTR [rbp-0x10]
0x0000555555555634 <+28>: mov rax,QWORD PTR [rsi]
0x0000555555555637 <+31>: mov rdx,QWORD PTR [rsi+0x8]
0x000055555555563b <+35>: mov QWORD PTR [rcx],rax- It loads the Security payload via
[rsi]intorax, then writes it asn->data = *x.
Pseudo-C
void __fastcall nodeSet(Node *a1, Security *a2, Node *a3)
{
Node *node; // rcx
Security *x; // rsi
__int64 v5; // rdx
__int64 v6; // [rsp-8h] [rbp-8h]
__asm { endbr64 }
*(&v6 - 1) = (__int64)a1;
*(&v6 - 2) = (__int64)a2;
*(&v6 - 3) = (__int64)a3;
node = (Node *)*(&v6 - 1);
x = (Security *)*(&v6 - 2);
v5 = *(_QWORD *)&x->name[4];
node->data = *(_QWORD *)&x->no;
node->next = v5;
node[1].data = *(_QWORD *)&x->name[12];
*(_QWORD *)(*(&v6 - 1) + 24) = *(&v6 - 3);
}Init

//c
void Init(List *list) {
list->head = NULL;
list->crnt = NULL;
}
Dump of assembler code for function Init:
=> 0x0000555555555659 <+0>: endbr64
0x000055555555565d <+4>: push rbp
0x000055555555565e <+5>: mov rbp,rsp
0x0000555555555661 <+8>: mov QWORD PTR [rbp-0x8],rdi
0x0000555555555665 <+12>: mov rax,QWORD PTR [rbp-0x8]
0x0000555555555669 <+16>: mov QWORD PTR [rax],0x0
0x0000555555555670 <+23>: mov rax,QWORD PTR [rbp-0x8]
0x0000555555555674 <+27>: mov QWORD PTR [rax+0x8],0x0
0x000055555555567c <+35>: nop
0x000055555555567d <+36>: pop rbp
0x000055555555567e <+37>: retInittakes thelistfrom main and zeros both pointer members.- Both members are pointers, 8 bytes apart.
Pseudo-C
void __fastcall Init(list *a1)
{
list *list; // [rsp-8h] [rbp-8h]
__asm { endbr64 }
*(&list - 1) = a1;
(*(&list - 1))->head = 0LL;
(*(&list - 1))->crnt = 0LL;
}frontInsert

void frontInsert(List *list, const Security *x) {
Node *ptr = list->head;
list->head = list->crnt = nodeAlloc();
nodeSet(list->head , x, ptr);
}
Dump of assembler code for function frontInsert:
0x00005555555556e8 <+0>: endbr64
0x00005555555556ec <+4>: push rbp
0x00005555555556ed <+5>: mov rbp,rsp
0x00005555555556f0 <+8>: sub rsp,0x20
0x00005555555556f4 <+12>: mov QWORD PTR [rbp-0x18],rdi
0x00005555555556f8 <+16>: mov QWORD PTR [rbp-0x20],rsi
0x00005555555556fc <+20>: mov rax,QWORD PTR [rbp-0x18]
0x0000555555555700 <+24>: mov rax,QWORD PTR [rax]
0x0000555555555703 <+27>: mov QWORD PTR [rbp-0x8],rax
0x0000555555555707 <+31>: call 0x5555555555ff <nodeAlloc>
0x000055555555570c <+36>: mov rdx,QWORD PTR [rbp-0x18]
0x0000555555555710 <+40>: mov QWORD PTR [rdx+0x8],rax
0x0000555555555714 <+44>: mov rax,QWORD PTR [rbp-0x18]
0x0000555555555718 <+48>: mov rdx,QWORD PTR [rax+0x8]
0x000055555555571c <+52>: mov rax,QWORD PTR [rbp-0x18]
0x0000555555555720 <+56>: mov QWORD PTR [rax],rdx
0x0000555555555723 <+59>: mov rax,QWORD PTR [rbp-0x18]
0x0000555555555727 <+63>: mov rax,QWORD PTR [rax]
0x000055555555572a <+66>: mov rdx,QWORD PTR [rbp-0x8]
0x000055555555572e <+70>: mov rcx,QWORD PTR [rbp-0x20]
0x0000555555555732 <+74>: mov rsi,rcx
0x0000555555555735 <+77>: mov rdi,rax
0x0000555555555738 <+80>: call 0x555555555618 <nodeSet>
0x000055555555573d <+85>: nop
0x000055555555573e <+86>: leave
0x000055555555573f <+87>: ret0x0000555555555414 in main ()
(gdb)
머리에 삽입하는 보안 코드를 입력하세요.
번호 : 10
이름 : AAAAAAAA
[----------------------------------registers-----------------------------------]
RAX: 0x7fffffffd9e0 --> 0x0
RBX: 0x555555555c40 (<__libc_csu_init>: endbr64)
RCX: 0x7fffffffd9b0 --> 0x414141410000000a ('\n')
RDX: 0x7fffffffd9f0 --> 0x414141410000000a ('\n')
RSI: 0x7fffffffd9f0 --> 0x414141410000000a ('\n')
RDI: 0x7fffffffd9e0 --> 0x0
RBP: 0x7fffffffda10 --> 0x0
RSP: 0x7fffffffd9b0 --> 0x414141410000000a ('\n')
RIP: 0x55555555543f (<main+199>: call 0x5555555556e8 <frontInsert>)
R8 : 0xa ('\n')
R9 : 0x9 ('\t')
R10: 0x55555555626b --> 0x31b010000007325
R11: 0x246
R12: 0x555555555140 (<_start>: endbr64)
R13: 0x7fffffffdb00 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
- On a head insert,
rdiholds the list andrsithe Security data; the typed payload sits at0x7fffffffd9f0in this run.
0x5555555556fc <frontInsert+20>: mov rax,QWORD PTR [rbp-0x18]
0x555555555700 <frontInsert+24>: mov rax,QWORD PTR [rax]
0x555555555703 <frontInsert+27>: mov QWORD PTR [rbp-0x8],rax
=> 0x555555555707 <frontInsert+31>: call 0x5555555555ff <nodeAlloc>
0x55555555570c <frontInsert+36>: mov rdx,QWORD PTR [rbp-0x18]
0x555555555710 <frontInsert+40>: mov QWORD PTR [rdx+0x8],rax
0x555555555714 <frontInsert+44>: mov rax,QWORD PTR [rbp-0x18]
0x555555555718 <frontInsert+48>: mov rdx,QWORD PTR [rax+0x8]nodeAlloccreates the new head node.
gdb-peda$ parseheap
addr prev size status fd bk
0x555555559000 0x0 0x290 Used None None
0x555555559290 0x0 0x410 Used None None
0x5555555596a0 0x0 0x410 Used None None
↓↓↓↓↓↓
gdb-peda$ parseheap
addr prev size status fd bk
0x555555559000 0x0 0x290 Used None None
0x555555559290 0x0 0x410 Used None None
0x5555555596a0 0x0 0x410 Used None None
0x555555559ab0 0x0 0x30 Used None None0x55555555572e <frontInsert+70>: mov rcx,QWORD PTR [rbp-0x20]
0x555555555732 <frontInsert+74>: mov rsi,rcx
0x555555555735 <frontInsert+77>: mov rdi,rax
=> 0x555555555738 <frontInsert+80>: call 0x555555555618 <nodeSet>
0x55555555573d <frontInsert+85>: nop
0x55555555573e <frontInsert+86>: leave
0x55555555573f <frontInsert+87>: ret
0x555555555740 <rearInsert>: endbr64
Guessed arguments:
arg[0]: 0x555555559ac0 --> 0x0
arg[1]: 0x7fffffffd9f0 --> 0x414141410000000a ('\n')
arg[2]: 0x0
arg[3]: 0x7fffffffd9f0 --> 0x414141410000000a ('\n')nodeSetinstalls the payload: arg0 is the new head, arg1 is the Security data, arg3/arg2 is the old-head pointer (here NULL for the first node).
gdb-peda$ x/10gx 0x555555559ab0
0x555555559ab0: 0x0000000000000000 0x0000000000000031
0x555555559ac0: 0x414141410000000a 0x0000004141414141
0x555555559ad0: 0x0000000000000000 0x0000000000000000
0x555555559ae0: 0x0000000000000000 0x0000000000020521
0x555555559af0: 0x0000000000000000 0x0000000000000000- Past the chunk size field, the data area holds the payload. The first insert sets
list->headto that new heap block.
rearInsert

- Now the tail-insert path:
0x55555555547d <main+261>: lea rax,[rbp-0x30]
0x555555555481 <main+265>: mov rsi,rdx
0x555555555484 <main+268>: mov rdi,rax
=> 0x555555555487 <main+271>: call 0x555555555740 <rearInsert>
0x55555555548c <main+276>: jmp 0x5555555555ce <main+598>
0x555555555491 <main+281>: lea rax,[rbp-0x30]
0x555555555495 <main+285>: mov rdi,rax
0x555555555498 <main+288>: call 0x5555555557d7 <frontRemove>
Guessed arguments:
arg[0]: 0x7fffffffd9e0 --> 0x555555559ac0 --> 0x414141410000000a ('\n')
arg[1]: 0x7fffffffd9f0 --> 0x4242424200000014
arg[2]: 0x7fffffffd9f0 --> 0x4242424200000014- First arg is the list; because a node already exists,
list->headis non-NULL. - Second arg is the new payload.
0x0000555555555758 <+24>: mov rax,QWORD PTR [rax]
=> 0x000055555555575b <+27>: test rax,rax
0x000055555555575e <+30>: jne 0x555555555775 <rearInsert+53>
0x0000555555555760 <+32>: mov rdx,QWORD PTR [rbp-0x20]
0x0000555555555764 <+36>: mov rax,QWORD PTR [rbp-0x18]
0x0000555555555768 <+40>: mov rsi,rdx
0x000055555555576b <+43>: mov rdi,rax
0x000055555555576e <+46>: call 0x5555555556e8 <frontInsert>- If
list->headis NULL, the empty-list branch callsfrontInsert.
0x0000555555555780 <+64>: jmp 0x55555555578e <rearInsert+78>
0x0000555555555782 <+66>: mov rax,QWORD PTR [rbp-0x8]
0x0000555555555786 <+70>: mov rax,QWORD PTR [rax+0x18]
0x000055555555578a <+74>: mov QWORD PTR [rbp-0x8],rax
0x000055555555578e <+78>: mov rax,QWORD PTR [rbp-0x8]
0x0000555555555792 <+82>: mov rax,QWORD PTR [rax+0x18]
0x0000555555555796 <+86>: test rax,rax
0x0000555555555799 <+89>: jne 0x555555555782 <rearInsert+66>
0x000055555555579b <+91>: call 0x5555555555ff <nodeAlloc>- If any node exists, walk until
nextis NULL, allocate, and link.
gdb-peda$ parseheap
addr prev size status fd bk
0x555555559000 0x0 0x290 Used None None
0x555555559290 0x0 0x410 Used None None
0x5555555596a0 0x0 0x410 Used None None
0x555555559ab0 0x0 0x30 Used None None
0x555555559ae0 0x0 0x30 Used None None
gdb-peda$ x/30gx 0x555555559ab0
0x555555559ab0: 0x0000000000000000 0x0000000000000031
0x555555559ac0: 0x414141410000000a 0x0000004141414141
0x555555559ad0: 0x0000000000000000 0x0000555555559af0
0x555555559ae0: 0x0000000000000000 0x0000000000000031
0x555555559af0: 0x4242424200000014 0x0000550042424242
0x555555559b00: 0x0000000000000000 0x0000000000000000
0x555555559b10: 0x0000000000000000 0x00000000000204f1- One more heap chunk appears; two nodes are live.
rearRemove

[-------------------------------------code-------------------------------------]
0x55555555549d <main+293>: jmp 0x5555555555ce <main+598>
0x5555555554a2 <main+298>: lea rax,[rbp-0x30]
0x5555555554a6 <main+302>: mov rdi,rax
=> 0x5555555554a9 <main+305>: call 0x55555555582f <rearRemove>
0x5555555554ae <main+310>: jmp 0x5555555555ce <main+598>
0x5555555554b3 <main+315>: lea rax,[rbp-0x30]
0x5555555554b7 <main+319>: mov rdi,rax
0x5555555554ba <main+322>: call 0x55555555599a <currentPrint>
Guessed arguments:
arg[0]: 0x7fffffffd9e0 --> 0x555555559ac0 --> 0x414141410000000a ('\n')- Tail remove takes the list pointer as its only argument.
0x0000555555555846 <+23>: test rax,rax
0x0000555555555849 <+26>: je 0x5555555558bb <rearRemove+140>
0x000055555555584b <+28>: mov rax,QWORD PTR [rbp-0x18]
0x000055555555584f <+32>: mov rax,QWORD PTR [rax]
0x0000555555555852 <+35>: mov rax,QWORD PTR [rax+0x18]
0x0000555555555856 <+39>: test rax,rax
0x0000555555555859 <+42>: jne 0x555555555869 <rearRemove+58>
0x000055555555585b <+44>: mov rax,QWORD PTR [rbp-0x18]
0x000055555555585f <+48>: mov rdi,rax
0x0000555555555862 <+51>: call 0x5555555557d7 <frontRemove>- Two checks: first require
list->head != NULL, then continue. - If
list->head->nextis NULL, the single node is also the tail — callfrontRemove. - Otherwise walk with
ptrandpreuntil the tail. - Null out
pre->next, freeptr, and leavecrntonpre.
addr prev size status fd bk
0x555555559000 0x0 0x290 Used None None
0x555555559290 0x0 0x410 Used None None
0x5555555596a0 0x0 0x410 Used None None
0x555555559ab0 0x0 0x30 Used None None
0x555555559ae0 0x0 0x30 Freed 0x0 None
gdb-peda$ heapinfoall
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x555555559b10 (size : 0x204f0)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x0
(0x30) tcache_entry[1](1): 0x555555559af0- The freed chunk is tcache-sized, so it lands on the tcache freelist.