Hi guys, this is series clear pwn from medium up to highest difficulty. I write these writeups mainly about the things I’ve learned, so some parts might be detailed while others are brief. Even so, they may still be useful to you if you read them. If you have any questions, free to ask me, Im free to share :>
PIE TIME 2
Description
1 2 3 4 5
Author: Darkraicg492
Description Can you try to get the flag? I'm not revealing anything anymore!! Additional details will be available after launching your challenge instance.
Arch: amd64 RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled Stripped: No
Attack analysis
My way is fast read all the code, so i can see vuln here, because printf need 2 parameter atleast to be safe, instead, it print the buffer value directly and it can has format string vulnerability
follow the root to find the branch, you can see win() function won’t be called in anywhere, so we have to call it by ourself.
And program flow is call call_function() to enter name, which has vuln format string. Then enter address to jump. So we can call win()
so we need win() address, but we got PIE, you can see it in checksec. It means address will be random.
The address will be calculated follow format : base address + offset
But as you know in code, random not really random, it needs some thing really random to calculate the random, like time. In binary we got ASLR, it’s make base address random with everytime we run, on each computer. In the other hands, it means offset is permanent
so if we can leak any address, we can calculate to base address, hence, we can know every address by know its offset.
In summary, we have a reverse road :
1 2 3 4
enter win() address ← know win() address ← know win’s offset and base address ← leak some address from binary
Description Here is a binary that has enough privilege to read the content of the flag file but will only let you know its hash. If only it could just give you the actual content! Additional details will be available after launching your challenge instance.
printf("You don't have what it takes. Only a true wizard could change my suspicions. What do you have to say?\n"); fflush(stdout); scanf("%1024s", buf); printf("Here's your input: "); printf(buf); printf("\n"); fflush(stdout);
if (sus == 0x67616c66) { printf("I have NO clue how you did that, you must be a wizard. Here you go...\n");
// Read in the flag FILE *fd = fopen("flag.txt", "r"); fgets(flag, 64, fd);
printf("%s", flag); fflush(stdout); } else { printf("sus = 0x%x\n", sus); printf("You can do better!\n"); fflush(stdout); }
return0; }
checksec
1 2 3 4 5 6 7 8
Arch: amd64 RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE(0x400000) SHSTK: Enabled IBT: Enabled Stripped: No
Attack analysis
You can see no PIE, so binary will be static address.
With format string vulnerabiliy :
1 2 3 4
printf("Here's your input: "); printf(buf); //vuln printf("\n"); fflush(stdout);
Our goal is print the flag by trigger the condition : sus == 0x67616c66, and we know sus‘s address because of no PIE.
So we know the sus‘s address, value to trigger and format string, so we will use %n to write a value for sus
Below here is shortest payload by using framework in pwntools.
payload = f'%{0x6761}c%24$hn'.encode() payload += f'%{0x6c66-0x6761}c%25$hn'.encode() payload = payload.ljust(0x50,b'a') payload += p64(sus+2) payload += p64(sus) p.sendlineafter(b'What do you have to say?\n', payload)
p.interactive()
explain :
fmtstr_payload() : from pwntools
14 : offset when your input entry
{sus : 0x67616c66} : sus is address we want to write value, and 0x67616c66 is that value
flag : picoCTF{f0rm47_57r?_f0rm47_m3m_741fa290}
format string 3
Description
1 2 3 4 5 6 7 8
Author: SkrubLawd
Description This program doesn't contain a win function. How can you win? Download the binary here. Download the source here. Download libc here, download the interpreter here. Run the binary with these two files present in the same directory. Additional details will be available after launching your challenge instance.
Source
All the file we got :
binary
source
libc
interpreter
Checksec :
1 2 3 4 5 6 7 8 9
Arch: amd64 RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE(0x3ff000) RUNPATH: b'.' SHSTK: Enabled IBT: Enabled Stripped: No
Attack analysis
Remember always patch binary with libc whenever you receive libc, I use pwninit to patch the libc.
Go around and we can’t see how to get shell or read flag, besides we get libc with different current version, so we need to ret2libc
In libc always has system() function, the goal maybe call command system("/bin/sh")
because of lacking of condition : return to system() + rdi is a pointer to "/bin/sh" string, i look around and see puts is holding that condition:
1
puts(normal_string);
normal_string is "/bin/sh" has been declared. But what’s wrong with puts ?
you can see this :
1
RELRO: Partial RELRO
it means got doesn’t GOT protection, so I think we can attack GOT. Bonus with no PIE it means GOT’s address will be static. Reverse our road we got :
1 2 3
call system("/bin/sh") <- attack puts's GOT to system() address <- format string to write into puts's GOT value