1. 程式人生 > >ichunqiu-try to pwn-II.md

ichunqiu-try to pwn-II.md

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

上次(這裡放上次的文章連結)由於時間原因,比較倉促、敷衍的完成了一篇大致的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

我們經常使用的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的常見套路
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
 /usr/local/bin/ropper --file fake --chain "execve cmd=/bin/sh" --badbytes 000a0d0b09

canary

原始碼中出現了readgsword(:0x14)

這個與checksec得到的stack canary found有關。

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

在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

問題

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]含義??