Tampaknya tidak ada metode runtime langsung untuk menambal deteksi fitur. Deteksi ini terjadi lebih awal di linker dinamis (ld.so).
Biner menambal linker tampaknya merupakan metode termudah saat ini. @osgx menjelaskan satu metode di mana lompatan ditimpa. Pendekatan lain adalah hanya memalsukan hasil cpuid. Biasanya cpuid(eax=0)
mengembalikan fungsi tertinggi yang didukung di eax
sedangkan ID pabrikan dikembalikan dalam register ebx, ecx dan edx. Kami memiliki cuplikan ini di glibc 2.25 sysdeps/x86/cpu-features.c
:
__cpuid (0, cpu_features->max_cpuid, ebx, ecx, edx);
/* This spells out "GenuineIntel". */
if (ebx == 0x756e6547 && ecx == 0x6c65746e && edx == 0x49656e69)
{
/* feature detection for various Intel CPUs */
}
/* another case for AMD */
else
{
kind = arch_kind_other;
get_common_indeces (cpu_features, NULL, NULL, NULL, NULL);
}
__cpuid
baris menerjemahkan instruksi ini di /lib/ld-linux-x86-64.so.2
(/lib/ld-2.25.so
):
172a8: 31 c0 xor eax,eax
172aa: c7 44 24 38 00 00 00 mov DWORD PTR [rsp+0x38],0x0
172b1: 00
172b2: c7 44 24 3c 00 00 00 mov DWORD PTR [rsp+0x3c],0x0
172b9: 00
172ba: 0f a2 cpuid
Jadi daripada menambal cabang, kita juga bisa mengubah cpuid
menjadi nop
instruksi yang akan menghasilkan pemanggilan else
terakhir cabang (karena register tidak akan berisi "GenuineIntel"). Sejak awal eax=0
, cpu_features->max_cpuid
juga akan menjadi 0 dan if (cpu_features->max_cpuid >= 7)
juga akan dilewati.
Penambalan biner cpuid(eax=0)
oleh nop
ini dapat dilakukan dengan utilitas ini (berfungsi untuk x86 dan x86-64):
#!/usr/bin/env python
import re
import sys
infile, outfile = sys.argv[1:]
d = open(infile, 'rb').read()
# Match CPUID(eax=0), "xor eax,eax" followed closely by "cpuid"
o = re.sub(b'(\x31\xc0.{0,32}?)\x0f\xa2', b'\\1\x66\x90', d)
assert d != o
open(outfile, 'wb').write(o)
Varian Perl yang setara, -0777
memastikan bahwa file dibaca sekaligus alih-alih memisahkan catatan pada umpan baris:
perl -0777 -pe 's/\x31\xc0.{0,32}?\K\x0f\xa2/\x66\x90/' < /lib/ld-linux-x86-64.so.2 > ld-linux-x86-64-patched.so.2
# Verify result, should display "Success"
cmp -s /lib/ld-linux-x86-64.so.2 ld-linux-x86-64-patched.so.2 && echo 'Not patched' || echo Success
Itu bagian yang mudah. Sekarang, saya tidak ingin mengganti linker dinamis di seluruh sistem, tetapi hanya menjalankan satu program tertentu dengan linker ini. Tentu, itu bisa dilakukan dengan ./ld-linux-x86-64-patched.so.2 ./a
, tetapi pemanggilan gdb yang naif gagal menyetel breakpoint:
$ gdb -q -ex "set exec-wrapper ./ld-linux-x86-64-patched.so.2" -ex start ./a
Reading symbols from ./a...done.
Temporary breakpoint 1 at 0x400502: file a.c, line 5.
Starting program: /tmp/a
During startup program exited normally.
(gdb) quit
$ gdb -q -ex start --args ./ld-linux-x86-64-patched.so.2 ./a
Reading symbols from ./ld-linux-x86-64-patched.so.2...(no debugging symbols found)...done.
Function "main" not defined.
Temporary breakpoint 1 (main) pending.
Starting program: /tmp/ld-linux-x86-64-patched.so.2 ./a
[Inferior 1 (process 27418) exited normally]
(gdb) quit
Solusi manual dijelaskan dalam Bagaimana cara men-debug program dengan juru bahasa elf khusus? Ini berfungsi, tetapi sayangnya tindakan manual menggunakan add-symbol-file
. Seharusnya mungkin untuk mengotomatiskannya sedikit menggunakan GDB Catchpoints.
Pendekatan alternatif yang tidak menghubungkan biner adalah LD_PRELOAD
membuat pustaka yang mendefinisikan rutin khusus untuk memcpy
, memove
, dll. Ini kemudian akan didahulukan dari rutinitas glibc. Daftar lengkap fungsi tersedia di sysdeps/x86_64/multiarch/ifunc-impl-list.c
. HEAD saat ini memiliki lebih banyak simbol dibandingkan dengan rilis glibc 2.25, secara total (grep -Po 'IFUNC_IMPL \(i, name, \K[^,]+' sysdeps/x86_64/multiarch/ifunc-impl-list.c
):
memchr,memcmp,__memmove_chk,memmove,memrchr,__memset_chk,memset,rawmemchr,strlen,strnlen,stpncpy,stpcpy,strcasecmp,strcasecmp_l,strcat,strchr,strchrnul,strchr,strcmp,strcpy,strcspn,strncasecmp,strncasecmp_l,strcat,strncpy, strpbrk,strspn,strstr,wcschr,wcsrchr,wcscpy,wcslen,wcsnlen,wmemchr,wmemcmp,wmemset,__memcpy_chk,memcpy,__mempcpy_chk,mempcpy,strncmp,__wmemset_chk,
Sepertinya ada solusi yang bagus untuk penerapan ini di glibc versi terbaru:fitur "merdu" yang memandu pemilihan fungsi string yang dioptimalkan. Anda dapat menemukan gambaran umum fitur ini di sini dan kode yang relevan di dalam glibc di ifunc-impl-list.c.
Begini cara saya mengetahuinya. Pertama, saya mengambil alamat yang dikeluhkan oleh gdb:
Process record does not support instruction 0xc5 at address 0x7ffff75c65d4.
Saya kemudian mencarinya di tabel perpustakaan bersama:
(gdb) info shared
From To Syms Read Shared Object Library
0x00007ffff7fd3090 0x00007ffff7ff3130 Yes /lib64/ld-linux-x86-64.so.2
0x00007ffff76366b0 0x00007ffff766b52e Yes /usr/lib/x86_64-linux-gnu/libubsan.so.1
0x00007ffff746a320 0x00007ffff75d9cab Yes /lib/x86_64-linux-gnu/libc.so.6
...
Anda dapat melihat bahwa alamat ini ada di dalam glibc. Tapi fungsi apa, khususnya?
(gdb) disassemble 0x7ffff75c65d4
Dump of assembler code for function __strcmp_avx2:
0x00007ffff75c65d0 <+0>: mov %edi,%eax
0x00007ffff75c65d2 <+2>: xor %edx,%edx
=> 0x00007ffff75c65d4 <+4>: vpxor %ymm7,%ymm7,%ymm7
Saya dapat melihat ifunc-impl-list.c untuk menemukan kode yang mengontrol pemilihan versi avx2:
IFUNC_IMPL (i, name, strcmp,
IFUNC_IMPL_ADD (array, i, strcmp,
HAS_ARCH_FEATURE (AVX2_Usable),
__strcmp_avx2)
IFUNC_IMPL_ADD (array, i, strcmp, HAS_CPU_FEATURE (SSE4_2),
__strcmp_sse42)
IFUNC_IMPL_ADD (array, i, strcmp, HAS_CPU_FEATURE (SSSE3),
__strcmp_ssse3)
IFUNC_IMPL_ADD (array, i, strcmp, 1, __strcmp_sse2_unaligned)
IFUNC_IMPL_ADD (array, i, strcmp, 1, __strcmp_sse2))
Sepertinya AVX2_Usable
adalah fitur untuk menonaktifkan. Mari jalankan kembali gdb sesuai:
GLIBC_TUNABLES=glibc.cpu.hwcaps=-AVX2_Usable gdb...
Pada iterasi ini ia mengeluh tentang __memmove_avx_unaligned_erms
, yang tampaknya diaktifkan oleh AVX_Usable
- tetapi saya menemukan jalur lain di ifunc-memmove.h yang diaktifkan oleh AVX_Fast_Unaligned_Load
. Kembali ke papan gambar:
GLIBC_TUNABLES=glibc.cpu.hwcaps=-AVX2_Usable,-AVX_Fast_Unaligned_Load gdb ...
Pada babak terakhir ini saya menemukan rdtscp
instruksi di perpustakaan bersama ASAN, jadi saya mengkompilasi ulang tanpa pembersih alamat dan akhirnya berhasil.
Singkatnya:dengan beberapa pekerjaan, Anda dapat menonaktifkan petunjuk ini dari baris perintah dan menggunakan fitur catatan gdb tanpa peretasan parah.
Saya mengalami masalah ini baru-baru ini juga, dan akhirnya menyelesaikannya menggunakan kesalahan CPUID dinamis untuk menghentikan eksekusi instruksi CPUID dan mengesampingkan hasilnya, yang menghindari menyentuh glibc atau penghubung dinamis. Ini memerlukan dukungan prosesor untuk kesalahan CPUID (Ivy Bridge+) serta dukungan kernel Linux (4.12+) untuk membukanya ke ruang pengguna melalui ARCH_GET_CPUID
dan ARCH_SET_CPUID
subfungsi dari arch_prctl()
. Saat fitur ini diaktifkan, SIGSEGV
sinyal akan dikirimkan pada setiap eksekusi CPUID, memungkinkan penangan sinyal dapat meniru eksekusi instruksi dan mengganti hasilnya.
Solusi lengkapnya sedikit terlibat karena saya juga perlu memasang linker dinamis, karena deteksi kemampuan perangkat keras dipindahkan ke sana dimulai dengan glibc 2.26+. Saya telah mengunggah solusi lengkap secara online di https://github.com/ddcc/libcpuidoverride .