头歌网络安全(11.12)
头歌禁止复制解决
必须先下篡改猴!!!!
头歌复制助手 Educoder Copy Helperhttps://scriptcat.org/zh-CN/script-show-page/1860
Java生成验证码
第1关:使用Servlet生成验证码
任务描述
本关任务:使用servlet生成验证码。
相关知识
验证码在我们登陆、注册网站,火车票买票的时候经常会见到的,为什么要有验证码呢?可能很多人都会有这个疑问。
但是作为开发者,可能我们更多的就会关注怎么生成验证码了。
要了解如何生成验证码,我们首先要知道什么是验证码,网站为什么需要它。
为什么要有验证码,什么是验证码
我们经常需要在网站或者应用程序中填写验证码,不过作为用户而言其实我们一点都不喜欢验证码,因为有时候老容易填错。
为什么这个影响用户体验的东西还是一直存在呢?
肯定是有道理的。
一个网站除了我们人操作电脑可以登录之外,使用JavaScript代码和一些脚本语言也是可以登录的,但是我们开发网站是给人用的而不是给机器使用的,我们想象一个网站如果没有验证码,我们只需要编写一段脚本就可以无限次数的登陆某个网站,这样无数次的尝试就可以暴力破解用户的密码,如果是注册行为那就会给网站制造很多垃圾信息,这个就会对该网站造成极大的资源浪费,严重的可能会让这个网站崩溃,所以就有了验证码。
说白了,验证码就是用来判断是人在操作还是机器在操作。
如何使用Servlet生成验证码
在Java中我们可以在Web项目中使用Servlet来生成验证码,流程是:前端请求验证码servlet对应的地址,后端servlet收到请求,生成一串字符作为验证码,存入到Session中,最后将验证码作为一张图片返回给前端。前端填写了验证码提交到服务器来验证。
我们看一个示例,你也可以根据这个示例在右侧编辑器中一步一步实现验证码的功能。
项目和servlet已经创建好了,我们首先在web.xml文件中注册servlet。
如下:
在servlet的doGet()方法中编写代码实现生成图片验证码:
分为如下步骤:
定义图像数据缓冲区(BufferedImage);
创建图片对象;
创建绘制工具(Graphics);
生成随机数,存入到session中;
使用Graphics绘制图形;
将验证码通过图像输出流(ImageIO)输出到客户端;
最后输入验证码地址即可访问单验证码。
具体代码如下:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 使用验证码的步骤
// 定义图片的宽高
int height = 20;
int width = 60;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 绘图工具
Graphics graphics = image.getGraphics();
// 绘制矩形
graphics.setColor(getRandColor());
// 绘制矩形背景 前两个参数 是 x y的坐标
graphics.fillRect(0, 0, width, height);
// 设置文字的颜色 为白色
graphics.setColor(Color.WHITE);
String yzm = "";
// 生成四个随机数字并且画在图片上
for (int i = 1; i <= 4; i++) {
// 生成随机数字并且显示到页面上
int number = new Random().nextInt(10);
yzm += number;
graphics.drawString(number + "", 10 * i, 10);
}
// 将验证码放入Httpsession中
HttpSession session = req.getSession();
session.setAttribute("sessionYzm", yzm);
// 将验证码图片输出到客户端
ImageIO.write(image, "jpg", resp.getOutputStream());
}
// 获取随机颜色
private Color getRandColor() {
int red = new Random().nextInt(255);
int green = new Random().nextInt(255);
int blue = new Random().nextInt(255);
return new Color(red, green, blue);
}
编程要求
web.xml中的代码已经添加,按照上述步骤编写servlet代码,点击测评即可。
效果图:
测试说明
因为需要部署服务器,并且运行测试代码,所以评测时间较长,需要30秒左右。
开始你的任务吧,祝你成功!
package com.servlet;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class CodeServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/********* Begin *********/
//请在此编写生成验证码的代码
int height = 20;
int width = 60;
BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
Graphics graphics = image.getGraphics();
graphics.setColor(getRandColor());
graphics.fillRect(0,0,width,height);
graphics.setColor(Color.WHITE);
String yzm = "";
for(int i=1;i<=4;i++){
int number = new Random().nextInt(10);
yzm += number;
graphics.drawString(number + "",10*i,10);
HttpSession session = req.getSession();
session.setAttribute("sessionYzm",yzm);
ImageIO.write(image,"jpg",resp.getOutputStream());
}
/********* End *********/
}
// 获取随机颜色
private Color getRandColor() {
int red = new Random().nextInt(255);
int green = new Random().nextInt(255);
int blue = new Random().nextInt(255);
return new Color(red, green, blue);
}
}
第2关:用户登录时校验验证码是否正确
任务描述
本关任务:编写程序验证验证码是否正确。
相关知识
上一关我们已经学习如何生成验证码了,为了完成一整套的验证码使用流程我们还需要知道如何验证用户提交的验证码是否正确。
登录功能
我们经常在登录注册的时候填写验证码,本关我们就来实现登录功能。
首先我们来理解验证码校验的基本流程:
上图展示了一个用户填写验证码的基本流程,用户打开网页显示服务端生成的验证码,点击“看不清楚”标签可以重新生成,这个时候会从新请求服务端数据,服务端用Session来保存验证码信息。
当用户点击确认按钮的时候,我们就需要对用户通过表单提交的验证码进行校验了,这个时候服务端获取Session保存的验证码信息和用户提交的验证码数据进行校验如果两者一致则校验通过。
这就是一个完整的验证码流程了。
我们可以将验证码的流程总结为:前端表单登陆 => 后端获取到验证码校验 => 前端收到后端的响应。
借下来我们来实现这个过程。
前端实现
我们创建一个登录表单,代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<form action="loginServlet">
用户名:
<input type="text" name="username">
<br>
密 码:
<input type="text" name="password">
<br>
验证码:
<input type="text" name="verifycode" id="yzm">
<!-- src填servlet的地址就能显示网络上的图片 -->
<a href="javascript:reload()"><img id="yzmImg" src="code"/> </a>
<br>
<input type="submit" value="提交">
</form>
</body>
<script type="text/javascript">
//重新加载验证码
function reload() {
var img = document.getElementById("yzmImg");
img.src = "code?" + new Date().getTime();
}
</script>
</html>
package com.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
/********* Begin *********/
//请在此进行登录校验
PrintWriter writer = resp.getWriter();
String username = req.getParameter("username");
String password = req.getParameter("password");
String verifycode = req.getParameter("verifycode");
HttpSession session = req.getSession();
String realCode = (String) session.getAttribute("sessionYzm");
if(!verifycode.equals(realCode)){
writer.write("验证码错误");
}else{
if("admin".equals(username)&&"admin123".equals(password)){
writer.write("登录成功");
}
}
/********* Begin *********/
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
第3关:使用Kaptcha组件生成验证码
任务描述
本关任务:使用Kaptcha组件生成验证码,并校验验证码是否正确。
相关知识
之前两关我们已经了解了验证码的制作流程,不过我们在开发中一般不会去自己从零开始编写验证码,而是会使用到开源的组件,本关我们就来使用Kaptcha来生成验证码,并且编写一个页面校验用户的验证码是否输入正确。
Kaptcha 组件的使用
先来看要实现的效果:
首先制作用户填写验证码的页面captchacode.jsp
<script type="text/javascript">
function reloadCode() {
var date = new Date().getTime();
document.getElementById("code").src = "<%=request.getContextPath() %>/imageKaptcha?d="+date;
}
</script>
<form action="checkCaptcha.jsp" method = "post">
<img alt="验证码" src="imageKaptcha" id = "code"><a href = "javascript:reloadCode();">看不清</a>
<input type = "text" name = "captcha">
<input type = "submit" value = "submit">
</form>
接着我们写一个检查验证码输入是否正确的类checkCaptchaServlet.java
request.setCha\fracterEncoding("utf-8");
// 获取Kaptcha jar包里面的KAPTCHA_SESSION_KEY
String trueCaptcha = (String)session.getAttribute(Constants.KAPTCHA_SESSION_KEY);
String inputCaptcha = request.getParameter("captcha");
if(trueCaptcha.toLowerCase().equals(inputCaptcha.toLowerCase())) {
out.write("验证码输入正确");
} else {
out.write("验证码输入错误");
}
然后配置好web.xml就ok了。下面我们来看看怎么配置web.xml
<servlet>
<servlet-name>myCaptcha</servlet-name>
<!-- jar中的 KaptchaServlet的路径-->
<servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
</servlet>
<!--配置kaptcha 校验验证码是否正确的 servlet-->
<servlet>
<servlet-name>CheckCaptcha</servlet-name>
<servlet-class>com.servlet.CheckCaptchaServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>myCaptcha</servlet-name>
<!-- 对于index.jsp中img的src -->
<url-pattern>/imageKaptcha</url-pattern>
</servlet-mapping>
<!--第三关:配置kaptcha 校验验证码的 servlet-->
<servlet-mapping>
<servlet-name>CheckCaptcha</servlet-name>
<url-pattern>/checkCaptcha</url-pattern>
</servlet-mapping>
做完上述步骤之后,运行项目,打开网页,即可查看验证码。
输入正确的验证码点击submit:
经过上述步骤我们就使用Kaptcha组件生成验证码了。
扩展:Kaptcha还有很多其他的设置可以实现图片边框,边框颜色,中文验证码等操作,限于篇幅在这里就不在赘述。
编程要求
好了,到你啦,来使用Kaptcha生成验证码并校验输入的验证码是否正确吧。
补全captchacode.jsp,实现验证码表单的页面效果;
补全CheckCaptchaServlet,实现验证码的校验功能,验证码正确返回:验证码输入正确,否则返回:验证码输入错误。
开始你的任务吧,祝你成功!
checkCaptchaServlet.java
package com.servlet;
import com.google.code.kaptcha.Constants;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
/**
* Created by 63194 on 2018/9/14.
*/
public class CheckCaptchaServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter(); // 初始化 out 变量
HttpSession session = req.getSession(); // 初始化 session 变量
//校验kaptcha 验证码是否正确
//获取Kaptcha jar包里面的KAPTCHA_SESSION_KEY
String trueCaptcha = (String) session.getAttribute(Constants.KAPTCHA_SESSION_KEY);
String inputCaptcha = req.getParameter("captcha"); // 使用 req 参数
if (trueCaptcha != null && inputCaptcha != null && trueCaptcha.toLowerCase().equals(inputCaptcha.toLowerCase())) {
out.write("验证码输入正确");
} else {
out.write("验证码输入错误");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
captchacode.jsp
<%--
Created by IntelliJ IDEA.
User: 63194
Date: 2018/9/14
Time: 19:14
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<!--------- Begin --------->
<script type="text/javascript">
function reloadCode() {
var date = new Date().getTime();
document.getElementById("code").src = "<%=request.getContextPath() %>/imageKaptcha?d="+date;
}
</script>
<form action="checkCaptcha.jsp" method = "post">
<img alt="验证码" src="imageKaptcha" id = "code"><a href = "javascript:reloadCode();">看不清</a>
<input type = "text" name = "captcha">
<input type = "submit" value = "submit">
</form>
<!--------- End --------->
</body>
</html>
动态分析技术
任务描述
本关任务:
使用 qira 拿到属于你的 flag !!!
相关知识
为了完成本关任务,你需要掌握:
1、正确的程序分析思路
2、动态调试器 qira 的使用
程序分析
在本关学习新知识之前,我们需要先知道如何分析一个程序,即拿到一个二进制程序文件后应当如何入手。
对一个程序的分析应当从以下三步入手:运行程序 --> 静态分析 --> 动态分析。运行程序,即在本机上直接和程序进行交互,观察程序的输入和输出,作为分析人员的我们应当尽可能的执行完所有的功能。静态分析,即利用静态分析软件,如:IDA Pro,尝试对程序进行反汇编、反编译,分析伪代码,结合上一步程序运行的输入和输出快速定位有问题的代码段。动态分析,即利用动态分析软件,如:qira ,尝试观察程序动态运行的结果,能够直接观察到程序运行过程中寄存器、栈、堆等数据的变化。
接下来我们将用一个例子来完整的走一遍程序分析的流程,示例文件为 demo ,可以在目录/home/headless/Desktop/workspace/myshixun/pwnPro/step2/下找到。
首先,打开终端,如下终端命令所示,进入到程序所在目录,查看程序信息,发现程序没有可执行权限,添加可执行权限,然后执行程序。
cd /home/headless/Desktop/workspace/myshixun/pwnPro/step2/
ls -all demo
chmod +x demo
./demo
如下截图所示,发现程序运行后,只打印了No flag here!!!,难道真的没有 flag ,我肯定是不信的,接着我们用 IDA 来分析。
按照上一关所学的内容,我们在 IDA 中打开 demo 文件,然后在反汇编界面按下 F5 ,直接观察伪代码。如下截图所示,是 demo 程序的主逻辑,最外层是一个 for 循环,将会执行18次,每次都对 false_flag 数组中的值进行了判断,根据判断的结果对 false_flag 数组元素的值进行变化,然后再将其赋值给 true_flag ,最后调用 puts 打印了No flag here!!!。根据题目逻辑,false_flag 是待变换的数组,true_flag 才是存储真正 flag 的地方,程序通过对 false_flag 进行变化然后得到 true_flag ,所以我们需要的是 true_flag 的值,然而这里并没有打印该值。
我们继续分析,在 IDA 中我们可以通过点击直接跳转到对应变量所在位置,这里我们点击 false_flag 将会跳转到其位置所在。如下截图所示,可以发现 false_flag 位于 data 段,表明其是一个全局变量,后面的箭头所指dq offset aXdsyIkwHz3Eajs表明其是一个指针变量,指向的内容我们可以看后面的方框,这是 IDA 给的注释,表明其指向的字符串是xdsy{Ikw_Hz3_Eajs}。
我们再点击 true_flag ,如下截图所示,发现其位于 bss 段,表明其是未初始化的全局变量。箭头指向的位置标注了_BYTE_true_flag[19]表明其是一个长为19的字符数组。当然这里并不能看到其值是啥,因为需要程序运行结束后才能观察到。如果我们想要得到这里的 true_flag ,有两种办法:一是根据程序的逻辑,自己编写求解脚本来得到 flag ,编写脚本的方式也很简单,直接复现程序的逻辑即可;二是使用动态调试,因为 true_flag 是程序的一个变量,在程序运行到某一时刻,该变量中一定存储着正确的 flag 。因此在下一小节,我们将会使用 qira 动态调试来直接得到 true_flag。
动态调试器 qira
什么是 qira
qira
在学习动态调试之前,我们先来了解下 qira 。qira 是 github 上的一个开源项目,在上方已经给出了其链接,点击即可跳转到 github ,github 中有详细的介绍和其安装方式。当然在本实验平台中,我们已经安装好了。qira 被誉为超越时空的调试器,即可以在时间中任意穿梭的动态调试器,实质上是一个 trace 工具,将程序整个执行流全部记录下来,然后给予用户回溯、查看命中断点的所有指令(即交叉引用)等。
qira 的基本使用
打开实验环境的终端,切换到 demo 文件所在的目录下,使用 qira 启动程序,整个过程如下命令所示。
cd /home/headless/Desktop/wordspace/myshixun/pwnPro/step2/
qira demo
启动后,终端输出如下截图所示,可以看到箭头所指的地方,分别是程序所在路径(这里运行时的环境和实验平台不一样,请用自己在平台的输出进行验证),监听的 ip+port ,以及程序自身的运行输出。值得说明的是,qira 采用 web 页面来展示整个程序的执行过程,后端使用 python flask 框架实现。
之后,我们打开浏览器,输入127.0.0.1:3002即可看到 qira 的界面,如下截图所示。序号1指示的是 qira 的一条时间线( vtimeline ),在调试复杂程序的时候程序并不会一次执行完,我们每次步入都需要在 web 页面进行刷新,此时就会出现新的时间线。序号2指示的是 qira 的一些控制数据,前三个盒子中展示的内容分别是:第几条指令、第几条时间线、指令地址,我们可以直接修改盒子中的数据来实现跳转。序号3指示的是程序的汇编指令,这里展示的指令和 IDA 中的没有什么区别,当然地址会有所不同,这是因为程序动态运行加上了基地址所导致的。序号4指示的是寄存器的值,序号5指示的是程序运行过程中的函数堆栈,从这里我们可以看到程序的函数调用关系。
接下来,我们再看看 qira 的强大之处,在 qira 的主界面上,我们可以通过点击位于内存中的地址查看当前内存的数据信息。如下截图所示,我们点击寄存器中的 RSP 栈顶指针寄存器,此时最下方就会展示当前栈帧的内存数据信息,图中方框和箭头圈出的地方刚好是 RSP 指向的8个字节内存数据,后面显示的乱码就是对应字节的 ascii 编码信息,转换不出来就显示 . 号。
使用 qira 找到 flag
在之前的介绍中,我们知道了 qira 是一个 trace 工具,它会记录程序运行过程中所有寄存器和内存数据的变化,因此通过它,我们就可以在内存中找到 true_flag ,从而无需编写脚本就能获得 flag 。
在实践之前,这里我们先引入基址这个概念。如下截图所示,我们同时打开 IDA 和 qira ,将 IDA 汇编代码定位到 start 函数,将 qira 指令定位到第0条。我们知道 c 语言中 main 函数是程序的入口点,其实在 main 函数之前还会调用 start 函数,做二进制分析的时候我们应当认为这才是程序真正的入口点。通过对比汇编指令,我们可以看到 qira 就是从 start 开始执行的,但是我们也会发现 IDA 显示的地址是0x0000000000001060,而 qira 显示的却是0x4000001060。 我们知道64位程序的地址都是 64bit ,十六进制表示的话就是16个十六进制数,高位为0可以省略。因此这里 qira 的地址比 IDA 多了0x4000000000,这个地址我们就称为基址,所有汇编指令在运行时的地址都是基址 + 偏移,而 IDA 是静态分析,所以只显示偏移,没有加上这个基址。
在弄清楚基址这个概念后,寻找 flag 就方便多了,首先我们在 IDA 中找到 true_flag 的地址,如下截图所示,为4030,然后我们将基址加上就是0x4000004030,之后我们将其输入到 qira 最上控制面板的第四个盒子中,截图如下。
之前并没有介绍这第四个盒子的作用,其实该盒子的作用就是用于展示对应地址的内存数据信息。按照上面的步骤,我们在 qira 中输入 true_flag 的地址后,直接回车,此时可以看到最下方已经出现了对应地址的内存数据信息,如下截图所示,可以看到程序运行后的 flag 了。
好了,到这里我们已经基本掌握了程序的分析步骤,qira 动态调试器的使用,接下来就用学到的知识去完成本关挑战吧。
编程要求
本课程采用了 CTF 比赛获取 flag 的方式来进行实践练习,你的目标是拿到一个 flag ,形式为flag{xxxxxxxxxxxxxxx},本关目标文件为/home/headless/Desktop/workspace/myshixun/pwnPro/step2/目录下的 level2 。
本关没有编程要求,但需要你通过静态分析 + 动态调试拿到隐藏在二进制程序中的 flag ,后面的课程将需要你利用程序漏洞进行编程获取到 shell ,进一步拿到 flag 。
测试说明
将你拿到的 flag 写入到实验环境提供的 flag.txt 文件中,然后点击评测即可。
开始你的任务吧,祝你成功!
flag{Y0u_Are_Great}
静态分析技术
任务描述
本关任务:
使用 IDA pro 拿到属于你的 flag !!!
相关知识
为了完成本关任务,你需要掌握:
1、反编译器 IDA pro 的使用
2、基本的汇编指令
3、基本的 ELF 文件格式
IDA pro
什么是 IDA pro
交互式反汇编器专业版( Interactive Disassembler Professional ),人们常称其为 IDA Pro ,或简称为 IDA 。是目前最棒的一个静态反编译软件,为众多 0day 世界的成员和 ShellCode 安全分析人士不可缺少的利器!IDA Pro 是一款交互式的,可编程的,可扩展的,多处理器的,交叉 Windows 或 Linux WinCE MacOS 平台主机来分析程序, 被公认为最好的花钱可以买到的逆向工程利器。IDA Pro 已经成为事实上的分析敌意代码的标准并让其自身迅速成为攻击研究领域的重要工具。它支持数十种 CPU 指令集其中包括 Intel x86,x64,MIPS,PowerPC,ARM,Z80,68000,c8051 等等。
IDA 是 Hex-Rays 公司的旗舰产品,作为一款致力于软件逆向和破解的工具,IDA 所属公司却特别痛恨盗版软件。因此,他们对于未经授权就使用 IDA 的做法深恶痛绝。这里特别介绍其反盗版措施,非常值得借鉴和学习。
第一种反盗技术:每一份 IDA 都带有水印,以将它与购买者一对一地对应起来。如果一份 IDA 出现在盗版软件站中,Hex-Rays 就能够通过水印追踪到购买者,并将其列入销售黑名单。我们常常可以在 Hex-Rays 的 IDA 支持论坛上发现有关 IDA 的“泄露”版本的讨论。
第二种反盗技术:扫描在局域网中运行的其他IDA程序。例如,Windows 版本的 IDA 启动后,它会在端口23945上广播一个 UDP 包,并等待响应,看相同子网中是否有其他使用相同许可证密钥的 IDA 实例在运行。然后,IDA 会将得到的响应数量与使用该许可证的用户数量进行比较,如果发现网络中存在过多的 IDA 实例,IDA 会拒绝启动,但是要注意,用户可以在一台计算机上使用相同的许可证运行多个 IDA 实例。
第三种反盗技术:使用密钥文件将每一名购买者与产品联系起来。在启动时,IDA 会搜索一个有效的 ida.key 文件。如果无法定位有效地密钥文件,IDA 就会立即关闭。密钥文件还用于用户升级 IDA 的资格。基本上,ida.key 文件就像是用户的购买收据,要想在将来获得升级资格,用户必须保管好这个文件。
IDA pro的基本使用
经过上面 IDA 背景知识的学习,我们可以猜想到 IDA 这款软件价值不菲,具体的费用因插件的完备性而不同,感兴趣的同学可以上网搜索了解一下。在本实验平台中,我们已经准备好了该实验环境,使用 IDA Pro 7.5 全插件版本,也就是说所有功能俱全。
首先,我们打开桌面上命名为 IDA_Pro_v7.5 的文件夹,如下截图所示,其中ida.exe和ida64.exe就是对应的 IDA 应用程序,分别用于逆向分析32位和64位的可执行程序。这里大家可能会有个疑问,为啥 ubuntu 下可以运行.exe程序?这是因为 linux 下的 IDA 本身版本少且不如 Windows 平台下的功能强大,所以这里采用了 Windows 平台下的 IDA 软件。其次之所以这里可以运行 Windows 平台下的程序,是因为平台实验环境已经配置了相应的环境,可以参考 wine 的配置,具体细节此处不详述,不是我们学习的重点。
这里我们以一个具体程序的为例介绍 IDA 的使用,待分析二进制程序为 demo ,如下截图所示,我们可以在/home/headless/Desktop/workspace/myshixun/pwnPro/step1/目录下找到该二进制程序。该文件为64位可执行程序,接下来我们将使用ida64.exe对其进行分析。
点击ida64.exe,首先会出现一个版本许可证的界面,下方会存在确认按钮,无论我们是否确认,之后都会进入到如下截图所示的页面。选择New或者Go开启 IDA 其实本身都没有啥区别。选择New会打开文件浏览窗口,我们可以在这里寻找 demo 文件,然后用 IDA 打开它。选择Go会直接打开一个空白的 IDA 界面,之后我们可以通过拖动的方式将 demo 文件拖到 IDA 中,这和第一种操作的结果是一样的。至于第三种选择Previous会直接打开下方方框中排在最上面的对应路径的二进制程序文件,这其实就是历史记录,当然我们也可以直接点击方框中的其它条目来打开之前分析过的二进制文件。
这里我们选择Go,然后将 demo 文件拖到 IDA 中,之后会出现如下截图所示的界面,其中高亮的地方表明了 IDA 会将其作为一个 ELF64 格式的文件来进行分析,其余配置我们默认即可,选择 OK 进入到 IDA 界面。
在进入正式认识 IDA 分析界面之前,我们来看下 IDA 启动后对文件做了啥更改。如下截图所示,我们发现原程序目录下出现了和待分析程序命名相同,但后缀不同的文件,这是 IDA 生成的数据库文件,值得一提的是,我们在 IDA 中对文件进行修改的话是不会影响原二进制程序文件本身的,改变的是这些数据库文件。
同样的,我们现在来尝试退出 IDA ,会出现如下截图所示的界面,其目的就是让我们选择是否打包存储 IDA 生成的数据库文件,默认选择如下,确认后会将原来的五个文件打包为后缀为.i64的数据库文件,该文件记录了我们在 IDA 所做的操作和更改。当然我们也可以选择最下面的 Don't save ,该操作在 IDA 关闭后将不会留下任何文件。
好了,经过前面的学习,我们已经掌握了一些基本的操作,接下来我们将会学习 IDA 的几个重要分析界面。
整体界面
如下截图所示,待 IDA 将二进制程序分析完毕后,IDA 整体界面可以大致切割为五个部分,我们从上往下依次介绍,序号1指示的是 IDA 的工具栏窗口,这里集合了所有功能选项和操作,具体操作细节我们会在后面实践课程中进一步学习。序号2指示的是Functions window,即函数窗口,这里会显示该程序中所有逆向出来的函数,可以看到其中有我们熟悉的 main 函数、printf 函数,当然还有以 sub 开头命名的函数,这些都是没有识别出具体名字的函数,将会以 sub+ 函数地址的方式命名。序号3指示的是view窗口,截图中是处于IDA View-A反汇编界面的状态,当然该视图还有string window字符串窗口,Hex View-1十六进制窗口等等。序号4指示的是Graph overview,即图表化概览,这里展示的是程序所有函数的流程图,可以看到其流程图和右边的反汇编界面展示的流程是一致的。序号5指示的是Output window,即输出窗口,这里会打印 IDA 在执行过程中的提示信息,最下面的 python 一栏还能执行 python 脚本。
反汇编界面
如下截图所示,分别是反汇编界面的两种代码展示方式。第一种是函数控制流程分支展示,这也是 IDA 默认的展示方式。通过这种方式,我们可以清晰的观察到函数的跳转分支情况,以下面的第一张图为例,红色箭头指示的是语句失败的分支,可以是 if 语句,当然具体的话还要根据汇编代码具体分析。第二种是汇编代码的文本视图,以下面的第二张图为例,我们可以看到汇编代码所属的段( segment )、对应地址等。上述两种视图的切换可以通过在 IDA View-A 视图中按下空格键快速切换。
伪代码界面
IDA 最强大之处在于提供了极好的反编译功能,如下截图所示,我们选中反汇编界面,按下F5将会执行反编译,此时出现了Pseudocode-A伪代码界面。可以看到,除了个别变量名无法还原外,这里的伪代码已经非常清晰的展示了程序流程。相比较于看汇编代码,通过阅读伪代码可以帮助我们更好的定位程序漏洞,这也是 IDA 为啥如此受欢迎的原因。
关于 IDA 的基本使用将会介绍到这里,在后面的实践操作中,我们将会进一步熟悉和介绍 IDA 的其它操作。
汇编指令学习
接下来,我们将以一道例题的方式来学习一下基本的汇编指令,熟悉在汇编下一个函数的功能是如何实现的,使用文件为 demo ,我们在 IDA 中打开它,软件会自动定位到 main 函数。
如下截图所示,红框圈起来的部分展示的是函数序言和函数尾声,也就是所有函数的开头和结尾都会使用的指令。在汇编层面,所有函数调用都会先开辟栈帧,push rbp会将 rbp 寄存器压栈,rbp 是栈帧寄存器,这里是保存上一个函数的栈帧;mov rbp, rsp,rsp 是栈顶指针寄存器,此时将 rsp 指向 rbp ,也就以为着该函数栈帧正式开始。leave相当于mov rsp, rbp; pop rbp,我们可以发现这和函数序言是完全相反的操作,不难理解,一个函数结束后需要清空自己的栈帧,所以采用相反的操作。最后retn相当于pop rip,rip 是程序指令寄存器也就是下一条要执行的指令,所以这里的retn会弹出之前压入栈中的返回地址到 rip 中继续运行,保证程序流程的完整运行。
如下截图所示,我们来分析下 scanf 函数的汇编指令执行过程,这里 IDA 会将 scanf 函数识别为__isoc99_scanf,命名中指明了 c 语言版本。先看第一个红框,这里是函数的局部变量,var_12代表距离 rbp 栈帧寄存器的偏移,单位为 byte ,可以看到后面函数调用中就是用 rbp 来定位局部变量的。第二个红框包含了 scanf 调用的完整汇编指令,首先lea rax, [rbp+var_12]将变量地址传到 rax 寄存器中,然后mov rsi, rax将 rax 寄存器的值传递给了 rsi ,之所以这么麻烦是因为 linux 下64位程序规定了前6个参数使用寄存器传参的顺序,即: rdi --> rsi --> rdx --> rcx --> r8 --> r9 ,所以这里 var12 是 scanf 函数的第二个参数。接着lea rdi, a10s将"%10s"也就是我们的格式化字符串传给了 rdi ,也就是 scanf 函数的第一个参数。最后再call ___isoc99_scanf实现函数调用,以上步骤就是实现了源代码的scanf("%10s", &var12)功能。当然在源代码层面,我们并不关心 scanf 的返回值,但是在汇编指令默认会用 rax 寄存器( eax 是 rax 的32位表达方式)来存储函数的返回值,如果函数没有返回值就会将0存放到 rax 寄存器中,这就是mov eax, 0汇编指令的功能。
好了,这里我们对汇编层面下函数流程的执行进行了一定的分析,其中对 printf 函数的分析,大家可以自己分析一波,比较简单。在分析完汇编后,我们再来看看反编译后的伪代码,相信你一定对二进制程序逆向分析有了更深入的感受和认识。
ELF文件格式
ELF 是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件,是 UNIX 系统实验室( USL )作为应用程序二进制接口( Application Binary Interface,ABI )而开发和发布的,也是 Linux 的主要可执行文件格式。
打开 IDA ,在其任意界面按下ctrl+s,会弹出如下截图所示的界面,一个 ELF 文件会根据文件内容将文件分为很多个段( segment ),接下来我们就根据其展示的内容来简单介绍下 ELF 文件格式。该界面展示的内容,从左往右依次是:段名、起始地址、结束地址、内存权限、对齐方式。
这里我将根据重要性着重介绍几个段:
.plt 实现程序函数链接 lazy 加载的段,里面存储的是跳转到 .got 表地址的代码;
.text 之前我们介绍的汇编指令都处于该段中,也就是说 .text 对应程序真正的代码指令;
.got 存储真正函数地址的段;
.data 程序已经初始化的全局变量;
.bss 程序未初始化的全局变量;
如下截图所示,在 segment 界面,我们可以通过点击的方式直接进入到程序的相应段中,在该段中我们可以发现一个名为 flag 全局变量,该变量是一个字符串。
当然除了上面的方式,我们要寻找程序中的字符串,也可以使用快捷键shift+f12,如下截图所示,会跳转到字符串界面,我们可以在最下面找到该字符串,同样点击它也会跳转到对应段。
编程要求
本课程采用了 CTF 比赛获取 flag 的方式来进行实践练习,你的目标是拿到一个 flag ,形式为flag{xxxxxxxxxxxxxxx},目标文件为/home/headless/Desktop/workspace/myshixun/pwnPro/step1/目录下的 level1 。
本关没有编程要求,但需要你通过逆向分析拿到隐藏在二进制程序中的 flag ,后面的课程将需要你利用程序漏洞进行编程获取到 shell ,进一步拿到 flag 。
测试说明
将你拿到的 flag 写入到实验环境提供的 flag.txt 文件中,然后点击评测即可。
开始你的任务吧,祝你成功!
flag{you_g0t_m3}