内容纲要

UPX

面试问了壳 发现了解的实在太浅了 自己学习实在是太过于浮于表面了

原理

upx可以完成代码的压缩和实时解压执行。且不会影响程序的执行效率。

实时解压的原理可以使用一下图形表示:
1==>2==>3==>4==>5==>6
假设1是upx插入的代码,2,3,4是压缩后的代码。5,6是随便的什么东西。
程序从1开始执行。而1的功能是将2,3,4解压缩为7,8,9。7,8,9就是2,3,4在压缩之前的形式。
1==>7==>8==>9==>5==>6

连起来就是:

最初代码的形式就应该是:7==>8==>9==>5==>6
用upx压缩之后形式为:1==>2==>3==>4==>5==>6
执行时的形式变为:1==>7==>8==>9==>5==>6

源码分析

从main函数开始

开始部分

main.cpp main()

image-20210530015254729

main.cpp upx_main()

image-20210530015334483

如果ucl初始化失败,输出信息

image-20210530015401041

main.cpp first_options()

第一类选项,一些查看用法的选项

image-20210530015417023

如:

upx --

image-20210530015428350

upx --no-env

image-20210530015506855

main.cpp do_option()

有很多的switch选项

避免-h和--help,区别有待考察

输出help信息

image-20210530015524586

输出version信息

image-20210530015541426

main.cpp upx_main()

如果没有定义no_env则get_envoptions(定义选项),同时检测有没有定义选项成功

image-20210530015554236

这里opt的类型结构(很长 放一段

image-20210530015629361

image-20210530015642115

main.cpp get_options()

定义了一些选项

image-20210530015656259

进行了do_option 给opt.cmd赋了值

image-20210530015714107

mfx_options结构:

image-20210530015723366

main() upx_main()

前面do_option给opt.cmd进行了赋值,现在开始分支走向

如果cmd为空,即我们输入了upx [file],设置cmd=压缩加upx壳

help,version,license即直接显示,version选项为之前的-V的setcmd所用,而之前的--version直接输出是show_version(0),这里为1

image-20210530015754290

看一下区别

……没有区别

image-20210530015808220

main.cpp upx_main()

check options

image-20210530015822017

main.cpp check_options()

排除一些不能同时选的选项,同时对opt的参数赋值

image-20210530015830381

main.cpp upx_main()

start work

可以看到重点是do_files

image-20210530015841592

工作原理部分

work.cpp do_files

一些try—catch,还有调用了do_one_file

image-20210529151415077

work.cpp do_one_file()

进行了一些判断

image-20210529170531773

创建了一个PackMaster类的pm

同时调用对应命令

image-20210529170613649

PackMaster.h class PackMaster

image-20210529170756446

PackMaster.cpp PackMaster::pack()

image-20210529170903310

image-20210529185031060

packer.cpp Packer::doPack()

image-20210529185209881

packmast.cpp PackMaster::visitAllPackers()

对应平台和架构

image-20210529202044249

packmast.cpp try_pack()

visitAllPackers的参数调用

image-20210529203048534

packer.cpp Packer::initPackHeader()

获得文件的信息

image-20210529203220003

image-20210529203351557

packer.cpp Packer::updatePackHeader()

在canPack后进行的操作

image-20210529204231892

image-20210529204203308

Packer

p_w32pe

canPack()

判断调用了readFileHeader()

image-20210529221244639

PackW32Pe::readFileHeader()

image-20210529221349622

image-20210529221521431

PeFile::readFileHeader()

定义了一些字符

image-20210529221744721

LE16双字节

image-20210529221733088

pefile.cpp PeFile::readFileHeader()

匹配签名和PE头,ic,pe_offset+delta

image-20210529230405382

返回信息

image-20210529234027141

p_w32pe.cpp PackW32Pe::pack()

image-20210529234915787

调用了一个近500行的函数pack0

image-20210529235002723

pefile.cpp pack0()

image-20210529235637954

pefile/cpp readSectionHeaders()

检验段头

image-20210529235535731

pefile.cpp PeFile::checkHeadValues()

排除一些不可加壳或已加壳的文件

image-20210529235756048

image-20210530000009356

image-20210529235945989

pefile.cpp pack0()

处理一些段表

image-20210530000308196

开始进行压缩处理

image-20210530001029059

image-20210530001042784

image-20210530001112887

packer.cpp Packer::compressWithFilters()

对被压缩文件进行拷贝

image-20210530001324045

压缩方法的选择

image-20210530001746786

packer.cpp prepareMethods()

image-20210530002112410

一些方法:

image-20210530002101780

packer.cpp Packer::compressWithFilters()

all methods 调用upx_compress进行压缩

image-20210530005600047

compress.cpp upx_compress()

image-20210530005755822

三种压缩方法

image-20210530005816835

lzma

原理介绍:

索引最近环缓冲区数据、解压缩时根据索引从环缓冲区取回原始的数据;MA意思是用了Markov Chain,也就是维护了一个类似状态机的东西,可以在压缩过程中调整当前所处位置信息的特性,当然,最后还要用区间编码进行熵编码后输出结果。

int upx_lzma_compress      ( const upx_bytep src, unsigned  src_len,
                                   upx_bytep dst, unsigned* dst_len,
                                   upx_callback_p cb,
                                   int method, int level,
                             const upx_compress_config_t *cconf_parm,
                                   upx_compress_result_t *cresult )
{
    assert(M_IS_LZMA(method));
    assert(level > 0); assert(cresult != nullptr);

    int r = UPX_E_ERROR;
    HRESULT rh;
    const lzma_compress_config_t *lcconf = cconf_parm ? &cconf_parm->conf_lzma : nullptr;
    lzma_compress_result_t *res = &cresult->result_lzma;

    MyLzma::InStream is; is.AddRef();
    MyLzma::OutStream os; os.AddRef();
    is.Init(src, src_len);
    os.Init(dst, *dst_len);

    MyLzma::ProgressInfo progress; progress.AddRef();
    progress.cb = cb;

    NCompress::NLZMA::CEncoder enc;
    const PROPID propIDs[8] = {
        NCoderPropID::kPosStateBits,        // 0  pb    _posStateBits(2)
        NCoderPropID::kLitPosBits,          // 1  lp    _numLiteralPosStateBits(0)
        NCoderPropID::kLitContextBits,      // 2  lc    _numLiteralContextBits(3)
        NCoderPropID::kDictionarySize,      // 3  ds
        NCoderPropID::kAlgorithm,           // 4  fm    _fastmode
        NCoderPropID::kNumFastBytes,        // 5  fb
        NCoderPropID::kMatchFinderCycles,   // 6  mfc   _matchFinderCycles, _cutValue
        NCoderPropID::kMatchFinder          // 7  mf
    };
    PROPVARIANT pr[8];
    const unsigned nprops = 8;
    static const wchar_t matchfinder[] = L"BT4";
    assert(NCompress::NLZMA::FindMatchFinder(matchfinder) >= 0);
    pr[7].vt = VT_BSTR; pr[7].bstrVal = ACC_PCAST(BSTR, ACC_UNCONST_CAST(wchar_t *, matchfinder));
    pr[0].vt = pr[1].vt = pr[2].vt = pr[3].vt = VT_UI4;
    pr[4].vt = pr[5].vt = pr[6].vt = VT_UI4;
    if (prepare(res, src_len, method, level, lcconf) != 0)
        goto error;
    pr[0].uintVal = res->pos_bits;
    pr[1].uintVal = res->lit_pos_bits;
    pr[2].uintVal = res->lit_context_bits;
    pr[3].uintVal = res->dict_size;
    pr[4].uintVal = res->fast_mode;
    pr[5].uintVal = res->num_fast_bytes;
    pr[6].uintVal = res->match_finder_cycles;

    try {

    if (enc.SetCoderProperties(propIDs, pr, nprops) != S_OK)
        goto error;
    if (enc.WriteCoderProperties(&os) != S_OK)
        goto error;
    if (os.overflow) {
        //r = UPX_E_OUTPUT_OVERRUN;
        r = UPX_E_NOT_COMPRESSIBLE;
        goto error;
    }
    assert(os.b_pos == 5);
    os.b_pos = 0;
    // extra stuff in first byte: 5 high bits convenience for stub decompressor
    unsigned t = res->lit_context_bits + res->lit_pos_bits;
    os.WriteByte(Byte((t << 3) | res->pos_bits));
    os.WriteByte(Byte((res->lit_pos_bits << 4) | (res->lit_context_bits)));

    rh = enc.Code(&is, &os, nullptr, nullptr, &progress);

    } catch (...) {
        rh = E_OUTOFMEMORY;
    }

    assert(is.b_pos <=  src_len);
    assert(os.b_pos <= *dst_len);
    if (rh == E_OUTOFMEMORY)
        r = UPX_E_OUT_OF_MEMORY;
    else if (os.overflow)
    {
        assert(os.b_pos == *dst_len);
        //r = UPX_E_OUTPUT_OVERRUN;
        r = UPX_E_NOT_COMPRESSIBLE;
    }
    else if (rh == S_OK)
    {
        assert(is.b_pos == src_len);
        r = UPX_E_OK;
    }

error:
    *dst_len = (unsigned) os.b_pos;
    //printf("\nlzma_compress: %d: %u %u %u %u %u, %u - > %u\n", r, res->pos_bits, res->lit_pos_bits, res->lit_context_bits, res->dict_size, res->num_probs, src_len, *dst_len);
    //printf("%u %u %u\n", is.__m_RefCount, os.__m_RefCount, progress.__m_RefCount);
    return r;
}

ucl

int upx_ucl_compress       ( const upx_bytep src, unsigned  src_len,
                                   upx_bytep dst, unsigned* dst_len,
                                   upx_callback_p cb_parm,
                                   int method, int level,
                             const upx_compress_config_t *cconf_parm,
                                   upx_compress_result_t *cresult )
{
    int r;
    assert(level > 0); assert(cresult != nullptr);

    COMPILE_TIME_ASSERT(sizeof(ucl_compress_config_t) == sizeof(REAL_ucl_compress_config_t))

    ucl_progress_callback_t cb;
    cb.callback = nullptr;
    cb.user = nullptr;
    if (cb_parm && cb_parm->nprogress) {
        cb.callback = wrap_nprogress_ucl;
        cb.user = cb_parm;
    }

    ucl_compress_config_t cconf; cconf.reset();
    if (cconf_parm)
        memcpy(&cconf, &cconf_parm->conf_ucl, sizeof(cconf)); // cconf = cconf_parm->conf_ucl; // struct copy

    ucl_uint *res = cresult->result_ucl.result;
    // assume no info available - fill in worst case results
    //res[0] = 1;                 // min_offset_found - NOT USED
    res[1] = src_len - 1;       // max_offset_found
    //res[2] = 2;                 // min_match_found - NOT USED
    res[3] = src_len - 1;       // max_match_found
    //res[4] = 1;                 // min_run_found - NOT USED
    res[5] = src_len;           // max_run_found
    res[6] = 1;                 // first_offset_found
    //res[7] = 999999;            // same_match_offsets_found - NOT USED

    // prepare bit-buffer settings
    cconf.bb_endian = 0;
    cconf.bb_size = 0;
    if (method >= M_NRV2B_LE32 && method <= M_NRV2E_LE16)
    {
        static const unsigned char sizes[3] = {32, 8, 16};
        cconf.bb_size = sizes[(method - M_NRV2B_LE32) % 3];
    }
    else {
        throwInternalError("unknown compression method");
        return UPX_E_ERROR;
    }

    // optimize compression parms
    if (level <= 3 && cconf.max_offset == UCL_UINT_MAX)
        cconf.max_offset = 8*1024-1;
    else if (level == 4 && cconf.max_offset == UCL_UINT_MAX)
        cconf.max_offset = 32*1024-1;

    if M_IS_NRV2B(method)
        r = ucl_nrv2b_99_compress(src, src_len, dst, dst_len,
                                  &cb, level, &cconf, res);
    else if M_IS_NRV2D(method)
        r = ucl_nrv2d_99_compress(src, src_len, dst, dst_len,
                                  &cb, level, &cconf, res);
    else if M_IS_NRV2E(method)
        r = ucl_nrv2e_99_compress(src, src_len, dst, dst_len,
                                  &cb, level, &cconf, res);
    else {
        throwInternalError("unknown compression method");
        return UPX_E_ERROR;
    }

    // make sure first_offset_found is set
    if (res[6] == 0)
        res[6] = 1;

    return convert_errno_from_ucl(r);
}

其他:

help.cpp的show_head()

image-20210527234243966

用于显示信息,如:

image-20210527234342691

main.cpp set_cmd()

image-20210528002222773

一些函数:

attribute_format:此属性使编译器检查所提供的参数对于指定函数而言是否采用正确的格式

流程图

image-20210529224002252

UPX防脱壳

以helloworld.exe为例

upx后,增加了区段和特征

image-20210530011639168

image-20210530013728138

修改这些upx

image-20210530012554228

image-20210530012538433

虽然没有-d成功,但还是会检测出有修改

识别upx的特征码:

特征码1:60 BE ?? ?? ?? 00 8D BE ?? ?? ?? FF
特征码2:60 BE ?? ?? ?? ?? 8D BE ?? ?? ?? ?? 57 EB 0B 90 8A 06 46 88 07 47 01 DB 75 ?? 8B 1E 83 ?? ?? 11 DB 72 ?? B8 01 00 00 00 01 DB 75
特征码3:55 FF 96 ?? ?? ?? ?? 09 C0 74 07 89 03 83 C3 04 EB ?? FF 96 ?? ?? ?? ?? 8B AE ?? ?? ?? ?? 8D BE 00 F0 FF FF BB 00 10 00 00 50 54 6A 04 53 57 FF D5 8D 87 ?? ?? 00 00 80 20 7F 80 60 28 7F 58 50 54 50 53 57 FF D5 58 61 8D 44 24 80 6A 00 39 C4 75 FA 83 EC 80

1.在特征值处修改upx的花指令或者加花

2.加区段

3.修改PE标识符位置

我的一些疑惑

upx加壳后,会有区段upx0,upx1,upx2,和特征upx!,所加的区段是什么?image-20210530234233655

image-20210530234313159

来源:UPX脱壳理解,手动脱壳讲的很好

其他:

经过UPX压缩的win32/pe文件,包含三个区段:UPX0, UPX1, .rsrc或UPX0, UPX1, UPX2(原文件本身无资源时)。
UPX0:在文件中没有内容,它的"Virtual size"加上UPX1的构成了原文件全部区段需要的内存空间,相当于区段合并。
UPX1:起始位置为需解压缩的源数据,目标地址为UPX0基址。紧接着源数据块是"UPX stub",即壳代码。一个典型的pushad/popad结构,所以人们常用"ESP定律"来脱UPX。
.rsrc/UPX2:在原文件有资源时,含有原资源段的完整头部和极少部分资源数据(类型为ICON、GROUP_ICON、VERSION和MANIFEST),以保证explorer.exe能正常显示图标、版本信息。还有就是UPX自己的Imports内容,导出表的库名和函数名(如果有的话)。

脱壳

看了之前写的,真是废话多,以下脱壳

32位不说了

64位

upx源码解释

image-20210602230445570

32位下是pushad popad,而64位改成了push一系列寄存器,大致为

image-20210602230533875

image-20210602230612277

不多说了好吧,多看看源码比什么都强

写个upx拖入ida

image-20210602230718090

image-20210602230740708

直接下断点,动调往下走

img

image-20210602230844032

前面说了UPX0是源码,UPX1是壳执行码,这提莫也太明显,太好脱了吧

参考资料

upx原理

upx防脱壳