13

将PIE可执行程序转换成动态链接库

 3 years ago
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.
neoserver,ios ssh client
YzU7NjB.png!mobile

在《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相关信息。

VNZfYv.png!mobile

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 );
}

本例刻意对比不同编译方式:

ne6ZJrz.png!mobile

尽管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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK