There should be D3CTF 2020 last year, but it was cancelled for some reasons.
At that time I was conceiving a challenge about kernel pwn and integer overflow, which originated from CVE-2017-16995, a eBPF-related vulnerability. I designed a VM inside the vulnerable kernel module, and due to the symbol asymmetry when comparing operands, it led to cross-border access.
But it was to hard to be debugged, as well as the exploit… And the most important thing is, the challenge itself is not novel enough, which is contrary to the theme of this competition.
The cancellation of D3CTF 2020 is very regrettable, however, it gave me a chance to calm down, and learn more things to conceived better questions.
Upcoming D3CTF 2021
Thanks to the support from Ant Security and the cooperation of Vidar-team, L-team and CNSS, AntCTF x D^3CTF 2021 will arise in March, 2021. And I think it’s time for me to settle down the challenges in the competition.
I decided to come up some challenges based on the points listed below:
In this challenge, the functions were in seperate sections. We couldn’t know the real function offset because each section was loaded into memory independently every time we start the kernel, which means that it is impossible for us to find gadgets in static environment. The official write-up told us that we can use .symtab section, a symbol table used to store information about functions and global variables that are defined and referenced in the program, to get the exact function addresss.
After solving the challenge, I was wondering if there is any other way to reach the root shell instead of using .symtab section to caculate the real offsets of the functions needed. And I notice that if memory leak vulnerability exists, we can use JIT-ROP, or some tricks like that, to find the real gadgets’ address.
php extension and php UAF
Months ago, my teammates @ccdragon and @eniv helped me to debug a php uaf exploit, and I learned a lot php debug skills from that.
As there are few challenges about php extension in ctf-pwn, I decided to construct a vulnerability in php extension. It’s going to be a attractive challenge, I think.
Honestly, I am interested in this kind of problem, but I don’t have any new ideas about qemu escape challenge.
A problem from defcon 2019 attracted me a lot. You can refer to this blog to explore more.
It’s a novel and CPU-level attack, and our goal is to release the lock to escape the transaction, and finally execute our shellcode.
But the thing is, as the blog mentioned above states, due to the unique scenario modeled by this CTF challenge, it is unlikely that this issue poses a risk to real-world applications, and what’s more, the uniqueness of this attack method also determines that it is difficult to construct new attack ideas.
After the game and Writeups
Every single member of us played an important role in this game, which made the competition an outstanding and widely acclaimed one. But in the process of the game, there were many problems in the deployment environment of the challenge and the challenge itself, which caused a lot of troubles for the participating teams. Sometimes we got unexpected solutions, and those are much different from the solutions we expected…
After collecting the writeups of the top ranked teams, I carefully checked them and learned a lot of new ideas. At the same time, I realized the shortcomings of the challenges. Here are my opinions about several challenges.
An easy php pwn challenge. This challenge is designed for challengers to use the vulnerability in php extension to execute arbitrary code.
Check hackphp.so carefully, and we can find the functions listed below:
hackphp_create, creates a buffer with the length of size, and stores the buffer pointer in buf
hackphp_delete, use efree to release buf and reset it to null
hackphp_edit, edit the buffer memory pointed by buf
hackphp_get, get the content of memory pointed by buf
And here we get the vulnerability in hackphp_create:
If we try to create a buffer with size < 0x100 || size > 0x200, then efree(buf) will be execute but buf will not be resetted to null, which caused a traditional UAF problem.
And now it’s pretty easy, because they (objects defined in .php source and buf) share the same heap memory, we can use the vulnerability to make an object and a buffer been allocated at the same place (make sure that the size of the object and the buffer should be the same). At last we can reach /readflag by type-confusion.
Type-confusion in PHP
How should we use type-confusion in PHP? Let’s check it out.
/* fake types used only for type hinting (Z_TYPE(zv) can not use them) */ #define _IS_BOOL 16 #define IS_CALLABLE 17 #define IS_ITERABLE 18 #define IS_VOID 19 #define _IS_NUMBER 20 ...
We can see that the type of $obj is object. Let’s look deeper into $obj:
The contents from 0x7ffff587d028 to 0x7ffff587d060 are the properties of $obj. And we should notice that 0x7ffff585c500 is the pointer to $obj->b, which is a Anonymous function, and in PHP kernel, Anonymous function is represented by zend_closure:
In order to execute malicious function, we should replace internal_function to the address of the function. At the same time, we should ensure the integrity of zend_closure, otherwise the program will crash.
Because of the UAF problem, we can control the data from 0x7ffff587d000 to 0x7ffff587d070 which leads to a fake object generated. And if we can forge a zend_closure structure, we almost complete the challenge.
The use of hackphp.so makes challengers able to modify .got to hijack function, but considering a more common circumstance, I would like to share a way to execute /readflag by forging fake closure.
The idea is very similar to this exp, some template functions can be used directly, but the question is only specific size of the buffer can be controlled in this challenge, and it is impossible to read and write to the next heap block out of boundary. This brings us trouble in constructing zend_closure.
The solution is to apply for a string type object again (the length must be 0x130 to hold the zend_closure content), which is filled with characteristic values, and then the address of the object is found through the constructed leak primitive, and then a closure can be faked, and finally Use UAF to modify the closure to the forged address.
After FG-KASLR is turned on, it will cause vmlinux and the corresponding kernel module to be segmented by function, and then the function loading order will be disrupted on the basis of the original address randomization, and the offset of the function on the basis of vmlinux_base_addr cannot be determined by static analysis.
The vulnerability is in the cast_a_spell function:
v1 is the parameter passed in. If v1 > 0x100, it will cause a stack overflow. This overflow will overwrite the ptr and size variables. The read and write functions rely on the ptr and size stored in global_buffer. It is equivalent to the opportunity for us to read and write at will.
In the original question of hxpctf, the real offset of the function is obtained by reading the structure of the .symtab section, and the read function in this challenge limits the scope of reading:
This shows that there is no way to get the true offset of the function by reading .symtab.
Another approach (expected solution) is to use fake JIT-ROP. Read the code segment through the memory leak vulnerability in this challenge to obtain the gagets we need, and then use the stack overflow to construct the ROP chains.
During the competition, some teams solved the challenge by modeprobe_path or by brute force search for cred structure on heap space.
Thanks to r3kapig, their member helped to find a unexpected solution which was fixed later. And there would be a big impact without the reminder from them.