首页 -> 安全研究
安全研究
绿盟月刊
绿盟安全月刊->第55期->安全文摘
作者:CoolQ <qufuping@ercist.iscas.ac.cn
出处:http: //www.linuxforum.net/forum/showflat.php?Cat=&Board=security&Number
日期:2005-01-06
|=-----------------------=[ Something about burneye ]=----------------------=|
|=--------------------------------------------------------------------------=|
|=-----------------=[ CoolQ <qufuping@ercist.iscas.ac.cn> ]=----------------=|
|=--------------------------------------------------------------------------=|
--[ 内容
0 - 前言
1 - 使用篇
1.1 下载
1.2 试用
1.3 patch
2 - 分析篇
2.1 源代码的结构
2.2 burneye加密的原理
2.2.1 patch 1
2.2.2 流程分析
2.2.3 patch 2
2.3 burneye的anti-debugger
2.4 burneye的loader
3 - 破解篇
3.1 加密算法
3.2 对策
4 - 几个问题
5 - 参考
--[ 0 - 前言
linuxforum上madsys的ABFrag放了好久了,也没有人感兴趣,看来Burneye还挺很神奇.
ABFrag有人说是一个假的Exploit,但我对Burneye产生了兴趣.
Burneye是Unix/Linux下少见的几个加密ELF的软件之一, 作者是TESO的成员scut.
本文共分三个部分:使用篇,分析篇,破解篇.
我使用的环境是RedHat FC2, kernel 2.6.8.1, gcc 3.3
--[ 1 - 使用篇
--[ 1.1 下载
packetstormsecurity.nl上Burneye的下载共有3个[1]:
burneye-1.0-linux-static.tar.gz, (静态编译可执行文件)
burneye-1.0.1-src.tar.bz2, (源代码包)
burneye-stripped.tar.gz
其中后两个的差别只是是否有ELF的标准文档,因此我们先把前两个都弄下来.
--[ 1.2 试用
先试着使用静态编译的Burneye
[root@localhost]# ./burneye
[root@localhost]#
[root@localhost]# strace ./burneye
execve("./burneye", ["./burneye"], [/* 20 vars */]) = 0
_exit(73)
看来burneye的二进制程序在我们的系统上不能使用,退出码73为EDOTDOT,并不常见.
接着我们编译一下源代码:
[root@localhost]# tar jvxf burneye-1.0.1-src.tar.bz2
[root@localhost]# cd src && make
....
gcc -Wall -o burneye burneye.c common.o cipher-glfsr.o cipher-rc4.o
cipher-sha1.o fingerprint.o rs.o
好,编译成功,我们来试用一下
[root@localhost]# ./burneye
usage: ./burneye [options] <program>
banner options
-b file display banner from 'file' before start
-B file display banner from 'file' on tty before start
password protect options
-p pass use password encryption with 'pass' as password
-P env first try to read password from environment 'env',
will use password from 'env' now, too, if its there
-i ignore invalid entered password and execute junk
not recommended (default: off)
fingerprinting options
-S SEAL mode (options F,f,t are ignored)
-f file use fingerprint from 'file' to protect binary
-F use fingerprint of current host (do not use -f and -F)
-t num tolerate 'num' deviations in fingerprint
-q be quiet about wrong fingerprint, just exit
(default: 0)
-E do tolerance even if erasure warning is given
-l list fingerprint tests that can be done
-e test enable fingerprint test 'test'
-d test disable fingerprint test 'test'
generic options
-o out specify another output file name (default: output)
....
burneye中的README给出了几个典型的例子:(详细介绍请参见doc/DIST-README)
burneye -o ls /bin/ls # 只作代码的obfuscate,不作加密
burneye -p "secret" -o ls /bin/ls # 使用密码'secret'加密
burneye -B warning.txt -o ls /bin/ls # 启动时显示warning.txt的信息
burneye -F -t 1 -B warning.txt -o ls /bin/ls # 使用本地主机的指纹,启动时
# 显示warning.txt的信息, 测试时允许有一项失败
fingerprint -d procpartitions -f foohost.fp
burneye -f foohost.fp -t 2 -p "hidden" -o ls /bin/ls
# 使用fingerprint产生指纹文件,允许两项失败,启用密码保护
burneye -p puke -S -o ls /bin/ls
./ls # 将会把信息封存并保存到二进制文件中
OLDHOSTNAME=`hostname`
./ls # 正常运行
hostname moo
./ls # 运行环境与封存设置不符,失败
下面我们也按照README的例子试用一下:
[root@localhost] ./burneye -p 123 -o ls.new /bin/ls
burneye - TESO ELF Encryption Engine
version 1.0.1
-------------------------------------------------------------------------------
loaded 24768 bytes @ 0x08caf170
doing pre-stub encryption (@ 0x05371000) from offset 0x0000007f
next entry point is 0x05371080
end of segment 1: 0x0000563
bogus bytes at the end, i.e. something between segments end and file end.
并没有加密的ls.new生成,看来编译的源代码也不可用!
在网上搜索了一下,好像没有人遇到过这种情况,奇怪了,难道是系统的问题?
找了一个RedHat 9, kernel 2.4.20-8, gcc 3.2.2
原样不动做了一遍,结果发现静态二进制文件可以运行,而源代码包虽然能编译,
却仍然不能加密。
--[ 1.3 patch
既然我的FC2上不能运行,那就只能自力更生了,通过阅读源代码,发现了原因,自己写了
一个patch, 一切OK。(至于这个Patch是怎么来的,请参考分析篇)
source code patch:
--------------------------- stub.lds.diff ------------------------------------
--- new/src/stub/stub.lds 2001-07-15 06:20:18.000000000 -0400
+++ old/src/stub/stub.lds 2004-12-11 18:11:13.000000000 -0500
@@ -19,6 +19,7 @@
*(.text)
*(.data)
*(.rodata)
+ *(.rodata.str1.1)
*(.bss)
callgate.o(.text)
----------------------------- stub.diff --------------------------------------
--- new/src/stub/stub.c 2002-04-07 11:13:16.000000000 -0400
+++ old/src/stub/stub.c 2004-12-11 23:04:16.000000000 -0500
@@ -715,12 +715,14 @@
/* now insertion-sort the given array */
for (n = 0 ; n < auxc ; ++n) {
- if (auxv[n].a_type >= 32) {
+ if (auxv[n].a_type >= 42) {
be_printf ("FATAL: invalid AT_* entry detected\n");
_exit (73);
}
if (auxv[n].a_type != AT_NULL) {
+ if(auxv[n].a_type >= 32)
+ continue;
a_copy[auxv[n].a_type].a_type = auxv[n].a_type;
a_copy[auxv[n].a_type].a_un.a_val = auxv[n].a_un.a_val;
be_printf ("AUXV: 0x%08lx : 0x%08lx\n", auxv[n].a_type
2.6 kernel patch:
------------------------ binfmt_elf.c.diff -----------------------------------
--- /usr/src/linux/fs/binfmt_elf.c 2004-05-08 08:56:42.000000000 -0400
+++ binfmt_elf.c 2004-12-15 20:40:36.145239824 -0500
@@ -171,7 +171,7 @@
* ARCH_DLINFO must come first so PPC can do its special alignment of
* AUXV.
*/
- ARCH_DLINFO;
+ //ARCH_DLINFO;
#endif
NEW_AUX_ENT(AT_HWCAP, ELF_HWCAP);
NEW_AUX_ENT(AT_PAGESZ, ELF_EXEC_PAGESIZE)
注意:如果你从源代码编译burneye,只需要打源代码的patch即可,如果你手头上有
burneye加密的程序,并且该程序一定要在2.6内核上运行,你需要打上内核源代码的补丁
并重新编译内核。当然,你也可以为二进制的加密程序打patch,不过由于加密程序的
生成环境比较复杂,这种patch也不容易制作。
--[ 2 分析篇
--[ 2.1 源代码的结构
src/burneye.c 命令行主程序
stub/callgate.asm -+
stub/cgate.c |
stub/cipher-glfsr-c.c | 加密解密相关程序,其中glfsr是整个解密程序的入口,
stub/cipher-glfsr.asm +->由glfsr完成对加密程序的de-obfuscate,之后,转入
stub/cipher-rc4.c | init.asm的be_entry处
stub/cipher-sha1.c |
stub/rs.c -+
stub/fingerprint.c 生成机器的特征指纹
stub/help.c 常用函数
stub/init.asm 程序初始化入口,分配好ELF32_auxv_t结构后,转入
stub.c的burneye函数
stub/stub.c 这是加密程序运行的主函数
stub/stub.lds 这是一个连接脚本,以后还要讨论
stub/unlink_stub.asm 删除文件的程序
stub/utils/hdump.c 将二进制文件以十六进制形式打出
stub/utils/sstrip.c 处理ELF文件,使得ELF只保留ELF文件头,PHDR和所有的
Segment,其余的无用Section和Section Header一律抛弃
--[ 2.2 burneye加密的原理
burneye类似与Windows下一般的加壳工具,但是又有自己的特色,先来分析一下用
burneye加密后文件的结构:
[ELF头][程序头][Burneye常用例程][stub头][加密后的程序]
其中前三部分,Burneye使用了一个技巧,在编译Burneye命令时,生成了一个很大的数组
stub_bin[],其实就是一个通用的ELF头+程序头+Burneye常用例程,加密程序时,只需要对
数组里的某些字段稍作修改即可.
--[ 2.2.1 patch 1
下面我们来看一下stub_bin[]是怎么生成的,这样我们才能明白为什么原来的源代码无法
使用.
src/stub/Makefile:
stub.bin.h: stub.bin
echo "unsigned char stub_bin[] =" > stub-bin.h
utils/hdump < stub.bin >> stub-bin.h
egrep "\.text.+0x.+0x.+cipher-glfsr.o" stub.map | \
awk '{ printf ("#define BE_LAYER0_START %s\n", $$2); }' \
>> stub-bin.
...
stub.bin: buildtools $(OBJS) stub.lds
sed s/entry_point/$(ENTRY_POINT)/ < stub.lds > stub-bin.lds
ld -Map stub.map -s -T stub-bin.lds -o stub.bin $(OBJS)
rm -f stub-bin.lds
ifeq ($(rel),on)
strip stub.bin
utils/sstrip stub.bin
endif
可见,stub.bin.h实际上就是stub.bin的十六进制形式,然后添加几个宏构成的。而
stub.bin则是由ld将所有的目标文件按照stub-bin.lds的内容连接而成,并将结果
去除符号表并抛弃多余的Section/Section header。
问题的关键就是这个连接脚本,为什么原来的burneye编译以后无法加密呢?我们先看
一下stub.lds
PHDRS
{
text PT_LOAD FILEHDR PHDRS FLAGS (0x0007); /* PF_R | PF_W | PF_X */
data PT_LOAD FLAGS (0x0006); /* PF_R | PF_W */
}
SECTIONS
{
. = 0x05371000 ;
.text : {
cipher-glfsr.o(.text)
init.o(.text)
*(.text)
*(.data)
*(.rodata)
*(.bss)
callgate.o(.text)
PROVIDE (be_stubhdr_u = ABSOLUTE(.)) ;
LONG (0x41012345) ;
} : text
/* dummy segment to set fixed brk(0) value in kernelspace */
. = 0x08048000 ;
.data : {
} : data
/* save calls to objcopy, huh */
/DISCARD/ : {
*(.eh_frame)
*(.comment)
*(.note)
}
}
用这个脚本生成的stub.bin应该只有两个程序段,第一个段应该把原来所有目标文件
的大部分节都整合在一起,包括所有OBJS的.text, .data, .rodata, .bss.
再看一下原来加密出错时的源代码:
if (stub_len != phdr[0].p_offset + phdr[0].p_memsz) {
fprintf (stderr, "bogus bytes at the end, i.e. something "
"between segments end and file end.\n");
exit (EXIT_FAILURE);
}
其中stub_len就是stub_bin[]的长度,注意到phdr[0]中的0了么?这是指第一个程序段,
也就是说,程序期望stub.bin的第二个程序段的长度是0! 否则应该写成
if (stub_len != phdr[1].p_offset + phdr[1].p_memsz)
但实际上,我们生成的stub.bin,第二个程序段大小并不为0,我们修改Makefile文件,
将strip和sstrip的步骤省略,然后readelf一下stub.bin,发现了:
[root@localhost]# readelf -l stub.bin
...
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x05370000 0x05370000 0x05630 0x05630 RWE 0x1000
LOAD 0x006000 0x00000000 0x00000000 0x000c1 0x000c1 RW 0x1000
Section to Segment mapping:
Segment Sections...
00 .text
01 .rodata.str1.1 <---- 看见了吧? 加密不成就是多出来的这个Section.
出现.rodata.str1.1是编译器的原因,当然这是GCC 3.3的结果,我在Redhat 7.2上
还出现了.rodata.str1.32。
因此,我们需要在lds中把.rodata.str1.1也添加进去(当然如果有.32,也需要加上)。
这里还有一个问题,为什么不只用一个段呢?第二个空段是做什么用的呢?
你可以看一下doc/README的内容。
--[ 2.2.2 流程分析
接下来按照Burneye的代码,把整体流程简要描述一下
加密过程:(主要是burneye.c的wrap函数)
burneye首先以加密的文件作为对象,将stub_bin[]作为加密后文件的ELF头+程序头+
Burneye常用例程,然后按照burneye的命令选项,对文件进行逐层的处理,顺序是:
tag - banner - password - fingerprint - glfsr(obfuscate)
每层处理,都会在stub头中保存相应的标志和信息(例如加密时的随机序列,sha1哈希值)
,然后将结果放到最后一部分中。
解密过程:(主要是cipher-glfsr.asm,init.asm和stub.c中的burneye函数)
加密程序的入口是cipher-glfsr.asm的glent,负责de-obfuscated程序,去掉第一层保护,
接下来由init.asm处理一下运行程序的栈结构,并将系统控制权交给stub.c的burneye。
burneye是最主要的部分,会按照顺序处理Tag并显示Banner(如果有的话),接下来,是
fingerprint的检测,然后会提示你输入加密时设定的密码,如果一切OK,就将加密的
程序解密,然后调用burneye中自带的Loader,将解密后的程序映射到内存中,如过解密
的程序需要interpreter,loader也会负责将interpreter装入到内存中,最后将控制权交
给interpreter或者解密后的ELF。至此,一切恢复正常.
--[ 2.2.3 patch 2
打完了patch1,用burneye加密程序没有错误信息,但是问题依然存在:
[root@localhost]# ./burneye -p 1234 -o ls.new /bin/ls
XXX: stub_len = 0x00005720
phdr 1 @ 0x05370000
phdr 2 @ 0x00000000
burneye - TESO ELF Encryption Engine
version 1.0.1
------------------------------------------------------------------------------
loaded 22256 bytes @ 0x094d2170
doing pre-stub encryption (@ 0x05371000) from offset 0x0000007f
next entry point is 0x05371080
end of segment 1: 0x000056f0
brk(0) to force is 0x0805bff3
obfuscation layer: key = 0x52e8b1d3
------------------------------------------------------------------------------
[root@localhost]# ./ls.new
[root@localhost]#
[root@localhost]# strace ./ls.new
execve("./ls.new", ["./ls.new"], [/* 20 vars */]) = 0
_exit(73)
加密的程序不能运行! 跟前面的结果一样。通过程序的Debug选项,我们找到了出错的
位置:
for (n = 0 ; n < auxc ; ++n) {
if (auxv[n].a_type >= 32) {
be_printf ("FATAL: invalid AT_* entry detected\n");
_exit (73);
}
这里的auxv是一个ELF32_auxv_t的结构,一般人可能不太接触,这里从[2]上偷一段:
When the userspace receives control, the stack layout has a fixed format.
The rough order is this:
<arguments> <environ> <auxv> <string data>
The detailed layout, assuming IA32 architecture, is this (Linux kernel
series 2.2/2.4):
position content size (bytes) + comment
------------------------------------------------------------------------
stack pointer -> [ argc = number of args ] 4
[ argv[0] (pointer) ] 4 (program name)
[ argv[1] (pointer) ] 4
[ argv[..] (pointer) ] 4 * x
[ argv[n - 1] (pointer) ] 4
[ argv[n] (pointer) ] 4 (= NULL)
[ envp[0] (pointer) ] 4
[ envp[1] (pointer) ] 4
[ envp[..] (pointer) ] 4
[ envp[term] (pointer) ] 4 (= NULL)
[ auxv[0] (Elf32_auxv_t) ] 8
[ auxv[1] (Elf32_auxv_t) ] 8
[ auxv[..] (Elf32_auxv_t) ] 8
[ auxv[term] (Elf32_auxv_t) ] 8 (= AT_NULL vector)
[ padding ] 0 - 16
[ argument ASCIIZ strings ] >= 0
[ environment ASCIIZ str. ] >= 0
(0xbffffffc) [ end marker ] 4 (= NULL)
(0xc0000000) < top of stack > 0 (virtual)
------------------------------------------------------------------------
auxv主要是为内核与用户程序(主要是Loader)协同而设置的。2.4内核构造的auxv,
其类型最多不超过32(其实最大也就是23 - AT_SECURE),但是到了2.6内核,又出了
几种新的类型,因此,程序到这一步就出错了。通过GDB调试一个普通的程序,发现
栈中确实有类型为32(AT_SYSINFO)和33(AT_SYSINFO_EHDR)的auxv存在。
解决的方法就是将大于等于32的auxv跳过去(其实Loader只需五种类型的auxv)。
patch 2就这么产生了。
--[ 2.3 burneye的anti-debugger
主要有两种,一种是反编译,一种是反跟踪
反编译是通过对代码进行obfuscate(glfsr),使objdump等工具失效。
反跟踪使用了int3,并在程序里自己截获SIGTRAP,具体的代码是:(参考[3])
antistrace ();
if (nottraced == 0)
killme ()
void be_sigtrap (int signum)
{
nottraced++;
}
static inline int antistrace(void)
{
long ret;
__asm__ __volatile__ ("int $0x03\n\t"
:"=a" (ret)
: );
return (ret);
}
static inline int killme()
{
long ret;
__asm__ __volatile__ ("xorl %%eax, %%eax\t\n"
"xorl %%ebx, %%ebx\t\n"
"xorl %%ecx, %%ecx\t\n"
"xorl %%edx, %%edx\t\n"
"xorl %%esi, %%esi\t\n"
"xorl %%edi, %%edi\t\n"
"xorl %%ebp, %%ebp\t\n"
"xorl %%esp, %%esp\t\n"
"jmp %%esi"
:"=a" (ret)
: );
} return ret;
--[ 2.4 burneye的loader
由于解密以后的程序与burneye的例程函数是位于同一进程空间的,因此如果想要执行
解密后的程序,例程就需要自己装载程序(普通情况下,程序是由内核来完成装载任务的)
,burneye是通过be_remap来实现的,如果加密之前的程序是动态链接的,还需调用
be_mapinterpreter来装载interpreter。因此,即使你file ls.new结果是statically
linked, 你查看/proc/PID/maps发现多个.so时,也不要惊奇。
--[ 3 破解篇
--[ 3.1 加密算法
burneye的保护主要有三层,最外层的是通过glfsr算法对代码进行的obfuscate,第二层
和第三层以主机的某些特征(fingerprint)和密码作为密钥KEY,首先算出密钥的SHA1哈希
值DIG,然后从随机发生序列器中获得160位的随机数RAN,并与SHA1哈希值异或,得到
KEY2,然后用KEY2对需要程序进行RC4加密。burneye还将加密之前的明文SHA1哈希值保存
到stub头的相应部分,这样做密码验证时,能够判断密码的正确性。
--[ 3.2 对策
最外层的glfsr,虽然比XOR加密要好得多,但是仍然是相对较弱的加密,很容易对付。
第二/三层的加密,算法是比较强的,只能用暴力方法解决,但是如果方法得当,能够大大
提高解密的速度。来看看如何提高解密速度:
原来的加密程序
SHA1HashLen (pw_pass, strlen (pw_pass), pw_hash);
for (key_n = 0 ; key_n < sizeof (pw_hash) ; ++key_n)
pw_hash[key_n] ^= xor_hash[key_n];
SHA1HashLen (pw_hash, sizeof (pw_hash), pw_hash);
rc4_prepare_key (pw_hash, sizeof (pw_hash), &key_r);
rc4_cipher (exe_data, exe_len, &key_r);
其中,pw_pass就是密码字符串,exe_data就是需要加密的整个文件.
解密的时候使用的还是上边的代码,pw_pass是你输入的密码,而xor_hash保存在加密文件的
stub头中。
是不是需要将所有的exe_data都解密,然后判断密码的正确性呢?不是的,burneye在这一
方面有欠缺。exe_data其实就是待加密的整个ELF文件,因此,前面一部分必定是ELF头,
这样,我们只须解密exe_data的前几个字节,看是不是合法的ELF文件头即可,无须解密
整个exe_data,例如:
static char ELF_HEADER[] = "\x7f\x45\x4c\x46\x01\x01\x01"
"\0\0\0\0\0\0\0\0\0\x02\0\x03\0";
...
rc4_cipher (exe_data, sizeof(ELF_HEADER), &key_r);
if(!strncmp(exe_data, ELF_HEADER, sizeof(ELF_HEADER))
printf("Match!!\n");
互联网上能够找到几个暴力破解Burneye加密的工具包:
[http://www.rootshell.be/~ioc/releases/ioc6-final.tar.gz]
[http://66.230.171.10/releases/Crypto/UNFburninhell/UNFburninhell1.0c.tar.gz]
其中ioc6使用了上面的方法,破解的速度比较快。
--[ 4 几个问题
在研究Burneye的过程中,发现了几个问题,目前还没解决,如果哪位高人知道原因,请
给我回信,不甚感激。
o 问题一
用GDB对加密的程序进行调试,断点设置在ELF头entry point时无效:
根据Fenris的README,Fenris似乎可以设置断点[4]
[root@localhost]# readelf -l ./ls.new | grep entry
Entry point address: 0x5371035
[root@localhost]# gdb -q ./ls.new
...
(gdb) b *0x5371035
Breakpoint 1 at 0x5371035
(gdb) r
Starting program: /burneye/src/ls.new
warning: shared library handler failed to enable breakpoint
o 问题二
用strace时,结果如下:
execve("./ls.new", ["./ls.new"], [/* 20 vars */]) = 0
signal(SIGTRAP, 0x5371991) = ? ERESTARTNOINTR (To be restarted)
signal(SIGTRAP, 0x5371991) = ? ERESTARTNOINTR (To be restarted)
signal(SIGTRAP, 0x5371991) = ? ERESTARTNOINTR (To be restarted)
signal(SIGTRAP, 0x5371991) = ? ERESTARTNOINTR (To be restarted)
...
无限循环
这两个问题,用我在Burneye中看到的antistrace都无法解释。
--[ 5 参考
[1] [http://www.packetstormsecurity.nl/groups/teso/index2.html]
[http://www.packetstormsecurity.nl/groups/teso/burneye-1.0-linux-static.tar.gz]
[http://www.packetstormsecurity.nl/groups/teso/burneye-1.0.1-src.tar.bz2]
[http://www.packetstormsecurity.nl/groups/teso/burneye-stripped.tar.gz]
[2] [http://www.phrack.org/show.php?p=58&a=5]
[3] [http://vx.netlux.org/lib/vsc04.html]
[4] [http://lcamtuf.coredump.cx/fenris/be.txt]
版权所有,未经许可,不得转载