漏洞分析——USQLITE1.0.0远程代码执行漏洞

对于软件 USQLITE1.0.0 中存在的由栈溢出导致的远程代码执行漏洞的分析。

参考自:https://whereisk0shl.top/post/2019-01-06
poc如下——

import socket
import sys

if len(sys.argv)<=1:
    print("Usage: python usqlite.py hostname")
    sys.exit()

hostname=sys.argv[1]
port = 3002
buffer = "A"*259+"B"*4+"C"*360

sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=sock.connect((hostname,port))    //连接服务器
sock.send(buffer +'\r\n')                //发送payload
sock.recv(1024)
sock.close()

1.背景知识

A.SQLite

(1)简介

SQLite是遵守ACID的关系数据库管理系统,它包含在一个相对小的C程序库中。与许多其它数据库管理系统不同,SQLite不是一个客户端/服务器结构的数据库引擎,而是被集成在用户程序中

SQLite是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的数据库,这意味着与其他数据库一样,您不需要在系统中配置。

就像其他数据库,SQLite 引擎不是一个独立的进程,可以按应用程序需求进行静态或动态连接。SQLite 直接访问其存储文件。

SQLite是D.Richard Hipp用C语言编写的开源嵌入式数据库引擎。它支持大多数的SQL92标准,并且可以在所有主要的操作系统上运行。

(2)基本操作

基本操作包括但不限于以下所述——

  • 创建数据库
  • 创建表
  • 插入数据
  • 从.sql文件导入数据
  • 分析数据库使用状态
  • 备份数据库

(3)在java中使用SQLite

首先需要去网上下载SQLite相关驱动(如sqlite-jdbc-3.27.2.jar并将其作为java项目的库文件,放入lib文件夹中,然后还需要在项目中新建一个db文件夹用于存放数据库文件。具体代码如下——

package com.scott.sqlite;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class Test {
    public static void main(String[] args) throws Exception {
        Class.forName("org.sqlite.JDBC");     //通过反射,在程序运行时动态加载程序内部第三方jar包的class
        Connection conn = DriverManager.getConnection("jdbc:sqlite:db/test.db");
        Statement stmt = conn.createStatement();

        stmt.executeUpdate("DROP TABLE IF EXISTS person");                            //如果表person存在则删除再进行后续操作
        stmt.executeUpdate("CREATE TABLE person(id INTEGER, name STRING)");             //创建表person
        stmt.executeUpdate("INSERT INTO person VALUES(1, 'john')");                       //更新表中内容
        stmt.executeUpdate("INSERT INTO person VALUES(2, 'david')");
        stmt.executeUpdate("INSERT INTO person VALUES(3, 'henry')");
        ResultSet rs = stmt.executeQuery("SELECT * FROM person");                       //查询表中所有数据
        while (rs.next()) {                  //打印查询结果
            System.out.println("id=>" + rs.getInt("id") + ", name=>" + rs.getString("name"));
        }
        stmt.close();
        conn.close();
    }
}

SQLite 简介
SQLite数据库简介

B.USQLITE1.0.0安装使用

还以为解压双击exe就可以运行,但发现每次都会闪退,上网查也没有这个软件的使用教程,最后在软件解压后的Docs目录下的uSQLite FAQ.html中看到了对于此软件的使用说明如下——

Unpack the package in an empty directory, the application is staticly linked so their are no pre-requisites, setups or registrations to do.
From a command shell in the root directory launch uSQLiteServer.exe uSQL.ini (Linux version has no .exe extension!), you should get 3 messages to tell you that the service is starting, the sec db as been opened, and the db has been opened.
Launch a text terminal to connect to the server (a hyperterminal config may be found in db.ht)
Log in and run queries, in the following session sample user supplied input is in bold and the response from the server is in italics. (You will need to be familiar with SQL and the TechFell protocol to understand what is going on:).
……

所以在使用一个比较小众新软件时,查阅其官方使用说明文档(软件中包含的)是了解其安装使用的首要选项

C.基于TCP的客户端、服务器端socket编程

使用TCP套接字编程可以实现基于TCP/IP协议的面向连接的通信,它分为服务器端和客户端两部分,其主要实现过程如下。

linux网络编程(3)TCP编程模型与TCP的连接、关闭
基于TCP的客户端、服务器端socket编程
Socket send函数和recv函数详解
基于TCP协议的Socket编程和通信(实例)

2.漏洞复现

使用命令uSQLiteServer.exe uSQL.ini开启uSQLite Server后,运行poc,可以看到应用程序崩溃。

3.漏洞分析

A.漏洞点定位

如果用原始的POC,因为溢出空间较大,破坏了大部分的程序栈结构,所以是没法定位到漏洞点的,将buf长度减小到刚好覆盖到EIP的长度(再小的话程序不会造成溢出,漏洞不会被触发),然后就可以通过以下两种方式来定位漏洞点。

(1)方法1-利用程序崩溃时的栈调用信息

由如下图所示的在Immunity DebuggerWinDbg中的信息,可缩小漏洞点范围到函数00402DA3,故可从此函数入手分析寻找具体的漏洞点。

在ida中可定位到如下图所示代码——

(2)方法2-利用栈中关键字符串定位

触发崩溃后,观察栈中数据,发现紧挨EIP后面的是一串字符串: syntax error♪:OK♪,如下图——

然后在IDA中根据关键词OK搜寻类似string,最终定位到如下图所示string及对应代码处,如下图——

由上图,崩溃时的字符串: syntax error♪:OK♪: %s%c:OK%c高度吻合,而且相关代码中的sprintf函数是存在安全隐患的,所以到这里可以大胆猜测漏洞点就在sprintf函数处。接下来可以尝试动态调试验证分析。

B.漏洞分析

接下来通过分析数据流向,找到漏洞触发原因。

关键的数据流向如下:

在函数sub_402Da3中,在402E41处调用recv函数接收poc客户端发送的数据,这里的接收数据长度限制为0x800h,然后在402e76处将poc传入的超长字符串作为参数,调用函数sub_402C04
在函数sub_402C04中,首先会将传入的字符串参数和:PPRAGMA进行比较,如果不相同,则会跳入错误处理分支进行处理,然后在402cd6处调用sprintf打印错误信息时拷贝字符串到栈中时没有限制长度,导致栈上数据被覆盖,执行到402D3Bsub_402C04返回是由于返回地址被覆盖,故程序崩溃停止。

4.漏洞利用

由于最大的输入字符串长度是0x800h,所以在EIP后面有足够的长度用来存放shellcode,于是先尝试jmpesp的利用方式。除了利用mona插件辅助分析,在Immunity Debugger中还可以通过右键——search for——All commands in all modules——输入jmp esp查找程序和系统库中存在的jmp esp代码的地址,如下图。

但是在实际利用时发现,直接将payload改为"A"*259+jmpesp+"\x90"*10+sc+"\x90"*20的话,漏洞点反而不会被触发,于是重新跟踪数据流,以查找具体问题。

后来发现其实不用一步一步跟踪数据流,发现在利用失败的情况下并不会将字符串拷贝到栈中,此时报错信息如下图所示为unrecognized token,上网查询发现是输入了有问题的字符导致的。于是联想到编写shellcode时可能会遇到的坏字符问题,接下来尝试找出shellcode中会导致利用失败的坏字符。

坏字符包括:\x1C、\x08、\x5D、\x5C、\x1D、\x01、\x18、\x7B等。在利用时需要对shellcode进行编码,绕过这些坏字符,然后就可以利用成功了。

后续有时间再研究一下shellcode的编码并最终实现成功利用。。。

参考资料

sprintf函数引起的缓冲区溢出
为windbg安装mona.py
Windows 7下配置Mona.py
测试标志位指令