This was a really, really cool lab. This lab focused on writing shellcode and execution of my own exploit code in the 3 vulnerable executables. It was really satisfying to see assembly instructions I had created being executed. Below are a few notes for myself, a few good resources, and notes on a few new tools I used. I ran all the exploits on my local machine. If you want to skip ahead to my writeups they start here.
Registers:
strace cheat sheet:
https://blog.packagecloud.io/eng/2015/11/15/strace-cheat-sheet/
Read error values:
error-code-nums https://www.scadacore.com/tools/programming-calculators/online-hex-converter/
Std Streams:
I was dumb and had this messed up for a second. Leaving a not here for my future self. https://en.wikipedia.org/wiki/Standard_streams 0 -> stdin 1 -> stdout 2 -> stderr
Shell Noob
This tool is super helpful! It outputs compiled assembly instructions, syscall numbers, etc… ! https://github.com/reyammer/shellnoob/blob/master/README.md
Manual Assembly:
The steps for maunally loading and creating shellcode:
|
|
Pwntools
This python library is amazing! It has a ton of super useful builtin in functionality that I’m just beginning to explore. It was very frustrating to start using at first only because of errors in logic I had in my own code. I’m going to start to use this project in future posts! (It also has a hook into gdb for debugging exploit code!) https://docs.pwntools.com/en/stable/
Shellcode links: [1] [2]
GDB notes:
Follow forks in gdb: (Peda follows child!)
https://sourceware.org/gdb/onlinedocs/gdb/Forks.html
set follow-fork-mode [ 'parent' | 'child' ]
Checkpionts: checkpoint
**Breakpoints: **break [func] break *[func]+[offset] break *0x[addr]>/>
REMINDERS
REMEMBER!! Check ERRNO/Registers for errors in shellcode!! Filepath for header files: /usr/include grep -r O_RDONLY
LAB3C
|
|
In this challenge, the vulnerability lies in the lines highlighted above. The return pointer from the main function can be overwritten using an overflow in the stack variable a_user_pass
and a_user_name
. The input is taken in the fgets calls on line 28 and line 39. Here the obvious mistake is the use of hex to limit the size of user input (0x100 (256) and 0x64 (100)). Either buffer is a great location for shellcode but the heap is the better choice, as it is has much more reliable address ( a stack address will change based on stack input and size).
The exploit follows find the address of the a_user_name and a_user_pass variables and ensure the instruction pointer (EIP) jumps to a_user_name with a stack overflow overwriting the return address using a_user_pass. Of course that is easier said than done.
I Ran into an issue with python3. Python3 and python2 treat strings differently, python2 they are a series of bytes, where as python3 there are a series of unicode characters (hence python3’s byte and bytestring classes).
I wanted to run the exploit in python3, since I had used python2 in the last lab. Python2’s interpretation of strings makes the data easier to work with so I will be using python2 for the time being.
Getting address of a_user_name:
Placing a breakpoint (break *main+92
) at the verify function shows the address of the a_user_name
buffer as the top address in the stack.
[-------------------------------------code-------------------------------------]
0x80487d8 <main+72>: mov DWORD PTR [esp+0x4],0x100
0x80487e0 <main+80>: mov DWORD PTR [esp],0x8049c40
0x80487e7 <main+87>: call 0x80485f0 <fgets@plt>
=> 0x80487ec <main+92>: call 0x804873d <verify_user_name>
0x80487f1 <main+97>: mov DWORD PTR [esp+0x5c],eax
0x80487f5 <main+101>: cmp DWORD PTR [esp+0x5c],0x0
0x80487fa <main+106>: je 0x804880f <main+127>
0x80487fc <main+108>: mov DWORD PTR [esp],0x8048970
No argument
[------------------------------------stack-------------------------------------]
0000| 0xffffd010 --> 0x8049c40 ("rpisec\n")
Getting address of a_user_pass:
Placing a breakpoint at the verify function (break *main+175
) shows the address of the a_user_pass
[-------------------------------------code-------------------------------------]
0x8048833 <main+163>: call 0x80485f0 <fgets@plt>
0x8048838 <main+168>: lea eax,[esp+0x1c]
0x804883c <main+172>: mov DWORD PTR [esp],eax
=> 0x804883f <main+175>: call 0x804876d <verify_user_pass>
0x8048844 <main+180>: mov DWORD PTR [esp+0x5c],eax
0x8048848 <main+184>: cmp DWORD PTR [esp+0x5c],0x0
0x804884d <main+189>: je 0x8048856 <main+198>
0x804884f <main+191>: cmp DWORD PTR [esp+0x5c],0x0
Guessed arguments:
arg[0]: 0xffffd02c ("asdf\n")
[------------------------------------stack-------------------------------------]
0000| 0xffffd010 --> 0xffffd02c ("asdf\n")
Now the exploit can be written grabbing some helpful shellcode from the exploit-db database. This script prints out the payload and I pass it in on standard in.
|
|
The trick here is that just passing the shellcode payload by itself does not keep stdin open, and the program will close. (like in my last lab). Adding the & cat does the trick!
LAB3B
|
|
man page gets:
Never use this function.
gets() reads a line from stdin into the buffer pointed to by s until
either a terminating newline or EOF, which it replaces with a null byte
('\0'). No check for buffer overrun is performed (see BUGS below)
addr of buffer: like above in a breakpoint on the call to gets, shows the address of the buffer highlighted below.
[-------------------------------------code-------------------------------------]
0x8048a19 <main+138>: call 0x8048780 <puts@plt>
0x8048a1e <main+143>: lea eax,[esp+0x20]
0x8048a22 <main+147>: mov DWORD PTR [esp],eax
=> 0x8048a25 <main+150>: call 0x8048730 <gets@plt>
0x8048a2a <main+155>: jmp 0x8048ae2 <main+339>
0x8048a2f <main+160>: lea eax,[esp+0x1c]
0x8048a33 <main+164>: mov DWORD PTR [esp],eax
0x8048a36 <main+167>: call 0x8048770 <wait@plt>
Guessed arguments:
arg[0]: 0xffffcfe0 --> 0x0
[------------------------------------stack-------------------------------------]
0000| 0xffffcfc0 --> 0xffffcfe0 --> 0x0
addr of ret_addr:
|
|
**Caclulate the Overflow: ** expr $((0xffffd07c)) - $((0xffffcfe0)) ==> 156
Custom Shellcode!! Now for this lab, the ptrace in the executable prevented the execve syscall, so I got to experiment with writing/compiling my own shell code! I included the c program I used for testing as well!
|
|
Testing c code: Sometimes one way testing works and the other doesn’t so I have both!
|
|
The above string of text was compiled following the steps above(also borrowed from the MBE course slides [like in my last lab](https://security.cs.rpi.edu/courseshat just passing the shellcode payload by itself does not keep stdin open, and the program will close. (<a href=)). Adding the & cat does the trick!/binexp-spring2015/lectures/7/05_lecture.pdf">slides || local copy)
final payload
|
|
At first, I thought my shellcode wasn’t printing due to my address being wrong (I used strace -f to make sure the system calls were running), but I later realized the shellcode runs in the child! This means writing to stdin does not automatically display the dump in the actual execution. I got it to display in gdb though!!
Lab3A
|
|
The trick with this code is manipulating the store_number function! The index is not checked which enables arbitrary reads/writes to anywhere in the stack after the start of the data array!!
However, there’s a trick on line 25 in the store_number()
. This line makes every 3rd index unwritable and the msb of each index not be 0xb7.
Every 8 bytes of code will need a jmp condition to skip the next four bytes:
[_ _ _ _][_ _ _ _][ BAD][_ _ _ _][_ _ _ _][ BAD ]...
Find the address of data
[-------------------------------------code-------------------------------------]
0x8048b5e <main+332>: jne 0x8048b75 <main+355>
0x8048b60 <main+334>: lea eax,[esp+0x18]
0x8048b64 <main+338>: mov DWORD PTR [esp],eax
=> 0x8048b67 <main+341>: call 0x8048917 <store_number>
0x8048b6c <main+346>: mov DWORD PTR [esp+0x1bc],eax
0x8048b73 <main+353>: jmp 0x8048bd2 <main+448>
0x8048b75 <main+355>: mov DWORD PTR [esp+0x8],0x4
0x8048b7d <main+363>: mov DWORD PTR [esp+0x4],0x8048f63
Guessed arguments:
arg[0]: 0xffffcec8 --> 0x0
[------------------------------------stack-------------------------------------]
0000| 0xffffceb0 --> 0xffffcec8 --> 0x0
Find the return address
[-------------------------------------code-------------------------------------]
0x8048c38 <main+550>: pop ebx
0x8048c39 <main+551>: pop edi
0x8048c3a <main+552>: pop ebp
=> 0x8048c3b <main+553>: ret
0x8048c3c: xchg ax,ax
0x8048c3e: xchg ax,ax
0x8048c40 <__libc_csu_init>: push ebp
0x8048c41 <__libc_csu_init+1>: push edi
[------------------------------------stack-------------------------------------]
0000| 0xffffd07c --> 0xf7defe81 (<__libc_start_main+241>: add esp,0x10)
This results in an overflow needed:
However this is just the number of straight bytes. Since the arbitrary write function store_number
allows writing ints (every 4 bytes), the write needs to be at overflow/4 = 109th index!
I also learned a fun lesson about python2, it prints out hexcharacters in big endian!! (This proved a little troublesome, and is good to note as peda also has this quirk)
The next step was to write the shellcode in 4 byte chunks with jumps so it could be properly executed in the buffer! jmp 4 bytes
Snoob (shell noob) came in very handy here and helped with looking up syscall numbers and checking the length of specific shellcode!!
The last part was figuring out how to interact with a process using pwntools (VERY USEFUL!!). Pwntools can spawn a process, and uses its own internal libary tubes to create read/write pipes to a process. The context allows the user to control assembly of specific architectures and also turn debugging on (to see read/writes to process pipes). Attaching gdb to my process was also super helpful in debugging quite a few issues in my shell code!
I ran into a very weird issue where edx was filled with an old address before calling execve, so I added an xor line to clear it. Between all the old shellcode from lab3C nops and jmp $+2+4 were added as necessary. The asm()
function is pwntools compiler wrapper, that compiles based on the context set in the context()
function. u32()
unpacks the compiled hexstring shellcode into a 32bit int that is stored in a dict before being sent to the executable process.
I added a function to print out my shellcode to test it in the shellcode.c file to be sure it dropped me a shell. Finally, I attach gdb to the process while testing the sending of lines to the executable.
|
|
Finally I got a shell and printed the password!!