ichunqiu-try to pwn-II.md

FSP-stack pivot-ROP-try to pwn-II

本文参考的writeup原文

上次(这里放上次的文章链接)由于时间原因,比较仓促、敷衍的完成了一篇大致的writeup

但是其中还有很多的问题存在;这篇writeup旨在解决遇到的很多问题、疑惑;此外,还向别人请教了一些知识点,收获很大

题目相关

题目依然是ichunqiu中的try to pwn;也是2016百度-ctf的一道题目。

题目回顾

这里只简单的概括一下代码的漏洞点以及利用方式:

代码漏洞点在于:

  • welcome()中scanf()函数输入name时没有对name长度做限制和检查,导致写入name(位于bss段)的内容会覆盖到0x080efa00的内容(FILE结构体的首地址);

  • 接着在menu()中输入3,调用下述代码

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

    我们可以通过构造Fake FILE使得dword_0x080efa00内容不空,进而执行fclose()函数,而fclose()在执行的过程中会调用一些lib函数,这些函数地址是从Fake FILE中获取的,于是我们就可以在FILE结构体中修改函数地址,然后想办法构造可用的ROP

  • ROP的构造基本就是想办法构造execve("/bin/sh")等获取shell

题目难点分解分析

本部分如有不明白的地方,请看“学习内容”相应模块。

FSP

这里需要参考“学习内容”的fclose()源码,由于函数调用了fclose(),我们需要了解fclose()的实际运行流程,才能具体知道如何构造地址。

构造Fake File有两个关键点:

  1. 如何构造IO_Structure使得close()函数能够按照我们希望的流程运行?
  2. 找到调用vtable中的函数,然后修改该函数在FILE结构体中的位置的内容为我们希望的执行地址
_IO_FILE&_IO_jump_t

通过下边的fclose()源码可以看到其中调用的函数有这些:

_IO_do_flush
_IO_unsave_markers
_IO_SYSCLOSE
_IO_have_wbackup...
_IO_setb...
_IO_un_link

其中_IO_SYSCLOSE可以调用到_IO_jump_t中的__close(其他的我不是很清楚);所以可以考虑构造合适的flags使得程序运行到_IO_SYSCLOSE;然后程序就会进入FILE结构体中__close地址函数。如果我们将结构体中该位置修改为我们自己的执行地址,就可以实现程序控制流劫持了。

这个题目我没有有做出来,是学习别人的Writeup代码,write-up中将FILE结构体构造为:

FILE start
-------------------------------------IO FILE structure start	0
/bin	/sh\x00		0		0									0x10
......
-------------------------------------IO FILE structure end		0x94
-------------------------------------IO jump t start			0x94
......
our_address(0+0x94+0x44)
-------------------------------------IO jump t end
FILE end

其中需要说明的是:

在32bit系统中,_IO_jump_t的偏移是0x94,64bit系统中偏移是0xE8
在io_jump_t中,__close的偏移为17*4bytes;转化为hex格式就是0x44
这里代替flags的就是"/bin";这里感觉并不只能是/bin/sh,只要能满足close()函数的check都可以。(但是暂时还没有测试)
上述内容中是可以满足劫持程序控制流的,和我们最终的有点区别。
stack pivot

由于我们只能(?待商榷)控制一个程序转移地址,所以我们需要将函数栈迁移到我们能够控制的可执行的地址空间中,使得我们有足够的空间执行我们的ROP代码。于是就考虑到使用栈迁移的方法。栈迁移常见的套路见”学习内容“相关部分。

这篇writeup中使用的是第一种

xchg registerContainingFakeStackAddress, ESP

那么利用方式是,先使用ROPgadget或者ropper找到这个汇编语句的地址,然后将该地址放在上一步中找到的__close()的位置,实现在控制流被劫持的第一步就将栈迁移。

ROPgadget --binary fake --only "xchg|ret"
#0x08048f66 : xchg eax, esp ; ret

第一条命令执行之后有很多输出,但是我们需要找到一个合适的寄存器,该寄存器满足:registerContainingFakeStackAddress。找到eax,于是拿到了第一个转移地址。

xchg eax,esp之后是ret;所以需要给出一个ret的返回地址,因此“返回地址”所在的地址应该是在”此时的esp所指向的位置“,作为第二条执行命令。此时构造情况如下:

FILE start
-------------------------------------IO FILE structure start	0
/bin	/sh\x00		0		0									0x10
......
-------------------------------------IO FILE structure end		0x94
-------------------------------------IO jump t start			0x94
......
our_address即0x08048f66写入到(0+0x94+0x44)这里0+0x94+0x44=0x080efa5c
FILE end

这里还有点迷。。。。。

不知道是为什么把0x080efa5写在这里…

ROP

原文使用了ropper工具,然后提到对生成的ropchain进行修改和优化,,,,我不知道是如何做的。

学习内容

FSP
File structure

http://tacxingxing.com/2018/02/09/fsp/#toc_7

https://www.anquanke.com/post/id/84987

我们经常使用的stdin、stdout、stderr等与FILE-Structure有关的操作使用的都是_IO_FILE_plus,其结构为:

 /*We always allocate an extra word following an _IO_FILE.
   This contains a pointer to the function jump table used.
   This is for compatibility with C++ streambuf; the word can
   be used to smash to a pointer to a virtual function table. */
struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};

其中的_IO_FILE和_IO_jump_t结构放在fclose()下部,便于在查看fclose()源码时查询。

flose()源码

这里列出的时glibc2.23的源码;位于/libio/fileops.c中。(存在一个问题,见问题1)

int _IO_new_file_close_it (_IO_FILE *fp)
{
  int write_status;
  if (!_IO_file_is_open (fp))
    return EOF;
  if ((fp->_flags & _IO_NO_WRITES) == 0&& (fp->_flags & _IO_CURRENTLY_PUTTING) != 0)
    write_status = _IO_do_flush (fp);
  else
    write_status = 0;
    
  _IO_unsave_markers (fp);
  
  int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0 ? _IO_SYSCLOSE (fp) : 0);

  /* Free buffer. */
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  if (fp->_mode > 0)
    {
      if (_IO_have_wbackup (fp))_IO_free_wbackup_area (fp);
      _IO_wsetb (fp, NULL, NULL, 0);
      _IO_wsetg (fp, NULL, NULL, NULL);
      _IO_wsetp (fp, NULL, NULL);
    }
#endif
  _IO_setb (fp, NULL, NULL, 0);
  _IO_setg (fp, NULL, NULL, NULL);
  _IO_setp (fp, NULL, NULL);

  _IO_un_link ((struct _IO_FILE_plus *) fp);
  fp->_flags = _IO_MAGIC|CLOSED_FILEBUF_FLAGS;
  fp->_fileno = -1;
  fp->_offset = _IO_pos_BAD;
  return close_status ? close_status : write_status;
}
libc_hidden_ver (_IO_new_file_close_it, _IO_file_close_it)

源码中出现的一些宏定义参数:glibc/libio/libio.h

_IO_NO_WRITES:0x0008 
_IO_CURRENTLY_PUTTING 0x0800
调试过程

jump not taken eax=0

jump not taken eax=8

_IO_unsave_markers 参数:/bin/sh

fp->_flags2:BYTE PTR [ebx+0x3c];将其与0x20(_IO_FLAGS2_NOCLOSE)比较结果相等;然后跳转_IO_un_link???还是_IO_SYSCLOSE???感觉像是这个欸

#define _IO_SYSCLOSE(FP) JUMP0 (__close, FP)

然后就调用了sys_close的系统调用?在内存中可以看到是eax+0x44

_IO_FILE
/*_IO_FILE结构体*/
/* libio/libio.h */
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;

  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;
# endif
  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_jump_t
/*_IO_jump_t虚表结构体*/
/* in libio/libioP.h */
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
};
stack pivot
img
什么情况下需要利用stack pivot

1.栈溢出的字节比较少,无法直接利用溢出字节进行Rop
2.栈地址未知并且无法泄露,但是利用某些利用技术时必须要知道栈地址,就可以通过stack pivot将栈劫持到相应的区域
3.stack pivot能够使得一些非栈溢出的漏洞变成为栈溢出漏洞从而进行攻击,典型:可以将程序劫持到heap空间中

如果在尝试了直接Rop发现比较难实现,并且程序中有可以利用进行读写的函数,就可以考虑stack pivot

stack pivot的利用条件

1.存在内容可控的内存,位置已知,拥有读写的权限,有几个典型的位置可供选择

一个是bss段末有较大的空间,因为进程内存按页分配,分配给bss段的内存大小至少一个页(4k,0x1000)大小,一般bss段的内容是用不了这么大的空间的,并且bss段分配的内存页拥有读写权限,是stack pivot的好目标

另一个是heap空间,这个不用赘述了,但是需要注意泄露堆地址

2.控制rsp(esp),需要相应的Godgets

3.控制流可以劫持

stack pivot的常见套路

Stackpivoting techniques

Stack Pivoting—A1Logic Research

How to build the chain (chaining basics)

Here are a few possible ways:

1.xchg registerContainingFakeStackAddress, ESP
2.add ESP, SomeConstant	//we can execute this multiple times to get our desired value
3.sub ESP, SomeConstant	//we can execute this multiple times to get our desired value
4.mov ESP, registerContainingFakeStackAddress

hack the function prologue because they modify ESP there?
hack the function epilogue because they modify ESP there?
The trick here is to be creative and figure out how any(or any sequence) of instructions can be used to get our desired value into ESP.
ROP
roppper

https://github.com/sashs/Ropper

 /usr/local/bin/ropper --file fake --chain "execve cmd=/bin/sh" --badbytes 000a0d0b09
canary

源代码中出现了readgsword(:0x14)

这个与checksec得到的stack canary found有关。

canary的实现过程就是基于”将一个值读取存放在栈中,然后程序即将执行返回地址的时候,将这个值取出来进行比较,如果发现不同,就说明栈被修改“。

canary分析

asm&&command&&IDA
edi&&edx
EDX 则总是被用来放整数除法产生的余数。
ESI/EDI分别叫做"源/目标索引寄存器"(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串.
command-file

可以看到静态链接的信息

如果是静态链接,那么ldd不能看 因为ldd查看的是动态链接库的信息

file binary 可以看到静态链接库的版本,然后查看相应版本的源码,就可以知道很多内部信息。

command-ldd

Like one of the comment says - you tried using ldd on 64 bit system to inspect a 32-bit ELF object. ldd uses the standard dynamic linker to trace the dependencies, so if your platform doesn’t have the linker required by the ELF object being inspected, ldd fails. Readelf and objdump are more robust in these situations.

IDA Go

按‘G’,直接输入地址

scanf()过滤

这个是在实验过程中想知道为什么第二个图画红线的部分不能输入到内存中。

Same goes for 0x09 to 0x0c. But 0x01 to 0x08, 0x0e and above are working.

The scanf function skips over leading whitespace, with whitespace being the set of characters for which the isspace macro/function returns true.

In the standard locale, this set of characters consists of \t (0x09), \n (0x0a), \v (0x0b), \f (0x0c), and \r (0x0d). And, of course, the blank character (0x20).

test-popeax-correct

test-popeax-wrong

mprotect

mprotect()函数 Unix/Linux

Linux中mprotect()函数的用法

在Linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性。
函数原型如下:
#include <unistd.h> 
#include <sys/mmap.h> 
int mprotect(const void *start, size_t len, int prot); 
mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。

prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:
1)PROT_READ:表示内存段内的内容可写;
2)PROT_WRITE:表示内存段内的内容可读;
3)PROT_EXEC:表示内存段中的内容可执行;
4)PROT_NONE:表示内存段中的内容根本没法访问。

需要指出的是,锁指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。

这里需要注意的是,参数中是size_t len,所以如果len=1024,那么实际覆盖的内存区域大小应该是1024*4bytes.

Boches

https://baike.baidu.com/item/BOCHS

问题

1.FSP-为什么要设置io_save_base

io_save_based指向非当前获取区域的指针??

#io_save_base???下述测试结果的原因?

fake_file += p32(0x80efa10) ok

fake_file += p32(0x80efa14) ok

fake_file += p32(0x80efa18) #Got EOF while reading in interactive

2.如何确定glibc版本

动态的可以用很多工具,readelf objdump ldd等

但是这里是staticlly linked;目前我只通过file command找到对应的gnu/linux版本为:2.6.32至于为什么是glibc 2.23是因为直接google了_IO_new_file_close_it,下边直接出现的…所以放在这里还不是很靠谱。

???找到libc的版本

0240| 0xff93587c --> 0x804902d (<__libc_start_main+285>: cmp DWORD PTR [esp],0x6)

是不是可以通过这个东西找到glibc版本

我记得以前有过。。。。忘记了

3.cannot access the memmory
4.gdb.attach()在remote情况下不能用吗?
5.command

gdb ./fsp_vuln core;bt;info reg

objdump objdump -T tester | grep GLIBC_2.1.*

6.p32

这句话的作用是在这个地址上写一执行地址么?

io_jump_t = p32(0x80efa5c)
io_jump_t += p32(0x08048f66)

这里暂时还不太懂

rop+="/bin/sh/x00"

rop+="/x0a/x00/x00/x00"

rop+=p32(0xdeadbeef)

rop+=p32(0x0804aaa0)

rop+=p32(0)

rop+=p32(‘10’) #Got EOF while reading in interactive;

rop+=p32(‘0xa’) #Got EOF while reading in interactive

7.pop

pop是将栈顶内容作为地址取出来,然后取地址所指向的值?

8.&运算符
9.汇编

mov eax,DWORD PTR [ebx+0x38]含义??

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值