pyarmor icon indicating copy to clipboard operation
pyarmor copied to clipboard

Old linux server(only GLIBC_2.12)

Open ylluX opened this issue 1 year ago • 2 comments

My linux server system version is Red Hat 6.8, which only supports GLIBC_2.12, but pyarmor/cli/core/pyarmor_runtime.so require GLIBC_2.14, and pyarmor/cli/core/pyarmor_runtime.so requires GLIBC_2.15.

I'm sure the server can't upgrade the system and GLIBC. Can you regenerate the dynamic library that only depends on GLIBC_2.12?

My python version is 3.8,pyarmor version is Pyarmor 8.3.7 (group).

When I execute the command(pyarmor gen --platform centos6.x86_64 ...), I get an error:

ERROR: Could not find a version that satisfies the requirement pyarmor.cli.core.centos6==4.3.4 (from versions: none)
ERROR: No matching distribution found for pyarmor.cli.core.centos6==4.3.4

ylluX avatar Jun 29 '24 12:06 ylluX

There is no centos platform, please refer to https://pyarmor.readthedocs.io/en/v7.7/questions.html#lib64-libc-so-6-version-glibc-2-14-not-found

To patch pyarmor/cli/core/pytransform3.so and pyarmor/cli/core/pyarmor_runtime.so

Here is one example script

# ======================================================================
# Routine: replace_symbol_version
#   Provided so that scripts which are invoked via postinstall
#   can prevent escape codes from showing up in /var/log/setup.log
# ======================================================================
replace_symbol_version()
{
    local dest=${1}
    local srcoffset=${2}
    local destoffset=${3}

    echo "Patch ${1}, source offset ${2}, target offset ${3}"

    local offset=$($READELF -V $dest | grep "Offset" | $AWK 'FNR == 3 { print $4; }')
    if [[ -z "$offset" ]] ; then
        echo "No found offset of section '.gnu.version_r'"
        exit 1
    fi
    echo "Offset of section '.gnu.version_r': $offset"

    let -i addr1=$(( $offset + $srcoffset ))
    let -i addr2=$(( $offset + $destoffset ))
    echo "Copy 4 bytes from $(printf '%x' $addr1) to $(printf '%x' $addr2)"
    xxd -s $addr1 -l 4 $dest | sed "s/$(printf '%x' $addr1)/$(printf '%x' $addr2)/" | xxd -r - $dest

    let -i addr1=$(( $offset + $srcoffset + 0x8 ))
    let -i addr2=$(( $offset + $destoffset + 0x8 ))
    echo "Copy 4 bytes from $(printf '%x' $addr1) to $(printf '%x' $addr2)"
    xxd -s $addr1 -l 4 $dest | sed "s/$(printf '%x' $addr1)/$(printf '%x' $addr2)/" | xxd -r - $dest

} # === End of replace_symbol_version() === #
readonly -f replace_symbol_version

AWK=awk
READELF=readelf

[[ -f pytransform3.so.bak ]] && cp pytransform3.so pytransform3.so.bak
[[ -f pyarmor_runtime.so.bak ]] && cp pyarmor_runtime.so pyarmor_runtime.so.bak

replace_symbol_version pytransform3.so 0x10 0x50
replace_symbol_version pyarmor_runtime.so 0x10 0x50
replace_symbol_version pyarmor_runtime.so 0x10 0x60

Please run this script in the path of package pyarmor.cli.core

jondy avatar Jun 30 '24 07:06 jondy

@jondy Thank you for your prompt reply.

After applying the patch, I got the following error:

ImportError: /xxx/pyarmor_runtime_006111/pyarmor_runtime.so: symbol __fdelt_chk, version GLIBC_2.2.5 not defined in file libc.so.6 with link time reference

I found that __fdelt_chk@GLIBC_2.15 and memcpy@GLIBC_2.14 symbols were called in pyarmor_runtime.so. It seems feasible to replace memcpy@GLIBC_2.14 with memcpy@GLIBC_2.2.5, but __fdelt_chk@GLIBC_2.2.5 cannot replace __fdelt_chk@GLIBC_2.15 because there is no __fdelt_chk in GLIBC_2.2.5.

nm /lib64/libc.so.6 |grep fdelt    # Nothing returned

I need your continued help. I'm just one step away from success. Thanks!

Or, when compiling, can you remove the dependency on __fdelt_chk? Maybe you can refer to "Use older glibc on Linux".

ylluX avatar Jun 30 '24 09:06 ylluX

Because __fdelt_chk is only used by Pyarmor Group License in docker container, so it should work to replace string __fdelt_chk with __fgets_chk in the pyarmor_runtime.so by any binary editor tool

I prefer to patch extension file if patch script is simple.

jondy avatar Jul 01 '24 09:07 jondy

@jondy I don't know these hacking techniques, can you provide a patch file or give an example? Thanks

ylluX avatar Jul 01 '24 11:07 ylluX

I wrote a simple patch:

# patch.sh
# patch pytransform3.so
# GLIBC_2.14 -> GLIBC_2.2.5
xxd -s 0x22b0 -l 4 pytransform3.so | sed "s/22b0/22f0/" | xxd -r - pytransform3.so
xxd -s 0x22b8 -l 4 pytransform3.so | sed "s/22b8/22f8/" | xxd -r - pytransform3.so

# patch pyarmor_runtime.so
# GLIBC_2.14 -> GLIBC_2.2.5
xxd -s 0x3318 -l 4 pyarmor_runtime.so | sed "s/3318/3358/" | xxd -r - pyarmor_runtime.so
xxd -s 0x3320 -l 4 pyarmor_runtime.so | sed "s/3320/3360/" | xxd -r - pyarmor_runtime.so
# GLIBC_2.15 -> GLIBC_2.4
xxd -s 0x3378 -l 4 pyarmor_runtime.so | sed "s/3378/3368/" | xxd -r - pyarmor_runtime.so
xxd -s 0x3380 -l 4 pyarmor_runtime.so | sed "s/3380/3370/" | xxd -r - pyarmor_runtime.so
# __fdelt_chk@GLIBC_2.15 -> _fgets_chk@GLIBC_2.4
echo 00002f50:5f5f66676574735f63686b | xxd -r - pyarmor_runtime.so

The offsets (0x22b0, 0x22b8) in pytransform3.so comes from:

$ readelf -V pytransform3.so
Version needs section '.gnu.version_r' contains 3 entries:
 Addr: 0x00000000000022a0  Offset: 0x0022a0  Link: 4 (.dynstr)
  000000: Version: 1  File: libdl.so.2  Cnt: 1
  0x0010:   Name: GLIBC_2.2.5  Flags: none  Version: 10
  0x0020: Version: 1  File: libpthread.so.0  Cnt: 1
  0x0030:   Name: GLIBC_2.2.5  Flags: none  Version: 6
  0x0040: Version: 1  File: libc.so.6  Cnt: 6
  0x0050:   Name: GLIBC_2.14  Flags: none  Version: 9
  0x0060:   Name: GLIBC_2.4  Flags: none  Version: 8
0x22b0 = 0x0022a0 + 0x10
0x22b8 = 0x0022a0 + 0x18

The offsets (0x3318, 0x3320, ...) in pyarmor_runtime.so comes from:

$ readelf -V pyarmor_runtime.so
Version needs section '.gnu.version_r' contains 3 entries:
 Addr: 0x0000000000003308  Offset: 0x003308  Link: 4 (.dynstr)
  000000: Version: 1  File: libdl.so.2  Cnt: 1
  0x0010:   Name: GLIBC_2.2.5  Flags: none  Version: 11
  0x0020: Version: 1  File: libpthread.so.0  Cnt: 1
  0x0030:   Name: GLIBC_2.2.5  Flags: none  Version: 6
  0x0040: Version: 1  File: libc.so.6  Cnt: 7
  0x0050:   Name: GLIBC_2.14  Flags: none  Version: 10
  0x0060:   Name: GLIBC_2.15  Flags: none  Version: 9
  0x0070:   Name: GLIBC_2.4  Flags: none  Version: 8

the offsets (__fdelt_chk) in pyarmor_runtime.so comes from:

$xxd ../../core.bak/pyarmor_runtime.so |grep __fdelt_chk
00002f50: 5f5f 6664 656c 745f 6368 6b00 7365 6c65  __fdelt_chk.sele

ylluX avatar Jul 01 '24 13:07 ylluX

Nice.

It also could be patched by 4 lines python code

python -c "
with open('pyarmor_runtime.so', 'rb') as f:
    buf = f.read()

with open('pyarmor_runtime.so', 'wb') as f:
    f.write(buf.replace(b'__fdelt_chk', b'__fgets_chk'))
"

And __fdelt_chk is used only by checking expired date of the obfuscated script by NTP protocol, it's not used by group license.

I also upload a script to patch both of extensions

# This script is used to patch extensions `pyarmor_runtime.so` and
# `pytransform3.so` to make them works in CentOS 6 (GLIBC < 2.14)
#
# Usage:
#
#   cd /path/to/pyarmor/cli/core
#   bash patch-centos6.sh
#

set -e

AWK=awk
READELF=readelf
PYTHON=python3

# ======================================================================
# Routine: replace_symbol_version filename offset srcoffset destoffset
# ======================================================================
replace_symbol_version()
{
    local dest=${1}
    local offset=${2}
    local srcoffset=${3}
    local destoffset=${4}

    echo "Target symbol offset: ${4}"

    let -i addr1=$(( $offset + $srcoffset ))
    let -i addr2=$(( $offset + $destoffset ))
    echo "Copy 4 bytes from $(printf '%x' $addr1) to $(printf '%x' $addr2)"
    xxd -s $addr1 -l 4 $dest | sed "s/$(printf '%x' $addr1)/$(printf '%x' $addr2)/" | xxd -r - $dest

    let -i addr1=$(( $offset + $srcoffset + 0x8 ))
    let -i addr2=$(( $offset + $destoffset + 0x8 ))
    echo "Copy 4 bytes from $(printf '%x' $addr1) to $(printf '%x' $addr2)"
    xxd -s $addr1 -l 4 $dest | sed "s/$(printf '%x' $addr1)/$(printf '%x' $addr2)/" | xxd -r - $dest

} # === End of replace_symbol_version() === #
readonly -f replace_symbol_version

# ======================================================================
# Routine: patch_section_version filename
# ======================================================================
patch_section_version()
{
    local dest=${1}
    echo ""
    echo "Patching ${1} ..."

    local offset=$($READELF -V $dest | grep "Offset" | $AWK 'FNR == 3 { print $4; }')
    if [[ -z "$offset" ]] ; then
        echo "No found offset of section '.gnu.version_r'"
        exit 1
    fi
    echo "Section '.gnu.version_r' offset: $offset"

    local src=$($READELF -V $dest | grep "Name: GLIBC_2.2.5" | $AWK 'FNR == 1 { print $1 }')
    if [[ -z "$src" ]] ; then
        echo "No found refer symbol (GLIBC_2.2.5) offset"
        exit 1
    fi
    src=${src%:}
    echo "Refer symbol offset (GLIBC_2.2.5): $src"

    local target=$($READELF -V $dest | grep "Name: GLIBC_2.14" | $AWK 'FNR == 1 { print $1 }')
    replace_symbol_version $dest $offset $src ${target%:}

    target=$($READELF -V $dest | grep "Name: GLIBC_2.15" | $AWK 'FNR == 1 { print $1 }')
    [[ -n "$target" ]] && replace_symbol_version $dest $offset $src ${target%:}

    echo "Patch ${1} end"
    echo ""
}

for x in pytransform3.so pyarmor_runtime.so ; do

    if [[ -f $x.bak ]] ; then
        echo "Restore $x from backup file $x.bak"
        cp $x.bak $x
    else
        echo "Create backup file $x.bak"
        cp $x $x.bak
    fi

    # pytransform3.so: 0x10 0x50
    # pyarmor_runtime.so: 0x10 0x50, 0x10 0x60
    patch_section_version $x

done

echo "Rename symbol '__fdelt_chk' to '__fgets_chk' in 'pyarmor_runtime.so'"
$PYTHON -c "
with open('pyarmor_runtime.so', 'rb') as f:
    buf = f.read()

with open('pyarmor_runtime.so', 'wb') as f:
    f.write(buf.replace(b'__fdelt_chk', b'__fgets_chk'))
"

jondy avatar Jul 02 '24 07:07 jondy

@jondy Thanks! It has perfectly solved my problem. Thank you again, I learned a lot from you.

ylluX avatar Jul 02 '24 12:07 ylluX