将PIE可执行程序转换成动态链接库
source link: https://mp.weixin.qq.com/s?__biz=MzUzMjQyMDE3Ng%3D%3D&%3Bmid=2247484560&%3Bidx=1&%3Bsn=0644d39ff3a2ce050fc7cf35a4c758f7
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
在《IDA flare-emu示例》中提过一句将普通可执行ELF转成.so文件,bluerust就此向我推荐开源项目:
https://github.com/lief-project/LIEF
其原话是,"高级、好用"。bluerust本职工作是说单口相声的,平时嘴贫得不行,还都是用英语砸我,难得用一次汉语,居然才四个字。这我一定得试试。
考虑这样一种场景,某ELF中有未导出函数对in实现某种数学运算产生out,暂时搞不清细节,想快速利用之。简单情形可以直接读到内存里当成shellcode那样的可移动代码用,复杂情形只好具体问题具体分析。
我最早干的是用dlopen()加载传统可执行程序,本文介绍的是将PIE转成动态链接库,二者有所区别。不过来都来了,就过一遍吧。
本文是LIEF官方文档节选某一小段后的中译版,没有原创内容。
Transforming an ELF executable into a library https://lief.quarkslab.com/doc/latest/tutorials/08_elf_bin2lib.html
本文在x64/Ubuntu 16.04.6 LTS中测试。
安装LIEF:
python3 -m pip install --upgrade pip pip3 install setuptools --upgrade pip3 install lief
简单测试LIEF:
import lief binary = lief.parse( "/bin/ls" ) print( binary )
一切正常的话,会看到很大一片输出,都是ELF相关信息。
true、ssh都是可执行ELF,但二者的Type不同;true是传统可执行程序,ssh是所谓的位置无关可执行程序(PIE)。PIE与.so一样,其加载基址是浮动的。用gdb调试PIE时,若想断在"Entry point",参看《GDB启动被调试进程时如何尽早断下》,需要一些奇技淫巧。
vi lief_sample_0.py
# -*- encoding: cp936 -*- # # python3 lief_sample_0.py /usr/bin/ssh # python3 lief_sample_0.py /lib/x86_64-linux-gnu/libc.so.6 # import sys import lief filename = sys.argv[1] binary = lief.parse( filename ) print( binary.header.file_type ) n = len( binary.exported_functions ) print( n ) if ( n > 0 ) : print( binary.exported_functions[0] )
$ python3 lief_sample_0.py /usr/bin/ssh E_TYPE.DYNAMIC 9 mkstemp - 0x67fe0 $ python3 lief_sample_0.py /lib/x86_64-linux-gnu/libc.so.6 E_TYPE.DYNAMIC 2062 putwchar - 0x710f0
PIE与.so的主要区别在于导出符号,ssh只有很少的导出符号,libc.so有很多导出符号。
vi lief_sample_1.c
#if 0 x64/Ubuntu 16.04.6 LTS + gcc 5.4.0 gcc -Wall -pipe -O3 -s -o lief_sample_1_a lief_sample_1.c gcc -Wall -pipe -O3 -fvisibility=hidden -fPIE -pie -Wl,-strip-all,--hash-style=both -o lief_sample_1_b lief_sample_1.c #endif #include <stdlib.h> #include <stdio.h> #include <string.h> static __attribute__((noinline)) int check ( char *sth ) { if ( strcmp( sth, "magic" ) == 0 ) { return( 1 ); } return( 0 ); } int main ( int argc, char * argv[] ) { if ( argc != 2 ) { printf( "Usage: %s <sth>\n", argv[0] ); return( -1 ); } if ( check( argv[1] ) ) { printf( "Ok\n" ); } else { printf( "Try again\n" ); } return( 0 ); }
本例刻意对比不同编译方式:
尽管lief_sample_1_b是PIE,但没有导出符号。
$ ./lief_sample_1_b magic Ok $ ./lief_sample_1_b other Try again
用IDA反汇编lief_sample_1_b,check()符号被抹去,其相对加载基址的内存偏移是0x860,不要用文件偏移。
vi lief_sample_2.py
# -*- encoding: cp936 -*- # # python3 lief_sample_2.py <piefile> <funcoff> <funcname> <sofile> # python3 lief_sample_2.py lief_sample_1_b 0x860 check lief_sample_1_b.so # import sys import lief piefile = sys.argv[1] funcoff = int( sys.argv[2], 0 ) funcname = sys.argv[3] sofile = sys.argv[4] binary = lief.parse( piefile ) binary.add_exported_function( funcoff, funcname ) try : # # glibc >= 2.29 deny calls to dlopen with PIE binaries. so we remove # DF_1_PIE flag. # binary[lief.ELF.DYNAMIC_TAGS.FLAGS_1].remove( lief.ELF.DYNAMIC_FLAGS_1.PIE ) except AttributeError : pass binary.write( sofile )
用LIEF将PIE转成.so:
python3 lief_sample_2.py lief_sample_1_b 0x860 check lief_sample_1_b.so
意思是,PIE加载后偏移0x860处的代码为之起名check(),按此要求生成.so。
$ python3 lief_sample_0.py lief_sample_1_b.so E_TYPE.DYNAMIC 1 check - 0x4860
对比lief_sample_1_b、lief_sample_1_b.so:
$ readelf --dyn-syms lief_sample_1_b Symbol table '.dynsym' contains 13 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000668 0 SECTION LOCAL DEFAULT 12 ... 12: 0000000000201040 0 NOTYPE GLOBAL DEFAULT 27 __bss_start $ readelf --dyn-syms lief_sample_1_b.so Symbol table '.dynsym' contains 14 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000004668 0 SECTION LOCAL DEFAULT 12 ... 12: 0000000000205040 0 NOTYPE GLOBAL DEFAULT 27 __bss_start 13: 0000000000004860 0x7bb22900 FUNC GLOBAL DEFAULT 15 check $ readelf -s lief_sample_1_b.so | grep "FUNC GLOBAL" 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2) 4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2) 6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __printf_chk@GLIBC_2.3.4 (3) 13: 0000000000004860 0x7bb22900 FUNC GLOBAL DEFAULT 15 check $ nm -D lief_sample_1_b.so | grep " T " 0000000000004860 T check $ objdump -T lief_sample_1_b.so | grep "g DF" 0000000000004860 g DF .text 000000007bb22900 check
常规ELF工具已经看到lief_sample_1_b.so中的导出符号check,IDA反汇编更显眼。
lief_sample_1_b.so现在可以当成可执行程序用,也可当成动态链接库用。
$ chmod +x lief_sample_1_b.so $ ./lief_sample_1_b.so magic Ok $ ./lief_sample_1_b.so other Try again
下面写个程序dlopen()打开lief_sample_1_b.so,调用其中的check()。
vi lief_sample_3.c
#if 0 x64/Ubuntu 16.04.6 LTS + gcc 5.4.0 gcc -Wall -pipe -O3 -s -o lief_sample_3 lief_sample_3.c -ldl #endif #include <stdio.h> #include <stdlib.h> #include <dlfcn.h> typedef int ( *some_t ) ( char * ); int main ( int argc, char * argv[] ) { char *sofile, *funcname, *sth; void *so; some_t some; int someret; if ( argc != 4 ) { printf( "Usage: %s <sofile> <funcname> <sth>\n", argv[0] ); return( -1 ); } sofile = argv[1]; funcname = argv[2]; sth = argv[3]; so = dlopen( sofile, RTLD_LAZY ); if ( !so ) { fprintf( stderr, "dlopen error: %s\n", dlerror() ); return( -1 ); } some = ( some_t )dlsym( so, funcname ); someret = some( sth ); printf( "%s(\"%s\")=%d\n", funcname, sth, someret ); if ( someret ) { printf( "Ok\n" ); } else { printf( "Try again\n" ); } return( 0 ); }
$ ./lief_sample_3 ./lief_sample_1_b.so check magic check("magic")=1 Ok $ ./lief_sample_3 ./lief_sample_1_b.so check other check("other")=0 Try again
lief_sample_2.py负责PIE转.so。更多编程细节参看:
LIEF API https://lief.quarkslab.com/doc/latest/api/index.html ELF API https://lief.quarkslab.com/doc/latest/api/python/elf.html
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK