1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
#!/usr/bin/env perl
use strict;
use warnings;
use 5.020;
use experimental 'signatures';
no warnings 'experimental::signatures';
# see http://www.lightofdawn.org/wiki/wiki.cgi/NewAppsOnOldGlibc for details
# as of currently, only `log2` is being used from GLIBC_2.29, and a
# corresponding version from GLIBC_2.2.5 does exist, so we can just swap it
# out.
use constant VER_FLG_WEAK => 0x02;
my $bin = $ARGV[0];
system("readelf -s $bin | grep -q GLIBC_2.29");
if ($?) {
die "no symbols from GLIBC_2.29 found, skipping\n";
}
my %readelf_v = parse_readelf_v($bin);
my %readelf_s = parse_readelf_s($bin);
if ($readelf_s{'log2@GLIBC_2.29'}{ver} != $readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.29'}{ver}) {
die "log2 doesn't appear to use the symbol version from GLIBC_2.29\n";
}
my $bin_contents = do {
local $/;
open my $bin_fh, '<', $bin or die "couldn't open $bin: $!";
<$bin_fh>
};
my $gnu_version_r_offset = $readelf_v{'.gnu.version_r'}{offset};
my $glibc_229_offset = $gnu_version_r_offset + $readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.29'}{offset};
my $glibc_225_offset = $gnu_version_r_offset + $readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.2.5'}{offset};
# we could just make the GLIBC_2.29 symbol weak (as described in the above
# page), but that causes a warning to be printed every time you run the binary,
# which is obnoxious
# my $prev_glibc_229_flags = substr $bin_contents, $glibc_229_offset + 0x04, 2, pack('s', VER_FLG_WEAK);
# if (unpack('s', $prev_glibc_229_flags) != 0) {
# die "GLIBC_2.39 did not have flags set to 0\n";
# }
# instead, we overwrite vna_hash, vna_flags, vna_other, vna_name with the 2.3.5
# versions (but leave vna_next the same) - this causes there to be two entries
# describing GLIBC_2.2.5, but this appears to be harmless
substr($bin_contents, $glibc_229_offset + 0x00, 12, substr($bin_contents, $glibc_225_offset + 0x00, 12));
my $gnu_version_offset = $readelf_v{'.gnu.version'}{offset};
my $log2_offset = $gnu_version_offset + 2 * $readelf_s{'log2@GLIBC_2.29'}{num};
my $prev_log2_version = substr $bin_contents, $log2_offset, 2, pack('s', $readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.2.5'}{ver});
if (unpack('s', $prev_log2_version) != $readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.29'}{ver}) {
die "log2 doesn't appear to use the symbol version from GLIBC_2.29\n";
}
open my $fh, '>', "$bin.new" or die "couldn't open $bin.new: $!";
print $fh $bin_contents;
chmod 0755, "$bin.new";
my $remaining = `readelf -s $bin.new | grep GLIBC_2.29`;
if (!$?) {
die "additional symbols from GLIBC_2.29 found:\n$remaining\n";
}
sub parse_readelf_s($bin) {
my $readelf = `readelf -s $bin`;
my %ret;
for my $line (split "\n", $readelf) {
if ($line =~ /^ *[0-9]+:/) {
my ($num, $value, $size, $type, $bind, $vis, $ndx, $name, $ver) = split ' ', $line;
if (defined $ver) {
($ver) = $ver =~ /\((.*)\)/;
}
if (defined $num) {
($num) = $num =~ /(.*):/;
}
$ret{$name} = {
num => $num,
ver => $ver,
}
}
}
%ret
}
sub parse_readelf_v($bin) {
my $readelf = `readelf -V $bin`;
my $section;
my $file;
my %ret;
for my $line (split "\n", $readelf) {
if ($line =~ /^Version (?:symbols|needs) section '([^']+)'/) {
$section = $1;
}
if ($line =~ /^ Addr: (?:[^ ]+) Offset: ([^ ]+)/) {
$ret{$section}{offset} = hex($1);
}
if ($line =~ /^ [^:]+: Version: [^ ]+ File: ([^ ]+)/) {
$file = $1;
}
if ($line =~ /^ ([^:]+): Name: ([^ ]+) Flags: (?:[^ ]+) Version: (.*)/) {
$ret{$section}{$file}{$2} = {
offset => hex($1),
ver => $3,
};
}
}
%ret
}
|