Linux系统调用Hook姿势总结

2023-05-16

  http://www.cnblogs.com/LittleHann/p/3854977.html
主题 Linux

相关学习资料


http://xiaonieblog.com/?post=121
http://hbprotoss.github.io/posts/li-yong-ld_preloadjin-xing-hook.html
http://www.catonmat.net/blog/simple-ld-preload-tutorial/
http://os.51cto.com/art/201004/195510.htm
http://sebug.net/paper/pst_WebZine/pst_WebZine_0x03/html/%5BPSTZine%200x03%5D%5B0x03%5D%5B%E9%AB%98%E7%BA%A7Linux%20Kernel%20Inline%20Hook%E6%8A%80%E6%9C%AF%E5%88%86%E6%9E%90%E4%B8%8E%E5%AE%9E%E7%8E%B0%5D.html
http://blog.chinaunix.net/uid-26310563-id-3175021.html
http://laokaddk.blog.51cto.com/368606/d-26/p-2
http://m.blog.csdn.net/blog/panfengyun12345/19480567
https://www.kernel.org/doc/Documentation/kprobes.txt
http://blog.chinaunix.net/uid-23769728-id-3198044.html
https://sourceware.org/systemtap/
http://alanwu.blog.51cto.com/3652632/1111213
http://laokaddk.blog.51cto.com/368606/421862
http://www.elliotbradbury.com/linux-syscall-hooking-interrupt-descriptor-table/
http://baike.baidu.com/view/336501.htm
http://blog.csdn.net/dog250/article/details/6451762
http://blog.csdn.net/sanbailiushiliuye/article/details/7552359  

目录


1. 系统调用Hook简介
2. Ring3中Hook技术
3. Ring0中Hook技术
4. 后记  

1. 系统调用Hook简介

系统调用属于一种软中断机制(内中断陷阱),它有操作系统提供的功能入口(sys_call)以及CPU提供的硬件支持(int 3 trap)共同完成。

我们必须要明白,Hook技术是一个相对较宽的话题,因为操作系统从ring3到ring0是分层次的结构,在每一个层次上都可以进行相应的Hook,它们使用的技术方法以及取得的效果也是不尽相同的。本文的主题是"系统调用的Hook学习","系统调用的Hook"是我们的目的,而要实现这个目的可以有很多方法,本文试图尽量覆盖从ring3到ring0中所涉及到的Hook技术,来实现系统调用的监控功能。

2. Ring3中Hook技术

0x1: LD_PRELOAD动态连接.so函数劫持

在linux操作系统的动态链接库的世界中,LD_PRELOAD就是这样一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。loader在进行动态链接的时候,会将有相同符号名的符号覆盖成LD_PRELOAD指定的so文件中的符号。换句话说,可以用我们自己的so库中的函数替换原来库里有的函数,从而达到hook的目的。这和:

1. Windows下通过修改import table来hook API
2. PHP中修改functions_table来hook function

从原理上来讲很类似。

我们知道,Linux的用C库的都是glibc,有一个叫libc.so.6的文件,这是几乎所有Linux下命令的动态链接中,其中有标准C的各种函数,默认情况下,linux所编译的程序中对标准C函数的链接,都是通过动态链接方式来链接libc.so.6这个函数库的。这也意味着我们在通过我们注入的.so来实现函数覆盖劫持之后需要从libc.so.6中取得原本的正常函数,让程序继续正常执行

正常程序main.c:


#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
  if( strcmp(argv[1], "test") )
  {
    printf("Incorrect password\n");
  }
  else
  {
    printf("Correct password\n");
  }
  return 0;
}  

用于劫持函数的.so代码hook.c

#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
/*
hook的目标是strcmp,所以typedef了一个STRCMP函数指针
hook的目的是要控制函数行为,从原库libc.so.6中拿到strcmp指针,保存成old_strcmp以备调用
*/
typedef int(*STRCMP)(const char*, const char*);

int strcmp(const char *s1, const char *s2)
{
  static void *handle = NULL;
  static STRCMP old_strcmp = NULL;

  if( !handle )
  {
    handle = dlopen("libc.so.6", RTLD_LAZY);
    old_strcmp = (STRCMP)dlsym(handle, "strcmp");
  }
  printf("oops!!! hack function invoked. s1=<%s> s2=<%s>\n", s1, s2);
  return old_strcmp(s1, s2);
}

编译:


gcc -o test main.c
gcc -fPIC -shared -o hook.so hook.c -ldl  

运行:

LD_PRELOAD=./hook.so ./test 123

0x2:  ...

3. Ring0中Hook技术

0x1: Kernel Inline Hook

传统的kernel inline hook技术就是修改内核函数的opcode,通过写入jmp或push ret等指令跳转到新的内核函数中,从何达到劫持的目的。对于这类劫持攻击,目前常见的做法是fireeye的"函数返回地址污点检测",通过对原有指令返回位置的汇编代码作污点标记,通过查找jmp,push ret等指令来进行防御

我们知道实现一个系统调用的函数中一定会递归的嵌套有很多的子函数,即它必定要调用它的下层函数。

而从汇编的角度来说,对一个子函数的调用是采用"段内相对短跳转 jmp offset"来实现的,即CPU根据offset来进行一个偏移量的跳转。

如果我们把下层函数在上层函数中的offset替换成我们"Hook函数"的offset,这样上层函数调用下层函数时,就会跳到我们的"Hook函数"中,我们就可以在"Hook函数"中做过滤和劫持内容的工作

以sys_read作为例子

\linux-2.6.32.63\fs\read_write.c


asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)
{
  struct file *file;
  ssize_t ret = -EBADF;
  int fput_needed;

  file = fget_light(fd, &fput_needed);
  if (file) 
    {
    loff_t pos = file_pos_read(file);
    ret = vfs_read(file, buf, count, &pos);
    file_pos_write(file, pos);
    fput_light(file, fput_needed);
  }

  return ret;
}
EXPORT_SYMBOL_GPL(sys_read);  

在sys_read()中,调用了子函数vfs_read()来完成读取数据的操作,在sys_read()中调用子函数vfs_read()的汇编命令是:

call 0xc106d75c <vfs_read>

等同于:

jmp offset(相对于sys_read()的基址偏移)

所以,我们的思路很明确,找到call   0xc106d75c <vfs_read>这条汇编,把其中的offset改成我们的Hook函数对应的offset,就可以实现劫持目的了

1. 搜索sys_read的opcode
2. 如果发现是call指令,根据call后面的offset计算要跳转的地址是不是我们要hook的函数地址
    1) 如果"不是"就重新计算Hook函数的offset,用Hook函数的offset替换原来的offset
    2) 如果"已经是"Hook函数的offset,则说明函数已经处于被劫持状态了,我们的Hook引擎应该直接忽略跳过,避免重复劫持

poc:


/*
参数:
1. handler是上层函数的地址,这里就是sys_read的地址
2. old_func是要替换的函数地址,这里就是vfs_read
3. new_func是新函数的地址,这里就是new_vfs_read的地址
*/
unsigned int patch_kernel_func(unsigned int handler, unsigned int old_func, 
    unsigned int new_func)
{
  unsigned char *p = (unsigned char *)handler;
  unsigned char buf[4] = "\x00\x00\x00\x00";
  unsigned int offset = 0;
  unsigned int orig = 0;
  int i = 0;

  DbgPrint("\n*** hook engine: start patch func at: 0x%08x\n", old_func);

  while (1) {
    if (i > 512)
      return 0;

    if (p[0] == 0xe8) {
      DbgPrint("*** hook engine: found opcode 0x%02x\n", p[0]);
      
      DbgPrint("*** hook engine: call addr: 0x%08x\n", 
        (unsigned int)p);
      buf[0] = p[1];
      buf[1] = p[2];
      buf[2] = p[3];
      buf[3] = p[4];

      DbgPrint("*** hook engine: 0x%02x 0x%02x 0x%02x 0x%02x\n", 
        p[1], p[2], p[3], p[4]);

        offset = *(unsigned int *)buf;
        DbgPrint("*** hook engine: offset: 0x%08x\n", offset);

        orig = offset + (unsigned int)p + 5;
        DbgPrint("*** hook engine: original func: 0x%08x\n", orig);

      if (orig == old_func) {
        DbgPrint("*** hook engine: found old func at"
          " 0x%08x\n", 
          old_func);

        DbgPrint("%d\n", i);
        break;
      }
    }
    p++;
    i++;
  }

  offset = new_func - (unsigned int)p - 5;
  DbgPrint("*** hook engine: new func offset: 0x%08x\n", offset);

  p[1] = (offset & 0x000000ff);
  p[2] = (offset & 0x0000ff00) >> 8;
  p[3] = (offset & 0x00ff0000) >> 16;
  p[4] = (offset & 0xff000000) >> 24;

  DbgPrint("*** hook engine: pachted new func offset.\n");

  return orig;
}   

0x2: 利用0x80中断劫持system_call->sys_call_table进行系统调用Hook

我们知道,要对系统调用(sys_call_table)进行替换,却必须要获取该地址后才可以进行替换。但是Linux 2.6版的内核出于安全的考虑没有将系统调用列表基地址的符号sys_call_table导出,但是我们可以采取一些hacking的方式进行获取。

因为系统调用都是通过0x80中断来进行的,故可以通过查找0x80中断的处理程序来获得sys_call_table的地址。其基本步骤是


1. 获取中断描述符表(IDT)的地址(使用C ASM汇编)
2. 从中查找0x80中断(系统调用中断)的服务例程(8*0x80偏移)
3. 搜索该例程的内存空间,
4. 从其中获取sys_call_table(保存所有系统调用例程的入口地址)的地址  

编程示例

find_sys_call_table.c


#include <linux/module.h>
#include <linux/kernel.h>

// 中断描述符表寄存器结构
struct 
{
  unsigned short limit;
  unsigned int base;
} __attribute__((packed)) idtr;


// 中断描述符表结构
struct 
{
  unsigned short off1;
  unsigned short sel;
  unsigned char none, flags;
  unsigned short off2;
} __attribute__((packed)) idt;

// 查找sys_call_table的地址
void disp_sys_call_table(void)
{
  unsigned int sys_call_off;
  unsigned int sys_call_table;
  char* p;
  int i;

  // 获取中断描述符表寄存器的地址
  asm("sidt %0":"=m"(idtr));
  printk("addr of idtr: %x\n", &idtr);

  // 获取0x80中断处理程序的地址
  memcpy(&idt, idtr.base+8*0x80, sizeof(idt));
  sys_call_off=((idt.off2<<16)|idt.off1);
  printk("addr of idt 0x80: %x\n", sys_call_off);

  // 从0x80中断服务例程中搜索sys_call_table的地址
  p=sys_call_off;
  for (i=0; i<100; i++)
  {
    if (p=='\xff' && p[i+1]=='\x14' && p[i+2]=='\x85')
    {
      sys_call_table=*(unsigned int*)(p+i+3);
      printk("addr of sys_call_table: %x\n", sys_call_table);
      return ;
    }
  }
}

// 模块载入时被调用
static int __init init_get_sys_call_table(void)
{
  disp_sys_call_table();
  return 0;
}

module_init(init_get_sys_call_table);

// 模块卸载时被调用
static void __exit exit_get_sys_call_table(void)
{
}

module_exit(exit_get_sys_call_table);

// 模块信息
MODULE_LICENSE("GPL2.0");
MODULE_AUTHOR("LittleHann");  

Makefile


obj-m := find_sys_call_table.o  

编译

make -C /usr/src/kernels/2.6.32-358.el6.i686 M=$(pwd) modules

测试效果

dmesg| tail

获取到了sys_call_table的基地址之后,我们就可以修改指定offset对应的系统调用了,从而达到劫持系统调用的目的

0x3: 获取sys_call_table的其他方法

1. 常用方法

模拟出一个call *sys_call_table(,%eax,4),然后看其机器码,然后在system_call的附近基于这个特征进行寻找


#include <stdio.h>
void fun1()
{
  printf("fun1/n");
}
void fun2()
{
  printf("fun2/n");
}
unsigned int sys_call_table[2] = {fun1, fun2};
int main(int argc, char **argv)
{
  asm("call *sys_call_table(%eax,4");
}

编译
gcc test.c -o test

objdump进行dump
objdump -D ./test | grep sys_call_table  

2. 通过/boot/System.map-2.6.32-358.el6.i686文件查找


cd /boot
grep sys_call_table System.map-2.6.32-358.el6.i686  

0x4: 利用Linux内核机制kprobe机制进行系统调用Hook

kprobe简介

kprobe是一个动态地收集调试和性能信息的工具,它从Dprobe项目派生而来,它几乎可以跟踪任何函数或被执行的指令以及一些异步事件。它的基本工作机制是:


1. 用户指定一个探测点,并把一个用户定义的处理函数关联到该探测点
2. 在注册探测点的时候,对被探测函数的指令码进行替换,替换为int 3的指令码
3. 在执行int 3的异常执行中,通过通知链的方式调用kprobe的异常处理函数
4. 在kprobe的异常出来函数中,判断是否存在pre_handler钩子,存在则执行
5. 执行完后,准备进入单步调试,通过设置EFLAGS中的TF标志位,并且把异常返回的地址修改为保存的原指令码
6. 代码返回,执行原有指令,执行结束后触发单步异常
7. 在单步异常的处理中,清除单步标志,执行post_handler流程,并最终返回  

从原理上来说,kprobe的这种机制属于系统提供的"回调订阅",和netfilter是类似的,linux内核通过在某些代码执行流程中给出回调函数接口供程序员订阅,内核开发人员可以在这些回调点上注册(订阅)自定义的处理函数,同时还可以获取到相应的状态信息,方便进行过滤、分析 kprobe实现了三种类型的探测点:


1. kprobes
kprobes是可以被插入到内核的任何指令位置的探测点,kprobe允许在同一地址注册多个kprobes,但是不能同时在该地址上有多个jprobes

2. jprobes
jprobes则只能被插入到一个内核函数的入口

3. kretprobes(也叫返回探测点)
而kretprobes则是在指定的内核函数返回时才被执行  

在本文中,我们可以使用kprobe的程序实现作一个内核模块,模块的初始化函数来负责安装探测点,退出函数卸载那些被安装的探测点。kprobe提供了接口函数(APIs)来安装或卸载探测点。目前kprobe支持如下架构:i386、x86_64、ppc64、ia64(不支持对slot1指令的探测)、sparc64 (返回探测还没有实现)

kprobe实现原理

1. kprobes


/*
kprobes执行流程
*/
1. 当安装一个kprobes探测点时,kprobe首先备份被探测的指令
2. 使用断点指令(int 3指令)来取代被探测指令的头一个或几个字节(这点和OD很像)
3. CPU执行到探测点时,将因运行断点指令而执行trap操作,那将导致保存CPU的寄存器,调用相应的trap处理函数
4. trap处理函数将调用相应的notifier_call_chain(内核中一种异步工作机制)中注册的所有notifier函数
5. kprobe正是通过向trap对应的notifier_call_chain注册关联到探测点的处理函数来实现探测处理的
6. 当kprobe注册的notifier被执行时
    6.1 它首先执行关联到探测点的pre_handler函数,并把相应的kprobe struct和保存的寄存器作为该函数的参数
    6.2 然后,kprobe单步执行被探测指令的备份(原始函数)
    6.3 最后,kprobe执行post_handler
7. 等所有这些运行完毕后,紧跟在被探测指令后的指令流将被正常执行  

#include linux/kprobes.h

int register_kprobe(struct kprobe *kp);

int pre_handler(struct kprobe *p, struct pt_regs *regs);
void post_handler(struct kprobe *p, struct pt_regs *regs,
    unsigned long flags);
fault_handler() 

整个顺序为:

pre_handler->被Hook原函数->post_handler

2. jprobe


/*
jprobe执行流程
*/
1. jprobe通过注册kprobes在被探测函数入口的来实现,它能无缝地访问被探测函数的参数
2. jprobe处理函数应当和被探测函数有同样的原型,而且该处理函数在函数末必须调用kprobe提供的函数jprobe_return()
3. 当执行到该探测点时,kprobe备份CPU寄存器和栈的一些部分,然后修改指令寄存器指向jprobe处理函数
4. 当执行该jprobe处理函数时,寄存器和栈内容与执行真正的被探测函数一模一样,因此它不需要任何特别的处理就能访问函数参数, 在该处理函数执行到最后
时,它调用jprobe_return(),那导致寄存器和栈恢复到执行探测点时的状态,因此被探测函数能被正常运行
5. 需要注意,被探测函数的参数可能通过栈传递,也可能通过寄存器传递,但是jprobe对于两种情况都能工作,因为它既备份了栈,又备份了寄存器,当然,前提
是jprobe处理函数原型必须与被探测函数完全一样  

#include linux/kprobes.h


int register_jprobe(struct jprobe *jp);  

3. kretprobe

/*
kretprobe执行流程
*/
1. kretprobe也使用了kprobes来实现2
2. 当用户调用register_kretprobe()时,kprobe在被探测函数的入口建立了一个探测点
3. 当执行到探测点时,kprobe保存了被探测函数的返回地址并取代返回地址为一个trampoline的地址,kprobe在初始化时定义了该trampoline并且为该
trampoline注册了一个kprobe
4. 当被探测函数执行它的返回指令时,控制传递到该trampoline,因此kprobe已经注册的对应于trampoline的处理函数将被执行,而该处理函数会调用用户
关联到该kretprobe上的处理函数
5. 处理完毕后,设置指令寄存器指向已经备份的函数返回地址,因而原来的函数返回被正常执行。
6. 被探测函数的返回地址保存在类型为kretprobe_instance的变量中,结构kretprobe的maxactive字段指定了被探测函数可以被同时探测的实例数
7. 函数register_kretprobe()将预分配指定数量的kretprobe_instance:
    7.1 如果被探测函数是非递归的并且调用时已经保持了自旋锁(spinlock),那么maxactive为1就足够了
    7.2 如果被探测函数是非递归的且运行时是抢占失效的,那么maxactive为NR_CPUS就可以了
    7.3 如果maxactive被设置为小于等于0, 它被设置到缺省值(如果抢占使能, 即配置了 CONFIG_PREEMPT,缺省值为102*NR_CPUS中的最大值,否则
缺省值为NR_CPUS)
    7.4 如果maxactive被设置的太小了,一些探测点的执行可能被丢失,但是不影响系统的正常运行,在结构kretprobe中nmissed字段将记录被丢失的探测
点执行数,它在返回探测点被注册时设置为0,每次当执行探测函数而没有kretprobe_instance可用时,它就加1

#include linux/kprobes.h


int register_kretprobe(struct kretprobe *rp);

int kretprobe_handler(struct kretprobe_instance *ri, struct pt_regs *regs);  

对应于每一个注册函数,有相应的卸载函数


void unregister_kprobe(struct kprobe *kp);
void unregister_jprobe(struct jprobe *jp);
void unregister_kretprobe(struct kretprobe *rp);  

了解了kprobe的基本原理之后,我们要回到我们本文的主题,系统调用的Hook上来,由于kprobe是linux提供的稳定的回调注册机制,linux天生就稳定地支持在我们指定的某个函数的执行流上进行注册回调,我们很方便地使用它来进行系统调用(例如sys_execv()、网络连接等)的执行Hook,从而劫持linux系统的系统调用流程,为下一步的恶意入侵行为分析作准备

kprobe编程示例

do_fork.c

/*
 * * You will see the trace data in /var/log/messages and on the console
 * * whenever do_fork() is invoked to create a new process.
 * */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>

//定义要Hook的函数,本例中do_fork
static struct kprobe kp = 
{
  .symbol_name = "do_fork",
};

static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
  struct thread_info *thread = current_thread_info();

  printk(KERN_INFO "pre-handler thread info: flags = %x, status = %d, cpu = %d, task->pid = %d\n",
  thread->flags, thread->status, thread->cpu, thread->task->pid);

  return 0;
}

static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags)
{  
  struct thread_info *thread = current_thread_info();

  printk(KERN_INFO "post-handler thread info: flags = %x, status = %d, cpu = %d, task->pid = %d\n",
  thread->flags, thread->status, thread->cpu, thread->task->pid);
}

static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr)
{
  printk(KERN_INFO "fault_handler: p->addr = 0x%p, trap #%dn",
  p->addr, trapnr);
  return 0;
}

/*
内核模块加载初始化,这个过程和windows下的内核驱动注册分发例程很类似
*/
static int __init kprobe_init(void)
{
  int ret;
  kp.pre_handler = handler_pre;
  kp.post_handler = handler_post;
  kp.fault_handler = handler_fault;

  ret = register_kprobe(&kp);
  if (ret < 0) 
  {
    printk(KERN_INFO "register_kprobe failed, returned %d\n", ret);
    return ret;
  }
  printk(KERN_INFO "Planted kprobe at %p\n", kp.addr);
  return 0;
}

static void __exit kprobe_exit(void)
{
  unregister_kprobe(&kp);
  printk(KERN_INFO "kprobe at %p unregistered\n", kp.addr);
}

module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");

Makefile


obj-m := do_fork.o  

编译:

make -C /usr/src/kernels/2.6.32-358.el6.i686 M=$(pwd) modules

加载内核模块:


insmod do_fork.ko  

测试效果:

dmesg| tail

cat /proc/kallsyms | grep do_fork

do_fork的地址与kprobe注册的地址一致,可见,在kprobe调试模块在内核停留期间,我们编写的内核监控模块劫持并记录了系统fork出了新的进程信息

4. 后记

Hook技术是进行主动防御、动态入侵检测的关键技术,从技术上来说,目前的很多Hook技术都属于"猥琐流",即:


1. 通过"劫持"在关键流程上的某些函数的执行地址,在Hook函数执行完之后,再跳回原始的函数继续执行(做好现场保护)
2. 或者通过dll、进程、线程注入比原始程序提前获得CPU执行权限  

但是随着windows的PatchGuard的出现,这些出于"安全性"的内核patct将被一视同仁地看作内核完整性的威胁者

更加优美、稳定的方法应该是:


1. 注册标准的回调方法,包括:
  1) 进程
  2) 线程
  3) 模块的创建
  4) 卸载回调函数
  5) 文件/网络等各种过滤驱动 2. 内核提供的标准处理流程Hook点
  1) kprobe机制
2. 网络协议栈提供的标准Hook点
  1) netfilter的链式处理流程  

Copyright (c) 2014 LittleHann All rights reserved

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux系统调用Hook姿势总结 的相关文章

  • 【springboot系列】springboot整合guava实现本地缓存

    概述 Guava Cache 是 Google 开源的一套开发工具集合 xff0c Guava Cache 是其中的一个专门用于处理本地缓存的轻量级框架 xff0c 是全内存方式的本地缓存 xff0c 而且是线程安全的 和 Concurre
  • 解决Windows莫名其妙地从休眠状态唤醒的问题

    因为经常写代码开很多个工作区 xff0c 电脑经常就是合盖休眠 xff0c 这样打开就能用 xff0c 但是总是在我不经意的时候发现电脑风扇在狂转 xff0c 不知道是哪个应用又在qj我的电源管理计划 xff0c 今天午休发现电脑风扇狂转
  • Ubunt装机后的必要设置及必备软件

    1 为 Ubuntu Dock 启用 Minimize on Click Ubuntu Dock xff08 位于屏幕左侧的任务栏 xff09 可以轻松打开 xff0c 并且切换和管理应用程序与正在运行的应用程序 你可以点击 Dock 中的
  • No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android 解决方法

    在ndk版本升级之后 xff0c 项目编译出现了问题 xff0c 提示没有对应的编译工具链 xff0c 目前最多的做法是 xff0c 下载之前的ndk版本 xff0c 然后再拷贝缺少的部分 这种做法确实可以解决问题 xff0c 但是既然新版
  • 来自一个前端大神转产品经理后的聊天感悟

    给的学习建议 xff1a 1 推荐给我一本书 锋利的jQuery 2 学会使用思维导图工具 3 课余时间学习理财 4 研发过程中 xff0c 多多留心一些交互 xff0c 自己完善反复琢磨自己的思路 xff08 保证是最简的 xff09 5
  • 08丨案例:编写最简单的性能脚本

    通常我们会遇到要手写脚本的时候 xff0c 就要针对一些接口编写脚本 这时候 xff0c 我们需要知道接口规范和后台的数据是什么 而有些性能测试工程师写脚本时 xff0c 并不知道后端的逻辑 xff0c 只知道实现脚本 xff0c 事实上
  • KindEditor图片上传相关问题 (转)

    size 61 x large 从众多的Web编辑器中选择KindEditor xff0c 主要是看重它的小巧 一个JS文件 两个CSS文件和一个GIF图片就是它的全部 所以在页面上的加载速度很快 xff0c 而且功能也相对齐全 目前Kin
  • hexo+Ubuntu+github搭建个人博客(详细)

    菜鸟初步搭建须知 xff08 是我没错 xff0c 备忘 xff09 相应知识 会一些基本的Linux命令和vim的操作命令 可以在实验楼上入门学习网上已经浏览了 官方文档安装ubuntu和git xff08 因为最近在学习用ubuntu
  • Dispatcher.BeginInvoke()方法使用不当导致UI界面卡死的原因分析

    前段时间 xff0c 公司同事开发了一个小工具 xff0c 在工具执行过程中 xff0c UI 界面一直处于卡死状态 通过阅读代码发现 xff0c 主要是由于 Dispatcher BeginInvoke 方法使用不当导致的 本文将通过一个
  • macos可以识别U盘但看不到U盘里的文件

    文章目录 1 问题描述2 解决 1 问题描述 以前U盘插上都可以直接操作的 xff0c 但是突然就不行了 可以看到 xff0c 我这个U盘是FAT32的格式网上有些说mac不可以直接读取fat32或者ntfs格式的文件 xff0c 或者说可
  • List的Clear方法与RemoveAll方法用法小结

    示例代码 using System using System Collections Generic namespace ListClearExp class Program static void Main string args Lis
  • 利用C#访问注册表获取软件的安装路径

    绝大多数软件 xff0c 基本上都会在注册表中记录自己的名字和安装路径信息 在注册表中记录这些信息的位置是 xff1a HKEY LOCAL MACHINE SOFTWARE Microsoft Windows CurrentVersion
  • 使用ValidationRule类来检查用户输入的有效性

    1 新建WPF应用程序ValidationRuleExp 整个程序的结构如下图所示 程序运行起来后的效果如下图所示 用户操作程序时 xff0c 先输入固话 手机 Email 个人网站等信息 xff0c 再点击右侧的 点我记住你 按钮 xff
  • 关闭窗体后,进程仍然在运行的问题重现与解决

    1 问题陈述 在开发中 xff0c 遇到这样一个问题 xff1a 点击程序主窗体右上角的叉号关闭应用程序后 xff0c 程序的进程却没有关闭 通过查阅资料 xff0c 了解到 xff0c 产生此类问题的原因主要有以下两点 xff1a 1 x
  • Python判断一个字符串是否包含子串的几种方法

    1 使用成员操作符 in span class hljs prompt gt gt gt span s 61 span class hljs string 39 nihao shijie 39 span span class hljs pr
  • easyui-datagrid获取行和列数据

    1 获取当前行 span class hljs keyword var span row 61 span class hljs string 39 dg 39 span datagrid span class hljs string 39
  • No plugin found for prefix ‘tomcat7’ in the current project and in the plugin groups

    idea中开发javaweb应用 xff0c 使用mvn tomcat7 run命令运行应用时 xff0c 需要配置tomcat的maven插件 在没有配置的情况下会出现下面的错误提示 ERROR No plugin found for p
  • C#中的IComparable和IComparer接口

    C 中 xff0c 自定义类型 xff0c 支持比较和排序 xff0c 需要实现IComparable接口 IComparable接口存在一个名为CompareTo 的方法 xff0c 接收类型为object的参数表示被比较对象 xff0c
  • C#接口汇总

    1 IComparable和IComparer接口 用于比较和排序 IComparable 可比较的 xff0c 实现该接口的类 xff0c 便具有 可比较的 特性 IComparer 比较器 xff0c 实现该接口的类 xff0c 是一个
  • Python操作环境变量

    1 使用os读取环境变量 import os os getenv 39 path 39 os environ get 39 path 39 os environ 39 path 39 2 遍历打印所有环境变量 通过访问os environ可

随机推荐

  • ITK——3. 编译remote库

    文章目录 1 在线编译 2 离线编译 2 1 下载对应的github库 2 2 编译 2 3 一点疑问 以ITKMinimalPathExtraction库为例 对应的github链接是 https github com InsightSo
  • Android Gradle编译改为mk编译

    原文地址 xff1a https www jianshu com p 8f00d4d692cd 最近出于工作需要 xff0c 要将一个模块由gradle编译改为mk方式加入源码编译 遇到了一些问题 xff0c 在这里记录一下 主要有以下几个
  • 生辰八字的计算

    我们常说的生辰八字 xff0c 是用天干地支表示人出生的年 月 日 时 xff0c 合起来是八个字 十天干 xff0c 甲乙丙丁午己庚辛壬癸 十二地支 xff0c 子丑寅卯辰巳午未申酉戌亥 十天干和十二地支依次相配 xff0c 如 甲子 乙
  • Androidstudio编译工程找不到对应的gradle-x.xx-all.zip文件的解决方法

    文章目录 前言一 打开gradle wrapper properties二 找到distributionUrl三 打开 gradle文件夹四 总结1 问题出现的原因2 解决思路 前言 AndroidStudio Gradle文件下载不下来的
  • Linux 系统运维常用命令

    linux 常用命令 雪松整理 Q 群 198173206 欢迎linux 系统运维朋友加入 xff01 博客 http hi baidu com quanzhou722 blog 错误在所难免 xff0c 还望指正 xff01 61 61
  • 安装更新nessus

    Ubuntu安装更新nessus 1 sudo dpkg i nessus安装包 2 service nessusd start 启动nussus服务 3 浏览器登录 4 输入注册码 43 用户名 密码 5 等待更新nussus 插件 6
  • windows10子系统安装kali并配置图形界面、中文tab命令补全

    1 开启Windows子系统功能 2 微软商店安装kali linux子系统设置账户与root账户 root账户命令 sudo passwd root 3 安装图形界面 1 apt install xorg 安装xorg 2 apt ins
  • ubuntu安装zenmap(图形化nmap)

    如何在Linux中安装Zenmap xff1f 在Ubuntu 20 04 LTS Focal Linux上安装ZenMap 由于Zenmap在Ubuntu 20 04的官方存储库中不再可用 xff0c 因此我们必须手动下载并安装它 由于依
  • 安装appimagelauncher

    安装appimagelauncher 1 sudo add apt repository ppa appimagelauncher team stable 2 sudo apt get update 3 sudo apt install a
  • Sub-process /usr/bin/dpkg returned an error code

    在用apt get安装软件包的时候遇到E Sub process usr bin dpkg returned an error code 1 问题 xff0c 解决方法如下 xff1a 1 cd var lib dpkg 2 sudo mv
  • centos6 安装或升级svn1.8

    方法一 xff1a xff08 服务器有互联网使用该方法 xff09 centos6上默认安装的是svn1 6版本 xff0c 这个版本最大的缺点是会在每一个目录下简历一个 svn目录 xff0c 导致项目很难管理 编辑文件 xff1a v
  • ITK——4. 医学影像坐标系问题(世界坐标系、解剖坐标系和图像坐标系)

    文章目录 2 坐标系方向orientation RAI AIL 2 1 统一转为RAI方向 itk的python代码 2 2 统一转为某个方向 itk的C 代码 2 3 设置成和另一个图方向一样 itk的C 2 3 1 方法2 2 3 2
  • (1)在ubuntu系统中安装docker

    目录 准备工作开始安装安装Docker引擎 卸载 准备工作 更新apt软件包索引和安装软件包 xff0c 以允许apt通过HTTPS使用repository span class token function sudo span span
  • 八十、尚硅谷kylin单机版环境——安装MySQL

    安装mysql xff08 网络正常 可以yum xff09 xff08 一 xff09 打开安装包所在地 root 64 kylin141 cd opt install root 64 kylin141 install ls apache
  • UNIX 环境高级编程之我见

    UNIX环境高级编程 xff08 第二版 xff09 xff08 人民邮电出版社 xff09 美 W Richard Stevens amp Stephen A Rago 著 本书的主要结构分为以下几个部分 xff1a xff08 1 xf
  • mac brew install

    brew cask install myprogram base darren 64 Darren 2 project brew cask install docker Error Unknown command cask brew ins
  • mac/linux 系统批量计算文件md5命令

    find type f print0 xargs 0 md5
  • android 电池充电状态记录

    摘抄源码记录下 http androidxref com 9 0 0 r3 xref frameworks native include batteryservice BatteryServiceConstants h This file
  • python 输入三个变量,然后按小到大输出(解析)

    python 实例解析 xff08 1 xff09 vim 2 python py x 61 int input 39 please input x 39 y 61 int input 39 please input y 39 z 61 i
  • Linux系统调用Hook姿势总结

    http www cnblogs com LittleHann p 3854977 html 主题 Linux 相关学习资料 http xiaonieblog com post 61 121 http hbprotoss github io