基础知识

学习网址:ctfwiki 安全客

Kernel:又称核心

维基百科:在计算机科学中是一个用来管理软件发出的数据I/O(输入与输出)要求的电脑程序,将这些要求转译为数据处理的指令并交由中央处理器(CPU)及电脑中其他电子组件进行处理,是现代操作系统中最基本的部分。它是为众多应用程序提供对计算机硬件的安全访问的一部分软件,这种访问是有限的,并由内核决定一个程序在什么时候对某部分硬件操作多长时间。直接对硬件操作是非常复杂的。所以内核通常提供一种硬件抽象的方法,来完成这些操作。有了这个,通过进程间通信机制及系统调用,应用进程可间接控制所需的硬件资源(特别是处理器及IO设备)。

严格地说,内核并不是计算机系统中必要的组成部分。有些程序可以直接地被调入计算机中执行;这样的设计,说明了设计者不希望提供任何硬件抽象和操作系统的支持;它常见于早期计算机系统的设计中。但随着电脑技术的发展,最终,一些辅助性程序,例如程序加载器和调试器,被设计到机器内核当中,或者写入在只读记忆体里。这些变化发生时,操作系统内核的概念就渐渐明晰起来了!



kernel 最主要的功能有两点:

  1. 控制并与硬件进行交互
  2. 提供 application 能运行的环境

权限

intel CPU 将 CPU 的特权级别分为 4 个级别:Ring 0, Ring 1, Ring 2, Ring 3

Ring0 只给 OS 使用,Ring 3 所有程序都可以使用,内层 Ring 可以随便使用外层 Ring 的资源。

Loadable Kernel Modules(LKMs)

LKMs 的文件格式和用户态的可执行程序相同,Linux 下为 ELF,Windows 下为 exe/dll,mac 下为 MACH-O,因此我们可以用 IDA 等工具来分析内核模块。

相关指令

insmod: 讲指定模块加载到内核中
rmmod: 从内核中卸载指定模块
lsmod: 列出已经加载的模块
modprobe: 添加或删除模块,modprobe 在加载模块时会查找依赖关系

syscall

系统调用,指的是用户空间的程序向操作系统内核请求需要更高权限的服务,比如 IO 操作或者进程间通信。系统调用提供用户程序与操作系统间的接口,部分库函数(如 scanf,puts 等 IO 相关的函数实际上是对系统调用的封装 (read 和 write))。

/usr/include/x86_64-linux-gnu/asm/unistd_64.h/usr/include/x86_64-linux-gnu/asm/unistd_32.h 分别可以查看 64 位和 32 位的系统调用号。

ioctl

#include <sys/ioctl.h>

       int ioctl(int fd, unsigned long request, ...);

ioctl 也是一个系统调用,用于与设备通信。

int ioctl(int fd, unsigned long request, ...) 的第一个参数为打开设备 (open) 返回的 文件描述符,第二个参数为用户程序对设备的控制命令,再后边的参数则是一些补充参数,与设备有关。

状态切换

user space to kernel space

当发生 系统调用产生异常外设产生中断等事件时,会发生用户态到内核态的切换,具体的过程为:

  1. 通过 swapgs 切换 GS 段寄存器,将 GS 寄存器值和一个特定位置的值进行交换,目的是保存 GS 值,同时将该位置的值作为内核执行时的 GS 值使用。
  2. 将当前栈顶(用户空间栈顶)记录在 CPU 独占变量区域里,将 CPU 独占区域里记录的内核栈顶放入 rsp/esp。
  3. 通过 push 保存各寄存器值,具体的 代码 如下:
 ENTRY(entry_SYSCALL_64)
/* SWAPGS_UNSAFE_STACK是一个宏,x86直接定义为swapgs指令 */
SWAPGS_UNSAFE_STACK /* 保存栈值,并设置内核栈 */
movq %rsp, PER_CPU_VAR(rsp_scratch)
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp /* 通过push保存寄存器值,形成一个pt_regs结构 */
/* Construct struct pt_regs on stack */
pushq $__USER_DS /* pt_regs->ss */
pushq PER_CPU_VAR(rsp_scratch) /* pt_regs->sp */
pushq %r11 /* pt_regs->flags */
pushq $__USER_CS /* pt_regs->cs */
pushq %rcx /* pt_regs->ip */
pushq %rax /* pt_regs->orig_ax */
pushq %rdi /* pt_regs->di */
pushq %rsi /* pt_regs->si */
pushq %rdx /* pt_regs->dx */
pushq %rcx tuichu /* pt_regs->cx */
pushq $-ENOSYS /* pt_regs->ax */
pushq %r8 /* pt_regs->r8 */
pushq %r9 /* pt_regs->r9 */
pushq %r10 /* pt_regs->r10 */
pushq %r11 /* pt_regs->r11 */
sub $(6*8), %rsp /* pt_regs->bp, bx, r12-15 not saved */
  1. 通过汇编指令判断是否为 x32_abi
  2. 通过系统调用号,跳到全局变量 sys_call_table 相应位置继续执行系统调用。

kernel space to user space

退出时,流程如下:

  1. 通过 swapgs 恢复 GS 值
  2. 通过 sysretq 或者 iretq 恢复到用户控件继续执行。如果使用 iretq 还需要给出用户空间的一些信息(CS, eflags/rflags, esp/rsp 等)

struct cred

cred 结构体记录的是关于kernel进程的权限,每个进程中都有一个 cred 结构,这个结构保存了该进程的权限等信息(uid,gid 等),如果能修改某个进程的 cred,那么也就修改了这个进程的权限。

源码 如下:

struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
} __randomize_layout;

主要提权手法:

commit_creds(prepare_kernel_cred(0));

关于perpare_kernel_cred函数的定义如下:

/**
* prepare_kernel_cred - Prepare a set of credentials for a kernel service
* @daemon: A userspace daemon to be used as a reference
*
* Prepare a set of credentials for a kernel service. This can then be used to
* override a task's own credentials so that work can be done on behalf of that
* task that requires a different subjective context.
*
* @daemon is used to provide a base for the security record, but can be NULL.
* If @daemon is supplied, then the security data will be derived from that;
* otherwise they'll be set to 0 and no groups, full capabilities and no keys.
*
* The caller may change these controls afterwards if desired.
*
* Returns the new credentials or NULL if out of memory.
*
* Does not take, and does not return holding current->cred_replace_mutex.
*/
struct cred *prepare_kernel_cred(struct task_struct *daemon)
{
const struct cred *old;
struct cred *new; new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
if (!new)
return NULL; kdebug("prepare_kernel_cred() alloc %p", new); if (daemon)
old = get_task_cred(daemon);
else
old = get_cred(&init_cred); validate_creds(old); *new = *old;
atomic_set(&new->usage, 1);
set_cred_subscribers(new, 0);
get_uid(new->user);
get_user_ns(new->user_ns);
get_group_info(new->group_info); #ifdef CONFIG_KEYS
new->session_keyring = NULL;
new->process_keyring = NULL;
new->thread_keyring = NULL;
new->request_key_auth = NULL;
new->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
#endif #ifdef CONFIG_SECURITY
new->security = NULL;
#endif
if (security_prepare_creds(new, old, GFP_KERNEL) < 0)
goto error; put_cred(old);
validate_creds(new);
return new; error:
put_cred(new);
put_cred(old);
return NULL;
}

注释中已经把函数功能描述得很具体了,简单来说,这个函数主要是生成一个cred结构体,主要根据传入的参数struct task_struct *daemon来确定一些内核服务的credentials,以便于给当前task提供在特定的context执行的权限。

在参数为NULL的情况下,也其实就是理解为把0号进程的task_struct作为参数的情况下,返回一个相应的cred结构体,这个结构体具有最高的root权限。

commit_creds函数定义为:

/**
* commit_creds - Install new credentials upon the current task
* @new: The credentials to be assigned
*
* Install a new set of credentials to the current task, using RCU to replace
* the old set. Both the objective and the subjective credentials pointers are
* updated. This function may not be called if the subjective credentials are
* in an overridden state.
*
* This function eats the caller's reference to the new credentials.
*
* Always returns 0 thus allowing this function to be tail-called at the end
* of, say, sys_setgid().
*/
int commit_creds(struct cred *new)
{
struct task_struct *task = current;
const struct cred *old = task->real_cred; kdebug("commit_creds(%p{%d,%d})", new,
atomic_read(&new->usage),
read_cred_subscribers(new)); BUG_ON(task->cred != old);
#ifdef CONFIG_DEBUG_CREDENTIALS
BUG_ON(read_cred_subscribers(old) < 2);
validate_creds(old);
validate_creds(new);
#endif
BUG_ON(atomic_read(&new->usage) < 1); get_cred(new); /* we will require a ref for the subj creds too */ /* dumpability changes */
if (!uid_eq(old->euid, new->euid) ||
!gid_eq(old->egid, new->egid) ||
!uid_eq(old->fsuid, new->fsuid) ||
!gid_eq(old->fsgid, new->fsgid) ||
!cred_cap_issubset(old, new)) {
if (task->mm)
set_dumpable(task->mm, suid_dumpable);
task->pdeath_signal = 0;
smp_wmb();
} /* alter the thread keyring */
if (!uid_eq(new->fsuid, old->fsuid))
key_fsuid_changed(task);
if (!gid_eq(new->fsgid, old->fsgid))
key_fsgid_changed(task); /* do it
* RLIMIT_NPROC limits on user->processes have already been checked
* in set_user().
*/
alter_cred_subscribers(new, 2);
if (new->user != old->user)
atomic_inc(&new->user->processes);
rcu_assign_pointer(task->real_cred, new);
rcu_assign_pointer(task->cred, new);
if (new->user != old->user)
atomic_dec(&old->user->processes);
alter_cred_subscribers(old, -2); /* send notifications */
if (!uid_eq(new->uid, old->uid) ||
!uid_eq(new->euid, old->euid) ||
!uid_eq(new->suid, old->suid) ||
!uid_eq(new->fsuid, old->fsuid))
proc_id_connector(task, PROC_EVENT_UID); if (!gid_eq(new->gid, old->gid) ||
!gid_eq(new->egid, old->egid) ||
!gid_eq(new->sgid, old->sgid) ||
!gid_eq(new->fsgid, old->fsgid))
proc_id_connector(task, PROC_EVENT_GID); /* release the old obj and subj refs both */
put_cred(old);
put_cred(old);
return 0;
}

从注释里也可以看到,这个函数的功能就是给当前task写入新的cred的结构体,从而改变了当前task的权限。

配合通过prepare_kernel_cred(0)得到的root权限的cred结构体,从而赋予当前task同样的root权限,这样就完成了提权。

内核态函数

相比用户态库函数,内核态的函数有了一些变化

  • printf() -> printk(),但需要注意的是 printk() 不一定会把内容显示到终端上,但一定在内核缓冲区里,可以通过 dmesg 查看效果

  • memcpy() -> copy_from_user()/copy_to_user()

    • copy_from_user() 实现了将用户空间的数据传送到内核空间
    • copy_to_user()实现了将内核空间的数据传送到用户空间

    Copy_to_user( to, &from, sizeof(from)

    To:用户空间函数 (可以是数组)

    From:内核空间函数(可以是数组)

    sizeof(from):表示从用户空间想内核空间拷贝数据的字节数。

    Copy_from_user(&from , to , sizeof(to) )

    To:用户空间函数 (可以是数组)

    From:内核空间函数(可以是数组)

    sizeof(from):内核空间要传递的数组的长度

    成功返回0,失败返回失败数目。

  • malloc() -> kmalloc(),内核态的内存分配函数,和 malloc() 相似,但使用的是 slab/slub 分配器

  • free() -> kfree(),同 kmalloc()

另外要注意的是,kernel 管理进程,因此 kernel 也记录了进程的权限。kernel 中有两个可以方便的改变权限的函数:

  • int commit_creds(struct cred *new)
  • struct cred* prepare_kernel_cred(struct task_struct* daemon)

从函数名也可以看出,执行 commit_creds(prepare_kernel_cred(0)) 即可获得 root 权限,0 表示 以 0 号进程作为参考准备新的 credentials。

更多关于 prepare_kernel_cred 的信息可以参考 源码

执行 commit_creds(prepare_kernel_cred(0)) 也是最常用的提权手段,两个函数的地址都可以在 /proc/kallsyms 中查看(较老的内核版本中是 /proc/ksyms)。

post sudo grep commit_creds /proc/kallsyms
[sudo] m4x 的密码:
ffffffffbb6af9e0 T commit_creds
ffffffffbc7cb3d0 r __ksymtab_commit_creds
ffffffffbc7f06fe r __kstrtab_commit_creds
post sudo grep prepare_kernel_cred /proc/kallsyms
ffffffffbb6afd90 T prepare_kernel_cred
ffffffffbc7d4f20 r __ksymtab_prepare_kernel_cred
ffffffffbc7f06b7 r __kstrtab_prepare_kernel_cred

一般情况下,/proc/kallsyms 的内容需要 root 权限才能查看

Mitigation

canary, dep, PIE, RELRO 等保护与用户态原理和作用相同

  • smep: Supervisor Mode Execution Protection,当处理器处于 ring0 模式,执行 用户空间 的代码会触发页错误。(在 arm 中该保护称为 PXN)
  • smap: Superivisor Mode Access Protection,类似于 smep,通常是在访问数据时。
  • mmap_min_addr:

CTF kernel pwn 相关

给定的文件

一般会给三个或者四个文件:

  1. boot.sh: 一个用于启动 kernel 的 shell 的脚本,多用 qemu,保护措施与 qemu 不同的启动参数有关
  2. bzImage: kernel binary(打包的内核代码,可以用来寻找gadget)
  3. rootfs.cpio: 文件系统映像
  4. file.ko: 有bug的程序,可以用ida打开

qemu 启动的参数:

  • -initrd rootfs.cpio,使用 rootfs.cpio 作为内核启动的文件系统
  • -kernel bzImage,使用 bzImage 作为 kernel 映像
  • -cpu kvm64,+smep,设置 CPU 的安全选项,这里开启了 smep
  • -m 64M,设置虚拟 RAM 为 64M,默认为 128M 其他的选项可以通过 --help 查看。
  • -initrd 设置根文件系统
  • -vnc :2, 打开一个vnc,这样可以通过vncviewer访问 localhost:2看到VM的控制台
  • -S 表示启动后就挂起,等待gdb连接
  • -s 是-gdb :1234的缩小,就是打开1234这个gdb调试端口
  • -net nic 表示为虚拟机创建一个虚拟网卡
  • -net user 表示QEMU使用user模式
  • -append kernel的启动参数,后面最好用引号()引起来。
    • root=/dev/sda 告诉qemu单板运行内核镜像路径(指定根文件系统的挂载点,是在QEMU Guest OS上的位置)
    • console=ttyS0 告诉内核vexpress单板运行,串口设备是哪个tty。这个值可以从生成的.config文件CONFIG_CONSOLE宏找到。
    • nokaslr 是传递给内核的参数。表示禁用kaslr(Kernel Address Space Layout Randomization) 。(kaslr:kernel加载到内存后他的地址会进行随机化)
    • rw 文件系统读写权限,这里是可读可写。其他可选值有ro
    • oops linux内核的行为不正确,并产生了一份相关的错误日志
    • panic 操作系统在监测到内部的致命错误,并无法安全处理此错误时采取的动作。
  • -nographic 不使用图形化界面,只使用串口
  • -netdev user 配置内部用户网络,与其它任何vm和外部网络都不通,属于宿主host和qemu内部的网络通道。

Linux内核模块的若干知识

1.fop结构体

内核模块程序的结构中包括一些callback回调表,对应的函数存在一个file_operations(fop)结构体中,这也是对我们pwn手来说最重要的结构体;结构体中实现了的回调函数就会静态初始化上函数地址,而未实现的函数,值为NULL。

2.proc_create创建文件

3.数据的通信

小知识

查看装载驱动

lsmod

查看所开保护

cat /proc/cpuinfo

Kaslr 地址随机化

Smep 内核态不可执行用户态代码

Smap 内核态不可访问用户态内存

查看内核堆块

cat /proc/slabinfo

查看prepare_kernel_cred和commit_creds地址

grep prepare_kernel_cred  /proc/kallsyms
grep commit_creds /proc/kallsyms cat /proc/kallsyms | grep prepare_kernel_cred
cat /proc/kallsyms | grep commit_creds

主要提权手法:

1) commit_creds(prepare_kernel_cred(0));
2) 修改cred结构体

文件压缩命令:

打包操作:

find . | cpio -o -H newc | gzip > ${myDIR}/initramfs.cpio.gz
或者 find . | cpio -o --format=newc > rootfs.cpio 解包操作: gunzip initramfs.cpio.gz
cpio -idmv < initramfs.cpio

gdb调试:

添加-gdb tcp::1234

set architecture i386:x86-64
target remote:1234
下断
c
运行.exp
调试

调试:

现在startvm.sh中添加-gdb tcp::1234(记得在上一行末添加/)

1、编译exp.c文件:musl-gcc -static -O2 exp.c -o exp

2、通过python solution.py连接和传送脚本。

3、打开gdb,设置x86_64架构:set architecture i386:x86-64

​ 连接gdb startvm.sh:target remote : 1234

4、gdb中下断,c,在连接的startvm中运行:./exp

查看startvm.sh文件:

loglevel调大 可以查看更多数据 nokaslr关闭保护

monitor 增加逃逸难度

cores=2,threads=2可能为double fetch

修改root.sh中的 -initrd initramfs.cpio 为-initrd root.cpio

修改root.cpiod的setsid cttyhack setuidgid 0100 sh为setsid cttyhack setuidgid 0000 sh

向startvm.sh 中添加-gdb tcp::1234添加gdb调试

对于exp文件:

solution.py脚本打包(包含远程连接、编译、加解密、加权限等)

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#
# Copyright 2019 saltedfish <17302010022@fudan.edu.cn>
#
# Distributed under terms of the MIT license. """ """
from pwn import * import sys
import os context.log_level = 'debug'
cmd = '$ '
def exploit(r):
r.sendlineafter(cmd, 'stty -echo') #关闭回显
os.system('musl-gcc -static -O2 exp.c -o exp') #静态编译
os.system('gzip -c exp > exp.gz') #压缩打包
r.sendlineafter(cmd, 'cat <<EOF > exp.gz.b64') #heredoc
r.sendline((read('exp.gz')).encode('base64')) #cat为可见字符,exp.gz多不可见字符,base64转换为可见字符
r.sendline('EOF') #base64加密后可能会出错,出现中断
r.sendlineafter(cmd, 'base64 -d exp.gz.b64 > exp.gz') #base64解码
r.sendlineafter(cmd, 'gunzip exp.gz') #解压
r.sendlineafter(cmd, 'chmod +x ./exp') #加权限
r.interactive() io=process('startvm.sh')
#io = remote('0.0.0.0',10001)
exploit(io)
#exploit(process('startvm.sh'))

kernel base的更多相关文章

  1. arm linux kernel 从入口到start_kernel 的代码分析

    参考资料: <ARM体系结构与编程> <嵌入式Linux应用开发完全手册> Linux_Memory_Address_Mapping http://www.chinaunix. ...

  2. qemu 模拟-arm-mini2440开发板-启动u-boot,kernel和nfs文件系统

    qemu 本文介绍了如何编译u-boot.linux kernel,然后用qemu启动u-boot和linux kernel,达到与开发板上一样的学习效果! 虽然已经买了2440开发板,但是在实际学习 ...

  3. qemu 模拟-arm-mini2440开发板-启动u-boot,kernel和nfs文件系统【转】

    转自:http://www.cnblogs.com/riskyer/p/3366001.html qemu 本文介绍了如何编译u-boot.linux kernel,然后用qemu启动u-boot和l ...

  4. linux kernel mini2440 start.S head-common.S 部分注释

    内核版本:2.6.32.2(mini2440光盘源码) github地址:https://github.com/guanglun/mini2440_uboot_linux (for_len分支 htt ...

  5. Windows内核开发-6-内核机制 Kernel Mechanisms

    Windows内核开发-6-内核机制 Kernel Mechanisms 一部分Windows的内核机制对于驱动开发很有帮助,还有一部分对于内核理解和调试也很有帮助. Interrupt Reques ...

  6. KVM安装部署

    KVM安装部署 公司开始部署KVM,KVM的全称是kernel base virtual machine,对KVM虚拟化技术研究了一段时间, KVM是基于硬件的完全虚拟化,跟vmware.xen.hy ...

  7. 蓝屏 Dump文件分析方法

    WinDbg使用有点麻烦,还要符号表什么的.试了下,感觉显示很乱,分析的也不够全面... 试试其他的吧!今天电脑蓝屏了,就使用其dump文件测试,如下: 1.首先,最详细的,要属Osr Online这 ...

  8. windbg无法下载符号文件

    symbol file path: srv*d:\symbolslocal*http://msdl.microsoft.com/download/symbols 即使设置是对的,但我用.reload, ...

  9. 谁在死锁Mutex——用Windbg查找Mutex死锁所有者线程

    Who is blocking that Mutex? - Fun with WinDbg, CDB and KD 05 Aug 2006 By Ingo Rammer I'm currently t ...

随机推荐

  1. HTTP 1.x 学习笔记 —— Web 性能权威指南

    HTTP 1.0的优化策略非常简单,就一句话:升级到HTTP 1.1.完了! 改进HTTP的性能是HTTP 1.1工作组的一个重要目标,后来这个版本也引入了大量增强性能的重要特性,其中一些大家比较熟知 ...

  2. 微信小程序(一)-工具创建和结构配置说明 Stable Build

    按装前特别说明: windows最好下载32位的,不然用到用到后面就出现"网络连接失败",然后就登录不上去了,打不开编辑器了! 问题 : 微信开发者工具网络连接失败, " ...

  3. list.add(int index, E element)和list.addAll(list1)

    List.add(int index, E element): 在列表的指定位置插入指定元素(可选操作).将当前处于该位置的元素(如果有的话)和所有后续元素向右移动(在其索引中加 1). 参数:ind ...

  4. 微信小程序:日期组件picker的使用

    1.页面代码 <view class="right"> <picker mode="date" value="{{material. ...

  5. SpringBoot 配置文件以及依赖 加上跨域配置文件

    配置目录: application.properties的配置 #设置服务端口号 server.port = 8090 #配置数据源 spring.datasource.driver-class-na ...

  6. MySQL like查询使用索引

    在使用msyql进行模糊查询的时候,很自然的会用到like语句,通常情况下,在数据量小的时候,不容易看出查询的效率,但在数据量达到百万级,千万级的时候,查询的效率就很容易显现出来.这个时候查询的效率就 ...

  7. python类的内部方法

    目录 一.绑定方法与非绑定方法 1.绑定方法 2.非绑定方法 二.property 1.什么是property? 2.为什么要用property? 3.如何使用property? 三.isinstan ...

  8. 保姆级别学生党安装Clion IDE(面向华师同学)

    保姆级别学生党安装Clion IDE(面向华师同学) 界面UI 废话不多说,直接上图 具备功能 UI美观 (下面会介绍) 基础的代码编写能力 大容量的IDE插件 (下面会介绍) 代码补全,以及搭配Ki ...

  9. keras报错:AttributeError: '_thread._local' object has no attribute 'value'

    需求是使用pyqt5中的槽函数运行keras模型训练,为了不让工具在模型训练的过程中出现假死的现象,于是把训练操作放到单独的线程中运行,于是问题来了,训练操作在主线程运行时正常,但是界面假死,假若训练 ...

  10. 永恒之蓝(MS17-010)检测与利用

    目录 利用Nmap检测 MSF反弹SHELL 注意 乱码 参考 利用Nmap检测 命令: nmap -p445 --script smb-vuln-ms17-010 [IP] # 如果运行报错,可以加 ...