网络靶场实战-物联网安全qiling框架初探

背景

Qiling Framework是一个基于Python的二进制分析、模拟和虚拟化框架。它可以用于动态分析和仿真运行不同操作系统、处理器和体系结构下的二进制文件。除此之外,Qiling框架还提供了易于使用的API和插件系统,方便使用者进行二进制分析和漏洞挖掘等工作。其创始人是一名IoT Hacker,创建qiling的初衷便是解决在研究IoT时遇到的种种问题,这也是为什么上一小节说qiling框架比unicorn框架更加适合IoT研究初学者。

qiling使用基础

qiling框架和AFLplusplus安装

sudo apt-get update
sudo apt-get install -y build-essential python3-dev automake cmake git flex bison libglib2.0-dev libpixman-1-dev python3-setuptools cargo libgtk-3-dev
sudo apt-get install -y lld-14 llvm-14 llvm-14-dev clang-14
sudo apt-get install -y gcc-$(gcc --version|head -n1|sed 's/\..*//'|sed 's/.* //')-plugin-dev libstdc++-$(gcc --version|head -n1|sed 's/\..*//'|sed 's/.* //')-dev
pip3 install qiling
git clone https://github.com/AFLplusplus/AFLplusplus
make -C AFLplusplus
cd AFLplusplus/unicorn_mode
./build_unicorn_support.sh

程序仿真

    首先我们需要克隆qiling仓库,仓库中一些实例脚本可供我们学习。

git clone --recurse-submodules https://github.com/qilingframework/qiling.git

    一个简单的示例:

#include <stdio.h>
#include <stdlib.h>
# gcc test.c -o test
# 注意:编译程序的主机libc需要与rootfs glibc版本(libc-2.7.so)相对应,其他架构同理
int main(){printf("hello world!");return 0;
}

    使用qiling编写一个简单的仿真脚本。

from qiling import *
from qiling.const import QL_VERBOSE
# 导入qiling模块和qiling.const模块中的QL_VERBOSE常量if __name__ == "__main__":#创建Qiling对象,实例中三个参数分别为:path(仿真程序路径)、rootfs(仿真程序文件系统目录)和verbose(输出信息参数),除此外还可以设置env和log_plain参数。ql = Qiling(["./x8664_linux_symlink/test"], "./x8664_linux_symlink",verbose=QL_VERBOSE.DEBUG)#运行Qiling对象的run()方法,开始执行仿真程序ql.run()

    这里的verbose(输出信息参数)有如下级别及其作用:

图片

图片

VFS劫持

    x86_fetch_urandom程序的作用为打开/dev/urandom文件,生成随机数。当qiling仿真x86_fetch_urandom程序时,环境需要用到仿真文件系统,我们就需要用到VFS劫持,这样就可以模拟修改文件系统。下面的代码中为仿真虚拟路径 "/dev/urandom" 会被映射到宿主系统上的现有"/dev/urandom"文件。当模拟程序将访问 /dev/random 时,将改为访问映射文件。

from qiling import Qilingif __name__ == "__main__":ql = Qiling(["x86_linux/bin/x86_fetch_urandom"], "x86_linux")ql.add_fs_mapper(r'/dev/urandom', r'/dev/urandom')ql.verbose=0ql.run()

图片

    如果我们想要控制虚拟文件'/dev/urandom'的交互结果,可以继承QlFsMappedObject类,并可自定义read、write、fstat、ioctl、readline等方法。

from qiling import Qiling
from qiling.os.mapper import QlFsMappedObjectclass FakeUrandom(QlFsMappedObject):def read(self, size: int) -> bytes:return b"\x01" #可以修改读取返回结果def fstat(self) -> int:return -1def close(self) -> int:return 0if __name__ == "__main__":ql = Qiling(["x86_linux/bin/x86_fetch_urandom"], "x86_linux")ql.add_fs_mapper(r'/dev/urandom', FakeUrandom())ql.run()

图片

函数hook

    下面示例中,我们给str1和str2俩个变量内存中分别复制"abcdef"和"ABCDEF"字符串。正常执行完毕后会打印出"str1 大于 str2"。我们可以使用qiling框架劫持strcmp实现为hook strcmp函数的效果,使其执行到不同分支的结果。

#include <stdio.h>
#include <string.h>//cd ./x8664_linux/
//gcc demo.c -o testint main ()
{char str1[15];char str2[15];int ret;strcpy(str1, "abcdef");strcpy(str2, "ABCDEF");ret = strcmp(str1, str2);if(ret < 0){printf("str1 小于 str2");}else if(ret > 0){printf("str1 大于 str2");}else{printf("str1 等于 str2");}return(0);
}

    以下代码为hook strcmp函数,并通过修改rax寄存器改变执行流程。

from qiling import *
from qiling.const import *# 自定义strcmp hook函数。当程序执行strcmp函数退出时,会调用此函数,并且在比较完毕后,将 rax 寄存器的值修改为 0,表示相等。
def hook_strcmp(ql,*args):
# qiling框架的寄存器取值为ql.arch.reg.xxxrax = ql.arch.regs.raxprint("hook_addr_rax:",hex(rax))ql.arch.regs.eax = 0 # 0:等于; -1:小于 ;1:大于# 使用 ql.os.set_api 函数为 strcmp 设置hook函数,第一个参数为要hook的函数名,第二个参数为自定义hook函数,第三个参数为hook类型,这里为退出时触发hook函数。
def hook_func(ql):ql.os.set_api('strcmp',hook_strcmp,QL_INTERCEPT.EXIT) # 也可以使用ql.hook_address()函数进行hook,使用方法为ql.hook_address(hook_strcmp,0xXXXXXXXX)if __name__ == "__main__":ql = Qiling(["./x8664_linux/test"],"./x8664_linux",verbose=QL_VERBOSE.DEBUG)hook_func(ql)#ql.debugger = "gdb:0.0.0.0:12345"ql.run()

图片

    定义hook函数时hook类型参数有以下三种:

图片

qiling使用实例

使用qiling解密CTF赛题

    当我们掌握了最基础的三个用法后,我们可以测试一个简单的例子来加深对qiling框架的理解。以上一小节中unicorn解密ctf题目为例,我们先简单写一个运行脚本。这里的ql.debugger="gdb:0.0.0.0:12345"为开启gdbserver服务,我们可以使用ida或者gdb进行调试。

图片

    简单运行后发现程序和上一小节中unicorn的运行状况类似。由于这里我设置了multithead为True,所以这里会比上一小节中unicorn的解密速度快不少。但是还是在有限时间内只输出4个字符。

图片

    当我们将verbose设置为QL_VERBOSE.DISASM便可观察模拟执行的汇编指令,根据汇编指令我们明显看到程序在call 0x400670处进行了递归调用(或使用调试器调试查看),导致解密时间非常长。所以我们需要进行代码优化,思路为使用栈空间来保存一个不同输入参数以及对应计算结果的字典来避免重复计算。

图片

   这里qiling由于是由unicorn开发而来,所以很多用法和unicorn相似。

from qiling import *
from qiling.const import *
from pwn import *def hook_start(ql):arg0 = ql.arch.regs.rdir_rsi = ql.arch.regs.rsiarg1 = u32(ql.mem.read(r_rsi,4))if (arg0,arg1) in direct:(ret_rax,ret_ref) = direct[(arg0,arg1)]ql.arch.regs.rax = ret_raxql.mem.write(r_rsi,p32(ret_ref))ql.arch.regs.rip = 0x400582else:ql.arch.stack_push(r_rsi)ql.arch.stack_push(arg1)ql.arch.stack_push(arg0)def hook_end(ql):arg0 = ql.arch.stack_pop()arg1 = ql.arch.stack_pop()r_rsi = ql.arch.stack_pop()ret_rax = ql.arch.regs.raxret_ref = u32(ql.mem.read(r_rsi,4))direct[(arg0,arg1)] = (ret_rax,ret_ref)def solve(ql):start_address = 0x400670end_address = 0x4006f1end_address2 = 0x400709ql.hook_address(hook_start,start_address)ql.hook_address(hook_end,end_address)ql.hook_address(hook_end,end_address2)if __name__ == '__main__':path = ["./x8664_linux_symlink/test"]rootfs = "./x8664_linux_symlink"direct = {}ql = Qiling(path, rootfs,verbose=QL_VERBOSE.DEFAULT)solve(ql)ql.run()

运行后便会打印出解密结果。

图片

    除了上一小节中的ctf题目掌握qiling的使用外,我们还可通过qilinglab来加深对qiling框架的使用。qilingLab是由11个小挑战组成的二进制程序,用来帮助新手快速熟悉和掌握 Qiling 框架的基本用法。官方提供了aarch64程序的解题方法,我们根据这个作为参考解密一下x86_64架构的练习程序。

    x86_64程序下载(https://www.shielder.com/attachments/qilinglab-x86_64)

    首先运行程序,给我们提示,challenges会造成程序崩溃,只有当我们解出相应challenge后才会显示信息。

图片

    我们可以通过ida逆向以及编写qiling脚本进行动态调试来完成这些challenge。

图片

    最终的解密脚本如下:

from qiling import *
from pwn import *
from qiling.const import *
from qiling.os.mapper import QlFsMappedObject
import os
import structdef hook_cpuid(ql, address, size):if ql.mem.read(address, size) == b'\x0F\xA2':regs = ql.arch.regsregs.ebx = 0x696C6951regs.ecx = 0x614C676Eregs.edx = 0x20202062regs.rip += 2def challenge11(ql):begin, end = 0, 0for info in ql.mem.map_info:#print("=====")#print(info)#print("=====")if info[2] == 5 and 'qilinglab-x86_64' in info[3]:begin, end = info[:2]#print("begin_addr",begin)#print("end_addr",end)ql.hook_code(hook_cpuid, begin=begin, end=end)class cmdline(QlFsMappedObject):def read(self, expected_len):return b'qilinglab'def close(self):return 0def challenge10(ql):ql.add_fs_mapper('/proc/self/cmdline', cmdline())def hook_tolower(ql):return 0def challenge9(ql):ql.os.set_api('tolower', hook_tolower)def find_and_patch(ql, *args, **kw):MAGIC = 0x3DFCD6EA00000539magic_addrs = ql.mem.search(p64(MAGIC))#print("magic_address:",hex(magic_addrs))for magic_addr in magic_addrs:malloc1_addr = magic_addr - 8malloc1_data = ql.mem.read(malloc1_addr, 24)string_addr, _ , check_addr = struct.unpack("QQQ",malloc1_data)if ql.mem.string(string_addr) == "Random data":ql.mem.write(check_addr, b"\x01")breakdef challenge8(ql):base_addr = ql.mem.get_lib_base(os.path.split(ql.path)[-1])#print("base_addr",hex(base_addr))ql.hook_address(find_and_patch, base_addr+0xFB5)def hook_sleep(ql):return 0def challenge7(ql):ql.os.set_api('sleep',hook_sleep)def hook_rax(ql):ql.arch.regs.rax = 0def challenge6(ql):base_addr = ql.mem.get_lib_base(os.path.split(ql.path)[-1])#print("base_addr",hex(base_addr))hook_addr = base_addr + 0xF16ql.hook_address(hook_rax, hook_addr)def hook_rand(ql):ql.arch.regs.rax = 0def challenge5(ql):ql.os.set_api('rand',hook_rand)def enter_forbidden_loop_hook(ql):ql.arch.regs.eax = 1def challenge4(ql):base = ql.mem.get_lib_base(os.path.split(ql.path)[-1])hook_addr = base + 0xE43print("qiling binary hookaddr:",hex(hook_addr))ql.hook_address(enter_forbidden_loop_hook, hook_addr)class FakeUrandom(QlFsMappedObject):def read(self, size: int) -> bytes:if size == 1:return b"\x42"else:return b"\x41" * sizedef close(self) -> int:return 0def hook_getrandom(ql, buf, buflen, flags):if buflen == 32:data = b'\x41' * buflen # b'\x41' = Aql.mem.write(buf, data)ql.os.set_syscall_return(buflen)else:ql.os.set_syscall_return(-1)def challenge3(ql):ql.add_fs_mapper(r'/dev/urandom', FakeUrandom())ql.os.set_syscall("getrandom", hook_getrandom)def my_uname_on_exit_hook(ql, *args):rdi = ql.arch.regs.rdiprint(f"utsname address: {hex(rdi)}")ql.mem.write(rdi, b'QilingOS\x00')ql.mem.write(rdi + 65 * 3, b'ChallengeStart\x00')def challenge2(ql):ql.os.set_api("uname", my_uname_on_exit_hook, QL_INTERCEPT.EXIT)def challenge1(ql):ql.mem.map(0x1000, 0x1000, info='challenge1')ql.mem.write(0x1337, p16(1337))if __name__ == '__main__':path = ["./x8664_linux/qilinglab-x86_64"]rootfs = "./x8664_linux"ql = Qiling(path, rootfs,verbose=QL_VERBOSE.OFF)challenge1(ql)challenge2(ql)challenge3(ql)challenge4(ql)challenge5(ql)challenge6(ql)challenge7(ql)challenge8(ql)challenge9(ql)challenge10(ql)challenge11(ql)#ql.debugger = "gdb:0.0.0.0:12345"ql.run()

    运行后,所有的challenge都会显示SOLVED。

图片

qiling设备仿真

    qiling提供了路由器仿真案例,该脚本路径为qiling/example路径下

#!/usr/bin/env python3
# 1. Download AC15 Firmware from https://down.tenda.com.cn/uploadfile/AC15/US_AC15V1.0BR_V15.03.05.19_multi_TD01.zip
# 2. unzip
# 3. binwalk -e US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin
# 4. locate squashfs-root
# 5. rm -rf webroot && mv webroot_ro webroot
#
# notes: we are using rootfs in this example, so rootfs = squashfs-root
#
import os, socket, threading
import sys
sys.path.append("../../../")
from qiling import Qiling
# 从qiling.const中导入QL_VERBOSE,指定qiling的日志输出级别
from qiling.const import QL_VERBOSE# 定义patcher函数,用于跳过网卡信息检测。在前面小节我们仿真tenda路由器时,路由器httpd程序在初始化网络时会检查网卡名称是否为br0。这里脚本直接将代码执行前内存中的br0字符串替换成了lo,从而跳过检查。
def patcher(ql: Qiling):br0_addr = ql.mem.search("br0".encode() + b'\x00')for addr in br0_addr:ql.mem.write(addr, b'lo\x00')# 定义nvram_listener函数,使用该函数监听Unix套接字,并在收到消息时返回数据。
def nvram_listener():server_address = 'rootfs/var/cfm_socket'data = ""try:os.unlink(server_address)except OSError:if os.path.exists(server_address):raisesock = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)sock.bind(server_address)sock.listen(1)while True:connection, _ = sock.accept()try:while True:data += str(connection.recv(1024))if "lan.webiplansslen" in data:connection.send('192.168.170.169'.encode())else:breakdata = ""finally:connection.close()# 定义myvfork函数,仿真程序在执行系统调用vfork时被调用,返回值0。
def myvfork(ql: Qiling):regreturn = 0ql.log.info("vfork() = %d" % regreturn)return regreturn
# 仿真主函数,生成qiling实例和添加VFS映射。
def my_sandbox(path, rootfs):print("path:",path)print("rootfs",rootfs)ql = Qiling(path, rootfs, verbose=QL_VERBOSE.DEBUG)print("ql:",ql)ql.add_fs_mapper("/dev/urandom","/dev/urandom")ql.hook_address(patcher, ql.loader.elf_entry)ql.debugger = Falseif ql.debugger == True:ql.os.set_syscall("vfork", myvfork) # vfork函数返回0时,debugger可正常调试。ql.run()if __name__ == "__main__":# 创建后台运行的线程并执行,以便收到Unix套接字的消息时进行响应。nvram_listener_therad = threading.Thread(target=nvram_listener, daemon=True)nvram_listener_therad.start()# 运行仿真实例my_sandbox(["rootfs/bin/httpd"], "rootfs")

    当我们运行脚本后,会显示路由器的ip和端口,当我们发现本地的8080正在监听时,说明设备已经仿真成功。

图片

    仿真成功后可访问http://localhost:8080查看效果:

图片

    在后面的小节中,我们会学习对仿真路由器设备进行fuzz。其中最为重要的一步便是编写仿真脚本,后续在我们分析好固件程序中要fuzz地址范围后,只有仿真设备可以顺利触发保存快照的功能,才可保证fuzz的正确性。

qiling fuzz

    qiling框架可以使用AFLplusplus对arm架构程序进行fuzz测试,测试代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Program that will crash easily.
#define SIZE (10)int fun(int i)
{char *buf = malloc(SIZE);char buf2[SIZE];while ((*buf = getc(stdin)) == 'A'){buf[i++] = *buf;}strncpy(buf2, buf, i);puts(buf2);return 0;
}int main(int argc, char **argv)
{return fun(argc);
}

    qiling提供的fuzz脚本如下:

#!/usr/bin/env python3
"""
Simple example of how to use Qiling together with AFLplusplus.
This is tested with the recent Qiling framework (the one you cloned),
afl++ from https://github.com/AFLplusplus/AFLplusplusAfter building afl++, make sure you install `unicorn_mode/setup_unicorn.sh`Then, run this file using afl++ unicorn mode with
afl-fuzz -i ./afl_inputs -o ./afl_outputs -m none -U -- python3 ./fuzz_x8664_linux.py @@
"""# No more need for importing unicornafl, try ql.afl_fuzz instead!import sys, os
from binascii import hexlify
sys.path.append("../../..")
from qiling import *
from qiling.extensions import pipe
from qiling.extensions.afl import ql_afl_fuzzdef main(input_file, enable_trace=False):ql = Qiling(["./arm_fuzz"], "../../rootfs/arm_qnx", console=enable_trace)# 设置ql的标准输入为进程的标准输入ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno())# 如果没有启用控制台追踪,则将标准输出和标准错误流设置为Nullif not enable_trace:ql.os.stdout = pipe.NullOutStream(sys.stdout.fileno())ql.os.stderr = pipe.NullOutStream(sys.stderr.fileno())def place_input_callback(ql: Qiling, input: bytes, _: int):# 设置fuzz输入点ql.os.stdin.write(input)return Truedef start_afl(_ql: Qiling):# 设置fuzz实例ql_afl_fuzz(_ql, input_file=input_file, place_input_callback=place_input_callback, exits=[ql.os.exit_point])# 获取libc的基地址LIBC_BASE = int(ql.profile.get("OS32", "interp_address"), 16)# 设置hook函数,用于处理SignalKill信号ql.hook_address(callback=lambda x: os.abort(), address=LIBC_BASE + 0x38170)# main函数地址main_addr = 0x08048aa0# 设置hook函数,在main函数运行时调用start_afl函数ql.hook_address(callback=start_afl, address=main_addr)# 若启用控制台追踪,则将设置相关信息输出if enable_trace:# The following lines are only for `-t` debug outputmd = ql.arch.disassemblercount = [0]def spaced_hex(data):return b' '.join(hexlify(data)[i:i+2] for i in range(0, len(hexlify(data)), 2)).decode('utf-8')def disasm(count, ql, address, size):buf = ql.mem.read(address, size)try:for i in md.disasm(buf, address):return "{:08X}\t{:08X}: {:24s} {:10s} {:16s}".format(count[0], i.address, spaced_hex(buf), i.mnemonic,i.op_str)except:import tracebackprint(traceback.format_exc())def trace_cb(ql, address, size, count):rtn = '{:100s}'.format(disasm(count, ql, address, size))print(rtn)count[0] += 1ql.hook_code(trace_cb, count)# okay, ready to roll.# try:ql.run()# except Exception as ex:# # Probable unicorn memory error. Treat as crash.# print(ex)# os.abort()os._exit(0) # that's a looot faster than tidying up.if __name__ == "__main__":if len(sys.argv) == 1:raise ValueError("No input file provided.")if len(sys.argv) > 2 and sys.argv[1] == "-t":main(sys.argv[2], enable_trace=True)else:main(sys.argv[1])

    AFLplusplus执行脚本如下:

#!/usr/bin/sh
AFL_AUTORESUME=1 AFL_PATH="$(realpath ../../../AFLplusplus)" PATH="$AFL_PATH:$PATH" afl-fuzz -i afl_inputs -o afl_outputs -U -- python3 ./fuzz_arm_qnx.py @@

    运行后fuzz.sh后,便会出现afl++ 运行界面,等待几秒后便出现crash。

图片

    crash的变异数据存放在afl_outputs目录下,我们可以使用xxd id:000000,xxxxxx命令查看变异数据。

#xxd id:000000,sig:06,src:000000,time:4112,execs:1077,op:havoc,rep:8 
00000000: 4141 4141 4141 4141 4141 4141 ff7f 4241 AAAAAAAAAAAA..BA
00000010: 4141 4145 4141 be41 4dff 0000 0041 4141 AAAEAA.AM....AAA
00000020: 41

总结

在这一小节中,我们简单学习了qiling框架,我们使用ctf例题以及qilinglab的11个闯关题目进行练习,熟练掌握了qiling框架的基础使用。后面的小节中,我们将使用qiling框架对仿真设备进行实例fuzz测试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/3031.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【求助】西门子S7-200PLC定时中断+数据归档的使用

前言 已经经历了种种磨难来记录我的数据&#xff08;使用过填表程序、触摸屏的历史记录和数据归档&#xff09;之后&#xff0c;具体可以看看这篇文章&#xff1a;&#x1f6aa;西门子S7-200PLC的数据归档怎么用&#xff1f;&#xff0c;出现了新的问题。 问题的提出 最新的…

网工交换基础——生成树协议(01)

一、生成树的技术概述 1、技术背景 二层交换机网络的冗余性导致出现二层环路&#xff1a; 人为因素导致的二层环路问题&#xff1a; 二层环路带来的网络问题&#xff1a; 生成树协议的概念&#xff1a; STP(Spanning Tree Protocol)是生成树协议的英文缩写。该协议可应用于在网…

代码随想录算法训练营第三十七天| 738.单调递增的数字,968.监控二叉树

目录 题目链接&#xff1a;738.单调递增的数字 思路 代码 题目链接&#xff1a;968.监控二叉树 思路 代码 总结 题目链接&#xff1a;738.单调递增的数字 思路 既然是求单调递增的数字&#xff0c;要判断相邻数字之间的大小关系。有两种遍历顺序&#xff0c;从前往后和从…

面向对象练习坦克大兵游戏

游戏玩家&#xff08;名称&#xff0c;生命值&#xff0c;等级&#xff09;&#xff0c;坦克&#xff0c;大兵类&#xff0c;玩家之间可以相互攻击&#xff0c;大兵拥有武器&#xff0c;用枪弹和反坦克炮弹&#xff0c;造成攻击不同&#xff0c;坦克攻击值固定&#xff0c;请设…

设计模式-六大原则

设计模式的六大原则是软件工程中的基本概念&#xff0c;使得构建可维护、可扩展和可重用的代码。 1.单一职责原则&#xff08;Single Responsibility Principle&#xff09;&#xff1a;一个类或方法应该只有一个引起变化的原因&#xff0c;确保类或模块的功能高度内聚。 案例&…

VMware-Linux切换桥接模式上网教程(超详细)

这里写目录标题 1. 虚拟机关机2. VMware 虚拟网络配置2.1 检查是否存在 VMnet02.2 修改桥接模式2.3 修改Linux虚拟机网络适配器 3. Linux 系统配置3.1 修改系统网卡配置3.1.1 配置项含义解释3.1.2 查看物理机网络信息3.3.3 修改配置 3.2 重启服务 4. 测试网络连接情况5. 注意事…

如何看待AIGC技术?

AIGC原名&#xff08;Artificial Intelligence Generated Content&#xff09; 技术是一种利用人工智能来自动生成内容的技术&#xff0c;这包括文本、图像、音频和视频等多种形式的创作。AIGC技术的发展标志着人工智能从1.0时代进入2.0时代的重大转变&#xff0c;它不仅体现了…

多线程优化人脸识别时遇到的问题

项目&#xff1a; face 遇到的问题&#xff1a; 其中一个线程报错&#xff0c;然后不返回相应的数据信息 问题思考&#xff1a; 每个线程独立执行&#xff0c;当其中一个线程报错后&#xff0c;随之这个线程不往下执行&#xff0c;但是其他线程还是正确执行的。 解决&…

【SpringBoot整合系列】SpringBoot整合JPA

目录 前期回顾ORM解决方案 JPA简介JPA的组成技术ORM映射元数据Java持久化API查询语言&#xff08;JPQL&#xff09; JPA的优势JPA的缺点 Spring Data JPASpring Data JPA简介Spring Data 家族Spring Data JPA、JPA和其他框架之间的关系 SpringBoot整合JPAJPA的核心注解1.依赖2.…

element-ui upload 组件 手动多次出发 submit

element 上传组件 upload 上传成功以后&#xff0c;想重新 调用 submit()函数&#xff0c;发现是不可以进行多次触发的,。 直接上解决方法&#xff0c;在上传成功后的钩子函数里添加:fileList[0l.status ready fileList是文件列表&#xff0c;status是单文件的状态改成ready就…

【Fastadmin】表格导出excel,图片显示太大

目录 1.直接导出示例 2.解决办法 3. 再次导出效果 1.直接导出示例 图片过大&#xff0c;格式错乱 2.解决办法 在js页面加入代码 // 导出图片过大处理 exportOptions: {ignoreColumn: [0, operate],onBeforeSaveToFile: function (data, fileName, type, charset, encoding,…

《深入Linux设备驱动程序内核机制》学习笔记-第4章

前言 本文是《深入Linux设备驱动程序内核机制》的读书笔记&#xff0c;本文因为是读书笔记所以抄写引用了该书中的大量内容&#xff0c;写读书笔记的目的是在写作的过程中加深对书中内容的理解。 建议读者直接阅读《深入Linux设备驱动程序内核机制》&#xff0c;这本书是Linu…

代码随想录三刷day47

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、力扣115. 不同的子序列二、力扣583. 两个字符串的删除操作三、力扣72. 编辑距离 前言 本周我们讲了动态规划之终极绝杀&#xff1a;编辑距离&#xff0c;为…

阿里云服务器部署wordpress站点

步骤如下&#xff1a; 安装宝塔登录宝塔&#xff0c;安装wordpress环境新建站点&#xff0c;新建的时候只输入ip地址就可以&#xff0c;再创建一个数据库dev1在站点文件夹中传入wordpress&#xff0c;更改站点的配置文件&#xff0c;将工作目录xxx改成xxx/wordpress修改wordpr…

浅谈 操作系统

文章目录 一、什么是操作系统1.1、对下&#xff0c;要管理各种硬件设备1.2、对上&#xff0c;要给各种软件提供一个稳定的运行环境 二、常见的操作系统有哪些 一、什么是操作系统 操作系统 其实就是一个 软件(software)。是一个用来进行 管理 的软件。进行什么样的管理呢&…

mPEG-Dansyl,Methoxy PEG Dansyl由甲氧基-聚乙二醇(mPEG)和丹磺酰氯(Dansyl)两部分组成

【试剂详情】 英文名称 mPEG-Dansyl&#xff0c;Methoxy PEG Dansyl 中文名称 聚乙二醇单甲醚丹磺酸酯&#xff0c;甲氧基-聚乙二醇-丹磺酰胺 外观性状 由分子量决定&#xff0c;液体或者固体 分子量 0.4k&#xff0c;0.6k&#xff0c;1k&#xff0c;2k&#xff0c;3.4k…

OceanBase v4.2 特性解析:Auto DOP

我们常会使用并行执行来缩短查询时间&#xff0c;以满足业务对加速查询的需求。那么&#xff0c;如何确定合适的并行资源量呢&#xff1f;在优化器中&#xff0c;并行资源量可以通过并行度&#xff08;DOP&#xff1a;Degree of Parallelism&#xff09;这来衡量。在实际业务场…

前端发送请求,显示超时取消

前端发送请求&#xff0c;显示超时取消 问题说明&#xff1a;后台接口请求60s尚未完成&#xff0c;前端控制台显示取消&#xff08;canceled&#xff09; 原因 1、前端设置60s超时则取消 2、后台接口响应时间过长&#xff0c;过长的原因统计的数据量多&#xff08;实际也才17…

react项目,文件夹和组件命名,有什么好的规范

在React项目中&#xff0c;文件夹和组件的命名规范对于保持代码的可读性和可维护性至关重要。以下是一些建议的规范&#xff1a; 文件夹命名规范 1、使用小写字母&#xff1a; 所有文件夹名称都应使用小写字母&#xff0c;避免使用大写字母或特殊字符。 2、使用短而描述性的…

LLM 安全 | 大语言模型应用安全入门

一、背景 2023年以来&#xff0c;LLM 变成了相当炙手可热的话题&#xff0c;以 ChatGPT 为代表的 LLM 的出现&#xff0c;让人们看到了无限的可能性。ChatGPT能写作&#xff0c;能翻译&#xff0c;能创作诗歌和故事&#xff0c;甚至能一定程度上做一些高度专业化的工作&#x…