Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

Here's a LIBC

Introduction

Here's a LIBC is the third challenge in the binary exploitation category. The description states:

I am once again asking for you to pwn this binary vuln libc.so.6 Makefile nc mercury.picoctf.net 49464

It provides links to the vulnerable executable, a specific libc libray, and the Makefile.

Information Gathering

Hint #1

PWNTools has a lot of useful features for getting offsets.

Hmm...this sounds like maybe we need to know the offset to something in libc. I guess we won't know unless we keep diving in!

Glibc

Since it's in the title, it must be important. Let's figure out what version of libc we are dealing with:

grep

As we learned in the last challenge we did, Cache Me Outside, glibc 2.27 was the version after per-thread caches were introduced. We don't really know much more than that at the moment, though.

Security settings

Checksec, a great program included with pwntools, gives us this information about the program vuln (though we also have the Makefile so we could look there).

checksec

The lack of vulnerability mitigations opens up a bunch of doors like smashing the stack or ROP/JOP. The stack is not executable, so I'm going to guess we need to execute inside of libc for this challenge. The executable is also not stripped which should make decompilation and debugging easier.

Running the Program

We need a matching version of the dynamic linker. We can get that with pwninit or download it off of some random website. I prefer the former.

pwninit

This will download our linker and patch our executable to use the libc version it shipped with and the new linker. We can then run the patched binary to see that it contains an echo server that capitalizes every other character:

run

Ghidra

To get a sense of what is going on, let's examine it in Ghidra.

We start by examining main, which we quickly find is not that interesting. main will initialize some stuff, print out the welcome message, and then loop infinitely calling do_stuff, as shown below:

main

The function do_stuff, on the other hand, asks for user input via a scanf and stores the input in a 112 byte stack-based buffer. The bounds of this buffer is never checked. Rather, the scanf will continue to take input until a new-line character is provided. This allows the user to overflow the buffer and adjust contents on the stack. After reading in user input, the function loops over the first 100 characters and calls convert_case to presumably have an uppercase character followed by a lowercase character. All bytes in the buffer after index 100 are not adjusted. Finally, the function calls puts to show the user the buffer after it has been converted.

Information Summary

Okay, what do we know:

  • We have an amd64 64-bit executable.
  • NX is enabled.
  • We assume ASLR is enabled on the remote host.
  • We have a stack-based buffer overflow in the do_stuff function.

Given that the stack isn't executable, we can't just overwrite the return address and point back to some shellcode on the stack. With ASLR enabled, we also can't reliably know a fixed address to jump to to execute a subroutine inside of a loaded library. This gives us only one real option: we somehow need to leak an address from inside a library (and since we were given libc, that's probably a good starting point) and use that to find subroutines inside of that library to execute code snippets, otherwise known as Return Oriented Programming (ROP).

TODO:

Okay, I committed the cardinal sin of writeups: I got ahead of myself and finished the challenge before I finished the writeup. Usually I do them in parallel. I don't feel like making the context switch to continue with this one, given that it has been a couple weeks after I've solved it. But I also can't keep a dangling branch open this long. So here we are in main without a finished writeup. I am sorry.