Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bug] heap buffer overflow in PackLinuxElf64::invert_pt_dynamic at p_lx_elf.cpp:5239 #379

Closed
14isnot40 opened this issue May 23, 2020 · 2 comments

Comments

@14isnot40
Copy link

What's the problem (or question)?

A heap-based buffer overflow was discovered in upx, during the variable 'bucket' points to an inaccessible address. The issue is being triggered in the function PackLinuxElf64::invert_pt_dynamic at p_lx_elf.cpp:5239.

ASAN reports:

==110294==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x63000000f760 at pc 0x000000466f38 bp 0x7ffcebb0b6a0 sp 0x7ffcebb0b690
READ of size 4 at 0x63000000f760 thread T0
    #0 0x466f37 in PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16, LE32, LE64, LE64, LE64> > const*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:5239
    #1 0x46f660 in PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16, LE32, LE64, LE64, LE64> > const*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:5127
    #2 0x46f660 in PackLinuxElf64::PackLinuxElf64help1(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:795
    #3 0x470479 in PackLinuxElf64Le::PackLinuxElf64Le(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.h:407
    #4 0x470479 in PackLinuxElf64amd::PackLinuxElf64amd(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:1008
    #5 0x4f34b2 in PackMaster::visitAllPackers(Packer* (*)(Packer*, void*), InputFile*, options_t const*, void*) /home/test/Desktop/EVAULATION/upx/src/packmast.cpp:194
    #6 0x4f50f9 in PackMaster::getUnpacker(InputFile*) /home/test/Desktop/EVAULATION/upx/src/packmast.cpp:248
    #7 0x4f521f in PackMaster::unpack(OutputFile*) /home/test/Desktop/EVAULATION/upx/src/packmast.cpp:266
    #8 0x52a1e6 in do_one_file(char const*, char*) /home/test/Desktop/EVAULATION/upx/src/work.cpp:160
    #9 0x52a69e in do_files(int, int, char**) /home/test/Desktop/EVAULATION/upx/src/work.cpp:271
    #10 0x403ace in main /home/test/Desktop/EVAULATION/upx/src/main.cpp:1538
    #11 0x7f8a7e1c882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #12 0x404828 in _start (/home/test/Desktop/EVAULATION/upx/src/upx.out+0x404828)

0x63000000f760 is located 0 bytes to the right of 62304-byte region [0x630000000400,0x63000000f760)
allocated by thread T0 here:
    #0 0x7f8a7edbc602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
    #1 0x42732a in MemBuffer::alloc(unsigned long long) /home/test/Desktop/EVAULATION/upx/src/mem.cpp:194

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:5239 PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16, LE32, LE64, LE64, LE64> > const*)
Shadow bytes around the buggy address:
  0x0c607fff9e90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c607fff9ea0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c607fff9eb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c607fff9ec0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c607fff9ed0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c607fff9ee0: 00 00 00 00 00 00 00 00 00 00 00 00[fa]fa fa fa
  0x0c607fff9ef0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c607fff9f00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c607fff9f10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c607fff9f20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c607fff9f30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
==110294==ABORTING

Then analysis the reasons for segv by debugging:

Program received signal SIGSEGV, Segmentation fault.
0x000000000053b1a8 in PackLinuxElf64::invert_pt_dynamic (this=this@entry=0xa00030, dynp=<optimized out>) at p_lx_elf.cpp:5239
5239                if (buckets[j]) {
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0x0000000000a00030  →  0x0000000000726a30  →  <vtable+0> add BYTE PTR [rax], al
$rcx   : 0x180             
$rdx   : 0xc00             
$rsp   : 0x00007fffffffcbf0  →  0x000000000065fa86  →  <mem_size(unsigned+0> mov rax, QWORD PTR [rsp+0x10]
$rbp   : 0xffffffff        
$rsi   : 0x7aa9            
$rdi   : 0x0000000000a01548  →  0x480000104fe81024
$rip   : 0x000000000053b1a8  →  <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov eax, DWORD PTR [rdi+rsi*4+0x14]
$r8    : 0x12              
$r9    : 0xffe0            
$r10   : 0xc10             
$r11   : 0x7               
$r12   : 0x8               
$r13   : 0x180             
$r14   : 0x0000000000a0f4d8  →  0x000000000000000d
$r15   : 0x0               
$eflags: [carry PARITY adjust ZERO sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffcbf0│+0x0000: 0x000000000065fa86  →  <mem_size(unsigned+0> mov rax, QWORD PTR [rsp+0x10]     ← $rsp
0x00007fffffffcbf8│+0x0008: 0x0000000000400298  →   add eax, DWORD PTR [rax]
0x00007fffffffcc00│+0x0010: 0x000000000000ffe0
0x00007fffffffcc08│+0x0018: 0x000000000000f360
0x00007fffffffcc10│+0x0020: 0x00007ffff7352260  →  <read+16> cmp rax, 0xfffffffffffff001
0x00007fffffffcc18│+0x0028: 0x000000000000f360
0x00007fffffffcc20│+0x0030: 0x00007ffff7362447  →  <lseek64+7> cmp rax, 0xfffffffffffff001
0x00007fffffffcc28│+0x0038: 0x0000000000539692  →  <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov rax, QWORD PTR [rsp+0x10]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x53b197 <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    rcx, QWORD PTR [rsp+0x8]
     0x53b19c <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    rdx, QWORD PTR [rsp]
     0x53b1a0 <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> lea    rsp, [rsp+0x98]
 →   0x53b1a8 <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    eax, DWORD PTR [rdi+rsi*4+0x14]
     0x53b1ac <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> test   eax, eax
     0x53b1ae <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> je     0x53b235 <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,  LE32,  LE64,  LE64,  LE64> > const*)+7109>
     0x53b1b4 <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> lea    rsp, [rsp-0x98]
     0x53b1bc <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    QWORD PTR [rsp], rdx
     0x53b1c0 <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    QWORD PTR [rsp+0x8], rcx
───────────────────────────────────────────────────────────────────────────────────────────────────────────────── source:p_lx_elf.cpp+5239 ────
   5234             unsigned     const *const hasharr = &buckets[n_bucket]; (void)hasharr;
   5235           //unsigned     const *const gashend = &hasharr[n_bucket];  // minimum, except:
   5236             // Rust and Android trim unused zeroes from high end of hasharr[]
   5237             unsigned bmax = 0;
   5238             for (unsigned j= 0; j < n_bucket; ++j) {
 → 5239                 if (buckets[j]) {
   5240                     if (buckets[j] < symbias) {
   5241                         char msg[50]; snprintf(msg, sizeof(msg),
   5242                                 "bad DT_GNU_HASH bucket[%d] < symbias{%#x}\n",
   5243                                 buckets[j], symbias);
   5244                         throwCantPack(msg);
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "upx.out", stopped, reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x53b1a8 → PackLinuxElf64::invert_pt_dynamic(this=0xa00030, dynp=<optimized out>)
[#1] 0x53d654 → PackLinuxElf64::invert_pt_dynamic(dynp=<optimized out>, this=0xa00030)
[#2] 0x53d654 → PackLinuxElf64::PackLinuxElf64help1(this=0xa00030, f=0x7fffffffce10)
[#3] 0x53f186 → PackLinuxElf64Le::PackLinuxElf64Le(f=0x7fffffffce10, this=0xa00030)
[#4] 0x53f186 → PackLinuxElf64amd::PackLinuxElf64amd(this=0xa00030, f=0x7fffffffce10)
[#5] 0x6048ac → PackMaster::visitAllPackers(func=0x602c30 <try_unpack(Packer*, void*)>, f=0x7fffffffce10, o=0x7fffffffcfc8, user=0x7fffffffce10)
[#6] 0x6072ca → PackMaster::getUnpacker(f=<optimized out>)
[#7] 0x6072ca → PackMaster::unpack(this=0x7fffffffcfb0, fo=0x7fffffffcee0)
[#8] 0x670dc5 → do_one_file(iname=0x7fffffffdf12 "id:000036,sig:11,src:000541+000827,op:MOpt-splice,rep:2", oname=0x7fffffffd550 "/dev/null")
[#9] 0x67157c → do_files(i=0x4, argc=0x5, argv=0x7fffffffdac8)

The instruction to crash is, corresponds to the bucket [j] in the source code

mov    eax, DWORD PTR [rdi+rsi*4+0x14]

The register rdi and rsi are:

$rsi   : 0x7aa9            
$rdi   : 0x0000000000a01548

The DWORD PTR pointer to 0xa20000, where is a invalid address.

gef➤  x /10xg 0xa20000
0xa20000:    Cannot access memory at address 0xa20000

What should have happened?

Decompress a crafted/suspicious file.

Do you have an idea for a solution?

A boundary check is needed for loop variable 'j' because the size allocated for variable 'buckets' is limited.

upx_uint64_t const *const bitmask = (upx_uint64_t const *)(void const *)&gashtab[4];
unsigned     const *const buckets = (unsigned const *)&bitmask[n_bitmask];
unsigned     const *const hasharr = &buckets[n_bucket]; (void)hasharr;
//unsigned     const *const gashend = &hasharr[n_bucket];  // minimum, except:
// Rust and Android trim unused zeroes from high end of hasharr[]
unsigned bmax = 0;
for (unsigned j= 0; j < n_bucket; ++j) {
    if (buckets[j]) {
        if (buckets[j] < symbias) {
            char msg[50]; snprintf(msg, sizeof(msg),
                                   "bad DT_GNU_HASH bucket[%d] < symbias{%#x}\n",
                                   buckets[j], symbias);
            throwCantPack(msg);
        }
        if (bmax < buckets[j]) {
            bmax = buckets[j];
        }
    }
}

How can we reproduce the issue?

  1. compile upx with address-sanitize
  2. execute cmd
upx.out -df $PoC -o /dev/null

Poc can be found here.

Please tell us details about your environment.

  • UPX version used (upx --version):
upx 4.0.0-git-c6b9e3c62d15 (latest-devel-branch)
UCL data compression library 1.03
zlib data compression library 1.2.8
LZMA SDK version 4.43
  • Host Operating System and version:
    Ubuntu 16.04 64-bit
  • Host CPU architecture:
    Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz with 8GB
  • Target Operating System and version:
    same as Host
  • Target CPU architecture:
    same as Host
jreiser added a commit that referenced this issue May 23, 2020
@jreiser
Copy link
Collaborator

jreiser commented May 28, 2020

Fixed on devel branch by above commit.

@jreiser jreiser closed this as completed May 28, 2020
markus-oberhumer pushed a commit that referenced this issue Aug 17, 2022
@jreiser
Copy link
Collaborator

jreiser commented Apr 17, 2023

Verified as "problem is not present" in official release upx-4.0.2 of Jan.30, 2023:

$ $UPX402 -df hbo_PackLinuxElf64__invert_pt_dynamic -o /dev/null
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2023
UPX 4.0.2       Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 30th 2023

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
upx: hbo_PackLinuxElf64__invert_pt_dynamic: CantUnpackException: bad DT_{0x6} = 0x400010 (beyond EOF)

Unpacked 0 files.

Also, the PoC file does not contain the string "UPX", so is independently verified as "not packed by UPX":

$ grep UPX hbo_PackLinuxElf64__invert_pt_dynamic
   ## empty output
$ echo $?
1   ## no matches
$ 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants