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 PackLinuxElf32::invert_pt_dynamic at p_lx_elf.cpp:1688 #378

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 PackLinuxElf32::invert_pt_dynamic at p_lx_elf.cpp:1688.

ASAN reports:

==110358==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61600000fed9 at pc 0x00000045acc8 bp 0x7ffd88c9b020 sp 0x7ffd88c9b010
READ of size 4 at 0x61600000fed9 thread T0
    #0 0x45acc7 in PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16, LE32, LE32, LE32, LE32> > const*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:1688
    #1 0x463ba5 in PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16, LE32, LE32, LE32, LE32> > const*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:1583
    #2 0x463ba5 in PackLinuxElf32::PackLinuxElf32help1(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:305
    #3 0x464e96 in PackLinuxElf32Le::PackLinuxElf32Le(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.h:395
    #4 0x464e96 in PackLinuxElf32x86::PackLinuxElf32x86(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:4800
    #5 0x464e96 in PackBSDElf32x86::PackBSDElf32x86(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:4817
    #6 0x464e96 in PackFreeBSDElf32x86::PackFreeBSDElf32x86(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:4828
    #7 0x4f337a in PackMaster::visitAllPackers(Packer* (*)(Packer*, void*), InputFile*, options_t const*, void*) /home/test/Desktop/EVAULATION/upx/src/packmast.cpp:190
    #8 0x4f50f9 in PackMaster::getUnpacker(InputFile*) /home/test/Desktop/EVAULATION/upx/src/packmast.cpp:248
    #9 0x4f521f in PackMaster::unpack(OutputFile*) /home/test/Desktop/EVAULATION/upx/src/packmast.cpp:266
    #10 0x52a1e6 in do_one_file(char const*, char*) /home/test/Desktop/EVAULATION/upx/src/work.cpp:160
    #11 0x52a69e in do_files(int, int, char**) /home/test/Desktop/EVAULATION/upx/src/work.cpp:271
    #12 0x403ace in main /home/test/Desktop/EVAULATION/upx/src/main.cpp:1538
    #13 0x7ff33c8dc82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #14 0x404828 in _start (/home/test/Desktop/EVAULATION/upx/src/upx.out+0x404828)

0x61600000fed9 is located 1 bytes to the right of 600-byte region [0x61600000fc80,0x61600000fed8)
allocated by thread T0 here:
    #0 0x7ff33d4d0602 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:1688 PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16, LE32, LE32, LE32, LE32> > const*)
Shadow bytes around the buggy address:
  0x0c2c7fff9f80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c2c7fff9f90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2c7fff9fa0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2c7fff9fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2c7fff9fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c2c7fff9fd0: 00 00 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa
  0x0c2c7fff9fe0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c2c7fff9ff0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c2c7fffa000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c2c7fffa010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c2c7fffa020: 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
==110358==ABORTING

Then analysis the reasons for segv by debugging:

Program received signal SIGSEGV, Segmentation fault.
0x0000000000527258 in PackLinuxElf32::invert_pt_dynamic (this=this@entry=0xa00030, dynp=<optimized out>) at p_lx_elf.cpp:1688
1688                if (buckets[j]) {
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0000000000a009c1  →  0x00000000ffffffff
$rbx   : 0x0000000000a00030  →  0x00000000007267a0  →  <vtable+0> add BYTE PTR [rax], al
$rcx   : 0x0               
$rdx   : 0x0               
$rsp   : 0x00007fffffffcc10  →  0x0000000000a007c0  →  0xff0000010900457f
$rbp   : 0xffffff00        
$rsi   : 0x7d88            
$rdi   : 0x6               
$rip   : 0x0000000000527258  →  <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov r9d, DWORD PTR [rax+rsi*4+0x1c]
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x10              
$r11   : 0x0               
$r12   : 0xd               
$r13   : 0xffffffff        
$r14   : 0x200             
$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 ────
0x00007fffffffcc10│+0x0000: 0x0000000000a007c0  →  0xff0000010900457f     ← $rsp
0x00007fffffffcc18│+0x0008: 0x0000000000000000
0x00007fffffffcc20│+0x0010: 0x00007ffff7352260  →  <read+16> cmp rax, 0xfffffffffffff001
0x00007fffffffcc28│+0x0018: 0x0000000000000258
0x00007fffffffcc30│+0x0020: 0x00007ffff7362447  →  <lseek64+7> cmp rax, 0xfffffffffffff001
0x00007fffffffcc38│+0x0028: 0x00000000005255b2  →  <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov rax, QWORD PTR [rsp+0x10]
0x00007fffffffcc40│+0x0030: 0x0000000000000000
0x00007fffffffcc48│+0x0038: 0x0000000000000000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x527247 <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    rcx, QWORD PTR [rsp+0x8]
     0x52724c <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    rdx, QWORD PTR [rsp]
     0x527250 <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> lea    rsp, [rsp+0x98]
 →   0x527258 <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    r9d, DWORD PTR [rax+rsi*4+0x1c]
     0x52725d <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> test   r9d, r9d
     0x527260 <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> je     0x5272eb <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,  LE32,  LE32,  LE32,  LE32> > const*)+7515>
     0x527266 <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> xchg   ax, ax
     0x527268 <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> lea    rsp, [rsp-0x98]
     0x527270 <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    QWORD PTR [rsp], rdx
───────────────────────────────────────────────────────────────────────────────────────────────────────────────── source:p_lx_elf.cpp+1688 ────
   1683             unsigned     const *const hasharr = &buckets[n_bucket]; (void)hasharr;
   1684           //unsigned     const *const gashend = &hasharr[n_bucket];  // minimum, except:
   1685             // Rust and Android trim unused zeroes from high end of hasharr[]
   1686             unsigned bmax = 0;
   1687             for (unsigned j= 0; j < n_bucket; ++j) {
 → 1688                 if (buckets[j]) {
   1689                     if (buckets[j] < symbias) {
   1690                         char msg[50]; snprintf(msg, sizeof(msg),
   1691                                 "bad DT_GNU_HASH bucket[%d] < symbias{%#x}\n",
   1692                                 buckets[j], symbias);
   1693                         throwCantPack(msg);
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "upx.out", stopped, reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x527258 → PackLinuxElf32::invert_pt_dynamic(this=0xa00030, dynp=<optimized out>)
[#1] 0x529294 → PackLinuxElf32::invert_pt_dynamic(dynp=<optimized out>, this=0xa00030)
[#2] 0x529294 → PackLinuxElf32::PackLinuxElf32help1(this=0xa00030, f=0x7fffffffce20)
[#3] 0x52c65e → PackLinuxElf32Le::PackLinuxElf32Le(f=0x7fffffffce20, this=0xa00030)
[#4] 0x52c65e → PackLinuxElf32x86::PackLinuxElf32x86(f=0x7fffffffce20, this=0xa00030)
[#5] 0x52c65e → PackBSDElf32x86::PackBSDElf32x86(f=0x7fffffffce20, this=0xa00030)
[#6] 0x52c65e → PackFreeBSDElf32x86::PackFreeBSDElf32x86(this=0xa00030, f=0x7fffffffce20)
[#7] 0x60448c → PackMaster::visitAllPackers(func=0x602c30 <try_unpack(Packer*, void*)>, f=0x7fffffffce20, o=0x7fffffffcfd8, user=0x7fffffffce20)
[#8] 0x6072ca → PackMaster::getUnpacker(f=<optimized out>)
[#9] 0x6072ca → PackMaster::unpack(this=0x7fffffffcfc0, fo=0x7fffffffcef0)

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

mov    r9d, DWORD PTR [rax+rsi*4+0x1c]

The value of register rax and rsi are:

$rax   : 0x0000000000a009c1
$rsi   : 0x7d88

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

gef➤  x /10xg 0xa1fffd
0xa1fffd:    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.

unsigned const *const bitmask = (unsigned 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]) {

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 "problem not present" in official release upx-4.0.2 of Jan.30, 2023:

$ valgrind $UPX402 -df $PoC -o /dev/null
==26280== Memcheck, a memory error detector
==26280== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==26280== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==26280== Command: /home2/upx/upx-4.0.2-amd64_linux/upx -df hbo_PackLinuxElf32invert_pt_dynamic -o /dev/null
==26280== 
                       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_PackLinuxElf32invert_pt_dynamic: NotPackedException: not packed by UPX
$ 

$ grep UPX *
   ## empty output: independent verification of "not packed by UPX"
$  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