By using the so called universal gadget from __libc_csu_init
we can read shellcode into the rwx memory segment and return into it.
By executing read function we can overwrite only last two bytes of read to find something useful and defeat ASLR. Fortunately there is one-gadget RCE located at 0xf0567
in this version of libc, right near the read
function (0xf6670). We overflow only last two bytes to defeat ASLR, so that only around 16 attemps needed, because of 4 bit entropy of ASLR.
EDIT: checkout another great solution proposed by agadeint in the comment section below, which is cleaner and does not require bruteforcing and one gadget.
Stack looks like this:
...
char buf[100];
char *buf_;
gets function is used to fill the buf
, so it's easy to overflow buf_
pointer, which is later passed into count_crc
function, so we can count crc almost of any memory region, thus we can get arbitrary read. We can get base address of libc from .got, and also we can get canary, because it's located in the memory segment right after the ld.so, which we can get by using kind of offset2ldso attack (though it is not stable and platform specific).
We can underflow the fu
pointer which in located in .bss section and make it point into the .got section, where we can read and write. I address of libc from got, then changed strlen@got
to address of system@libc
and puts@got
into the address of main function, so after restart of the binary system
function will be called instead of strlen
of string controlled by me.
First of all canary can be leaked by entering numbers 1 .. 7 into the menu and the last byte is always zero. Also in this binary couple of very useful gadgets like syscall; ret
can be found. So by using these gadgets and technic called SROP we can leak .got section, read our new ropchain and then return on it by using sigreturn.
In this task we can read any file, so we can read /proc/self/maps
to defeat ASLR and by abusing double free into fast bin attack we can overflow __malloc_hook
by address of system and call malloc(addr_of_binsh_on_heap)
, so system("/bin/sh")
will be called.
In this task we can not read any file on the system, but we can get memory leak of heap by reading contents of fastbin chunk, and we can get memory leak of libc arena by reading contents of small chunk. Then I overflow __malloc_hook
once again into the one-gadget RCE and trigger double free error, which will trigger our hook. This is done exactly that way, because there is not such one-gadget that meets contrains if you simply call malloc, and you can not do the trick with addr_of_binsh_on_heap
like the last time, because of the PIE address of the heap is now more that 2 ** 32
and we could only call malloc(unsigned int size)
.
defaulter says he accepts a shellcode as input. But obviously was some trick in this task. First I tried to call execve("/bin/sh", NULL, NULL)
shellcode, just to be sure that it blocked by seccomp sandbox or some sort of it (and it didn't work). So I tried testing over syscalls and I jumped into the inf. loop if syscall fails. open
syscall was blocked as well, but openat
was not, but openat
needs absolute path to file as argument so I read the /etc/passwd
to get username (which was pwn) and the read /home/pwn/flag
. So full path looks like this: openat flag, read the flag into writable location, write it to the screen.
Hello, @agadeint! Thank you for commenting!
The idea of overwriting read with syscall is absolutely beautiful, and to be real I like it even more than my own exploit since it does not use one-gadget RCE which are not stable, because of constraints, and also it does not require bruteforcing. Beautiful!
Alas, for me your exploit did not work as well. I suppose that when you launched exploit remotely your payload, which length is around 2048 bytes, was fragmented into different packets of length ~1500 bytes, because of the MTU. So the first
read(0, buffer, 1024);
consumed 1024 bytes and the second read did not wait for the second packet being received by network and consumed rest of 1500 - 1024 bytes.The read is not blocking function, so you don't need to pad exactly 1024 bytes in your exploit. Summarizing all that knowledge, this issue can be solved by simply adding
sleep(1)
between sending your different payloads to ensure they will not be consumed by the same read function.However, I would like to thank you for a great solution. Here is my exploit
for this task using technic you have described.