owned this note
owned this note
Published
Linked with GitHub
# lkgit from TSGCTF2021
# stats
![](https://i.imgur.com/qUozYU2.png)
- estimated difficulty: medium
- genre: kernel
- author: [@smallkirby](https://twitter.com/smallkirby)
- points: 322pts (7 solves)
# vuln: race condition into kernbase leak
`lkgit_get_objects()` copies data from/to userland, which can cause fault and be handled by `userfaultfd` handler in userland.
```lkgit.c
static long lkgit_get_object(log_object *req) {
long ret = -LKGIT_ERR_OBJECT_NOTFOUND;
char hash_other[HASH_SIZE] = {0};
char hash[HASH_SIZE];
int target_ix;
hash_object *target;
if (copy_from_user(hash, req->hash, HASH_SIZE)) // ...1
goto end;
if ((target_ix = find_by_hash(hash)) != -1) {
target = objects[target_ix]; ...★1
if (copy_to_user(req->content, target->content, FILE_MAXSZ)) // ...2
goto end;
// validity check of hash
get_hash(target->content, hash_other);
if (memcmp(hash, hash_other, HASH_SIZE) != 0)
goto end;
if (copy_to_user(req->message, target->message, MESSAGE_MAXSZ)) // ...3
goto end;
if (copy_to_user(req->hash, target->hash, HASH_SIZE)) // ...4
goto end;
ret = 0;
}
end:
return ret;
}
```
If you create new commit with a same hash during this halt, the `hash_object` structure in `target` var can be `kfree()`ed, which means kUAF(read) is achieved.
The key point for this challenge is to handle the fault timing appropriately.
In `lkgit_get_object()` function, there are 4 copies. But the only third copy is useful to leak kernbase. Other copies would fail due to validity check of hash, or would be just useless for leaking.
To cause fault only in the third copy, you have to place your `hash_object` in userland on the edge of two pages.
![](https://i.imgur.com/upMBVPh.jpg)
# same vuln for AAW
`hash_object` structure and `hash_object->message` have same size `0x20`. In `lkgit_amend_message()`, you can invoke fault in the same way with leak. During this halt, you can `kfree()` the object and create new hash object. The order of allocating new hash object is below:
```lkgit.c
char *content_buf = kzalloc(FILE_MAXSZ, GFP_KERNEL);
char *message_buf = kzalloc(MESSAGE_MAXSZ, GFP_KERNEL);
hash_object *req = kzalloc(sizeof(hash_object), GFP_KERNEL);
```
So if you `kfree()` the hash object and allocate new one right after it, you can get the hash object as `message_buf`, then you can write any value in it. After the fault, `target` var in `lkgit_amend_message()` is no longer the original one, but a newly created one.
```lkgit.c
if ((target_ix = find_by_hash(req.hash)) != -1) {
target = objects[target_ix];
// save message temporarily
if (copy_from_user(buf, reqptr->message, MESSAGE_MAXSZ))
goto end;
// return old information of object
ret = lkgit_get_object(reqptr);
// amend message
memcpy(target->message, buf, MESSAGE_MAXSZ);
}
```
This `target->message` is expected to have the pointer into message buffer, but now it is overwritten with arbitrary value during halt, for example `modprobe_path`. It means that you achieved AAW. You can use so-typical `modprobe_path` technique to change permission of `/home/user/flag`.
# Full exploit
Please refer to the [gist](https://gist.github.com/smallkirby/50fc3a0a7c275bf654f66ca5d226485f), or TSGCTF2021 repository(**TBD**).
# Nirugiri
For Japanese readers and masters of Google Translate, please refer to below blog post for the detailed description:
https://smallkirby.hatenablog.com/entry/2021/10/03/171804
# Community Writeups
There are some community writeups and are listed in above blog entry.
Especially, the one by `kileak` from `Super Guesser` is really detailed.
https://kileak.github.io/ctf/2021/tsg-lkgit/