AVSS Contest 24 Qualifier Write-Up

Introduction of AVSS Contest


This series of challenges is designed to help players learn the basic knowledge and bypass methods of MTE. The total four challenges share the same functionality and vulnerability, with different environment setups and heap allocators. BBOBB and BBOPT are equipped with PTMalloc, where BB does not enable MTE. BBOPA and BBOSC are equipped with PartitionAlloc and Scudo respectively. For the characteristics and weaknesses of MTE in each heap allocator, you can refer to our blog post.

The bug is apparent: the offset in edit does not have strict boundary checks (i.e., negative index). Therefore, we can modify the control data (size, ptr, etc.) of the heap block to gain OOB write on the heap.


The first challenge is a typical glibc heap challenge without MTE in 2.35. The only point is how to exploit it without hooks. You can refer to angry-FSROP or other materials for help.


Since MTE is enabled, we cannot simply achieve arbitrary OOB write on the heap because the heap ptr is tagged. However, we can edit the tag of the data ptr to 0, so as to write data to memory with 0 tag.

As for how to control PC, the intended solution is to edit the freed heap chunk (which is tagged 0 in glibc). Then, we can use large bin attack to modify IOlistall to our fake FILE and trigger file stream attack. However, there is an unintended circumstance: the distance between heap and libc is so close that we can directly OOB write libc :upsidedownface:.


As PartitionAlloc is able to leak the tag of the heap chunk, we can simply make two heap chunks have the same tag and OOB write data ptr to achieve arbitrary write.


We think there is a rare possibility to solve this problem under the current challenge setting. If you have more insights, please feel free to discuss with us.

Vehicle: BLE


The first challenge is designed to help players learn and complete BLE connections and interactions.

The CBC encryption key is calculated from the broadcast manufacturer and VIN code, which can obviously be obtained from the traffic packet, and also can decrypt the DKid and Phoneid from the traffic packet.

What players need to do is to obtain the manufacturer in the broadcast and calculate the encryption key of the CBC and re-encrypt the plaintext dkid、phoneid and other information before sending.

The main problem that many players meet is that they do not enable the notify, and to open the notify requires writing "\x01\x00" to the corresponding channel


This challenge is similar to the previous challenge, but simulates a more complete vehicle BLE verification process. After step 1, players need to send the app certificate, after the certificate is verified, the two sides carry out ECDH key exchange, and finally use the exchanged key to encrypt the digital key. The problem is that the certificate verification process uses the sender's certificate public key, so players can complete the challenge by forging the certificate. Another point is that players need to use a longer key for RSA signature.


This challenge is the closest to the actual situation, and two schemes are tried. The first is to use the public and private key of the certificate of another owner to complete the interaction, the problem is that the last step will check the digest of the certificate of the owner; the second is that there is a risk of iv reuse in AES-GCM encryption. We can obtain the certificate of the original owner and the ECDH data. Now our only problem is that we cannot know the exchanged key, but the iv reuse problem allows us to encrypt the plaintext data of the same length when we know multiple ciphertext pairs. The actual problem is that the returned data length is too short. So We don't have an expected solution.

Vehicle: AAOS


This challenge is designed to help players to be familiar with the development of Automotive. The main difficulty is the obscure document on the Internet and the ability to get the system application permission.

For the former point, challengers can spend time and do some research on it, there are many samples and blogs to make an HVAC application in the Automotive systems, just make a project and try their resolutions.

And when you try to change the air condition temperature set, the Android Studio tells you that your project needs CLIMATE permission, but u cannot be granted CLIMATE permission by just declaring it in the manifest, because it is a system permission that can only be granted to the system application. Then things become to how to get the system application permission in Automotive.

For AAOS_G, we compile the Automotive from AOSP without any modifications, which means we use the test-keys to sign our system. Players can get the test-keys from AOSP, and use test-keys to sign their applications, the applications signed by test-keys will be granted system application permission, and now the application can be granted the CLIMATE permissions successfully.


For AAOSP and AAOSV, we use the production image from the vendor, which means they use the private key to sign the system, we cannot create a system application with previous methods. Although we don't have an expected solution for those two challenges, these systems are based on Android 9, some enclosed vulnerabilities may be exploited on these systems.

Vehicle: FORMULA

The Vehicle: FORMULA is a series of challenges about vehicle security based on real-world devices. Players will connect to the components of the real car.

We know that sometimes cars need to be diagnosed or check the status of ECUs(Electronic Control Unit). It's necessary for vendors to place an engineering mode entry in the center controller of the car, which could do some private operations, such as turning on the debug mode or enabling debug interfaces. But if attackers access the mode, it may compromise the confidentiality, integrity and accessibility of the device and attackers could misguide the autopilot or even shut down the device while driving, so this mode should not allow unauthorized access. This series shows how vehicles provide and protect the powerful engineering mode.

There are three versions of authentication methods of engineering mode.

In this series, we provide a remote real-world device environment for players and players could connect to the infotainment system from the internet. Here is the Network Topology.


In this challenge, the authentication is implemented in a pre-installed APP. The APP registered a secret code broadcast receiver. After you pass the UUID auth of the remote connection, you can send a secret code broadcast, just like you input the code from the dialing panel. And you will get the screenshot of the device.

The secret code will be handled by the APP.

In the com.test.cardevsettings.SECRETCODECLASS , we can see that it has two types of secret code. The first one is to get the challenge, and the second one is to do verification.

In the native, we could find the logic of the Hardware ID generated by the VIN(Vehicle Identification Number), and the VIN is different for each car.

After you get the VIN, you can analyze the verification login. It uses an HMAC with a hardcode key to generate the key of the stream cipher, decrypt the secret code, and check the time. So you need to calculate a correct verification code based on the current time.


VEE is based on a more modern car than KARTING. It cancels the engineering mode from the dialing panel and implements a DoIP server on its vehicle ethernet which is exposed as an OBD interface.

In the VEE challenge, we connect the vehicle ethernet and forward the port of the DoIP server to players so you can just use UDS protocol to communicate with the device.

In UDS protocol, has several security levels. Only by passing the authentication, we can the privileged operations, such as some sensitive identifiers reading or writing.

To get the flag of VEE, you need to set the value of the identifier d09a. However, the access d09a is required for security level 3 and session 3.

After the DoIP server receives the Security Access request, it will generate 3 random bytes as the challenge and give them to the challenger. You need to calculate the response of these 3 bytes by the specific algorithm. In this case, the algorithm is the standard UDS security access algorithm which has 5 bytes secret key.

You can locate the seed generation and verification in the DoIP server, but each security level has different verification implementation. The authentication of level 3 uses a fixed key and you can do the same thing to get the 3-bytes response.

After you get a successful reply from the device, just read and write the identifier.

Put them together, you need:


ONE has a similar environment and the same goal as VEE. It also requires players to change the identifier d09a from 0 to 1.

But the key of authentication is not hardcode. The key is randomly generated and unique for each device and stored in TEE.

And the authentication has an anti bruteforce mechanism, if an invalid security access response is received, it needs about 10 seconds for the next try.

Just for indication, but not the same security level

So we think it is difficult to collide these 3-byte response values during the game and we don't have an expected solution for ONE. But it may have some memory corruption in the protocol stack.


This challenge is about exploiting different privilege escalation vulnerabilities across different Android kernel versions. The challenge adds a syscall with two vulnerabilities and provides the patch file, requiring participants to escalate privileges to root from the adb shell in the emulator.

There are 4 functions in the syscall, namely, ADD, DELETE, EDIT, and RUN. ADD will allocate an inst object with a function pointer in kmalloc-512 slab and put it on the global list. DELETE will remove an object from the list. EDIT will set the property of an object with the provided variable-length operands. RUN will traverse the list, execute the function pointer of the object, and then remove the whole list.

This challenge provides a backdoor function that will never be called. If you can call it in the kernel context, you can easily get to root.

clike noinline __int128 do_backdoor(__int128 input, __int128 *operands, unsigned long operand_num) { struct cred *cred; int ret; cred = prepare_kernel_cred(NULL); if (!cred) { return -1; } ret = commit_creds(cred); if (ret) { return -1; } selinux_enforcing = 0; return 0; }

The first vulnerability is an overflow in EDIT, which does not check the operand_num properly. The size of the inst object is 40 and MEM_SIZE is 512. round_up(472,16)equals 480, leading to an 8-byte-overflow in kmalloc-512.

clike if(_inst.operand_num > round_up(MEM_SIZE-sizeof(struct inst), sizeof(__int128))/sizeof(__int128))

The second vulnerability is a use-after-free in RUN. After executing all the function pointers, it will kfree all the objects in the list, but it does not clear inst_head. With EDIT, we can edit anything in a freed kmalloc-512 object, except the first 40 bytes.

clike list_for_each_entry_safe(curr, ptr, &inst_head->list, list) { list_del(&curr->list); kfree(curr); } kfree(inst_head); return 0;

KASLR is not enabled in the emulator, so no kernel address leak is needed.

Android 7

The kernel version of Android 7 is 3.10.0 and there is hardly any protection. Slab objects are allocated continuously in the memory, so 8-byte-overflow can overwrite the function pointer of the next inst object.

We can just use the overflow vulnerability to hijack the function pointer into the backdoor function and then use RUN to get to root.

Android 11

The kernel version of Android 11 is 5.4.50, which enables CONFIG_HARDENED_USERCOPY. This config will check the target object is fully in the slab object when calling copy_*_user. So the overflow vulnerability is no longer exploitable any more.

This version also enables kCFI protection, the backdoor function cannot be easily called by hijacking a function pointer.

To exploit the UAF vulnerability, we can spray some objects of kmalloc-512 to take the place of the UAF object and use the vulnerability to modify the data inside it.

pipe_buffer is a good object :

  1. We can useF_SETPIPE_SZ option of the fcntl to adjust pipe_buffer to be allocated in kmalloc-512
  2. Thepagevariable in pipe_buffer can be utilized to achieve arbitrary read/write in physical memory.

Finally, we can traverse the task_list to find the current task and set its uid, gid, etc. to zero. selinux_enforing should also be cleared to disable SELinux.

Android 13

The kernel version of Android 11 is 5.15.78, which adds a new set of kmalloc-cg-<n> (KMALLOC_CGROUP) caches for accounted objects only. The inst objects are allocated in normal caches, while pipe_buffer objects are allocated in accounted objects. They are separated, thus spraying pipe_buffer cannot easily reuse the UAF object.

We can use the cross-cache attack to solve the problem. The main idea is to change the object UAF into slab UAF:

  1. Release all objects located in the slab(kmalloc-512) containing the UAF object.
  2. Return the slab into the buddy system.
  3. Spraying pipe_buffer to obtain it as akmalloc-cg-512 slab.
  4. Exploiting the UAF vulnerability in the same way as in Android 11.


This challenge is based on the latest version (for Java 1.8) of the default Grails generated project and demonstrates the differences in exploiting the classic log4shell vulnerability across different versions of the Java runtime environment.

All four versions of the challenge have solutions! I hope to see everyone's creativity in finding different chains of exploitation, which is why I specifically used the default project and did not remove any dependencies unrelated to my own solution.

Java 1.8, 11, 17 and 21 are all LTS versions, so they are chosen.

v1.8 & v11

Both ldap and rmi can be used. Java module system and encapsulated packages protect was not introduced.

Some security researchers have found and shared many useful deserialization chains for Java 1.8. In this challenge, the most commonly used for v1.8 is Jackson as source and TemplatesImpl as sink. I think it is the obvious one since Grails depends on Spring Boot.

Note 1:

Tomcat's BeanFactory can't be used in this challenge because they removed forceString in 9.0.63.

Note 2:

Since 8u291, two vm properties are added: jdk.jndi.object.factoriesFilter and com.sun.jndi.ldap.object.trustSerialData. The latter property is default to true.

Since 8u311, the scope of com.sun.jndi.ldap.object.trustSerialData is extended to javaReferenceAddress.

Note 3:

Java module system is introduced in Java 9, but strong encapsulation is relaxed by default due to migration, which means --illegal-access is default to permit.


Two main differences: the first is that you can't use TemplatesImpl as the sink for the deserialization chain, and the second is that you can't use BadAttributeValueExpException to trigger Jackson's toString.

There are mainly two solutions: the first one is to utilize DataSourceFactory to trigger DataManager.getConnection, achieving RCE through the H2 JDBC Driver. The second one is to use GenericNamingResourcesFactory and SystemUtils to modify the org.apache.commons.collections.enableUnsafeSerialization property, and then RCE using the CC chain.

Note 1:

--illegal-access is defaulted to deny on Java 16 and is removed on Java 17.

Note 2:

The readObject() method of BadAttributeValueExpException no longer calls toString(). It is patched by this commit (since Java 15?).


Since Java 20, there are two patches related to JNDI and deserialization vulnerability:

  1. JDK-8290367:Default value of com.sun.jndi.ldap.object.trustSerialData is changed to false.
  2. JDK-8290368:Add default ObjectFactoriesFilter for LDAP and RMI lookup in JNDI.

So, not only deserialization through JNDI lookup, but also Reference with ObjectFactory through JNDI or RMI, is unavailable for exploitation by default. There is only one viable path left, that is RMI lookup and deserialization gadgets.

My solution is hibernate → ExecutorTrackedRunnable → JFormattedTextField$FocusLostHandler → JFormattedTextField → DefaultFormatter → ClassPathXmlApplicationContext. If you use this chain, it is notable that serialVersionUID of some classes are different between Java 1.8 and Java 21. You can patch the serialVersionUID manually or run your tool on Java 21 with --add-opens. I think too many --add-opens are annoying for exploitation, so I write a simple library magic.jms, maybe helpful.

I believe other usable chains may exist as well for this challenge, and someone may find a more novel one.