Gravitational plate of three masses and a slashed discABC0Static engraved plate. Three-dimensional view is unavailable or reduced motion is requested.

← back to fieldarticle

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 Node and Tail 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.png
./NODE.png

// 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 next is 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 Node and returns its pointer.

nodeSet

  • Sets the node's member values.
  • Writes data and next on a Node.
  • Copies *x into the node pointed to by n.
  • Stores the third argument in n->next.

Init

  • Initializes the list before use.
  • Sets list->head to NULL so the list starts empty.
head(null)
 
head->next
 
heax->next->next

Checking whether the list has any nodes

  • After init, head is NULL and the list is empty.
list->head == NULL

Checking for exactly one node

  • With one node, list->head points at both the head and the tail.
  • That node's next is NULL.
  • So a one-node list satisfies:
list->head->next == NULL

Checking for exactly two nodes

  • With two nodes, list->head->next points at the second node (list->head->next->next).
  • The tail's next is NULL, so two nodes means:
list->head->next->next == NULL

Checking whether a pointer is the head

  • p has type Node * and points at some list node.
  • It is the head when:
p == list->head

Checking whether a pointer is the tail

  • Same p, type Node *.
  • It is the tail when:
p->next == NULL
  • 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 condition
Node *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 match
Node *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;
}
  1. Seed ptr at list->head.
  2. Loop while ptr is non-NULL; if it becomes NULL, there is nothing left to search — return NULL.
  3. If compare returns 0, set list->crnt = ptr and return ptr.
  4. Otherwise advance with ptr = ptr->next.
  5. 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 head
void 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 frontInsert
void 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 head at B. A one-node list becomes empty because the old head's next was 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 tail

currentRemove

  • Free the node selected by list->crnt.
  1. When crnt is not the head
  • Find the predecessor by scanning from the head until ptr->next == list->crnt.
  • Relink ptr->next to list->crnt->next, free the selected node, set crnt to ptr.
  1. When crnt is 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);
#endif

Security.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);
#endif

Source 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
	- Makefile
LINKEDLISTPATH = 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 all

Intel 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>:    ret
  • nodeAlloc only allocates sizeof(Node) and returns it, so use that to recover the Node layout.

Pseudo-C

__int64 nodeAlloc()
{
  __asm { endbr64 }
  return calloc(1LL, 32LL);
}

Node struct in IDA

Struct menu

./0.png
./0.png

  • 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.

./1.png
./1.png

  • Click ends, then press d to add a member.

./2.png
./2.png

  • Define the Security struct first.
  • Add members as before; click the type on the right and press d to change it.
  • Click a member name and press n to rename.
  • name is a 20-byte array: set it to byte, then press * and size it to 20.

./3.png
./3.png

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;

./4.png
./4.png

./5.png
./5.png

  • Press Y to 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] into rax, then writes it as n->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

./NODENODE.png
./NODENODE.png

//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>:    ret
  • Init takes the list from 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

./insertTT.png
./insertTT.png

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>:    ret
0x0000555555555414 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, rdi holds the list and rsi the Security data; the typed payload sits at 0x7fffffffd9f0 in 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]
  • nodeAlloc creates 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              None
0x55555555572e <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')
  • nodeSet installs 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->head to that new heap block.

rearInsert

./insertT.png
./insertT.png

  • 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->head is 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->head is NULL, the empty-list branch calls frontInsert.
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 next is 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

./removeR.png
./removeR.png

[-------------------------------------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->next is NULL, the single node is also the tail — call frontRemove.
  • Otherwise walk with ptr and pre until the tail.
  • Null out pre->next, free ptr, and leave crnt on pre.
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.

related

  1. Mar 30, 2021/archiveWindows heap management layers
  2. Feb 9, 2021/archiveARM Registers
  3. Jan 3, 2021/archiveTLS (Thread Local Storage) Callbacks

graphfeed