//------------------------------------------------------------------------- static bool extract_version_from_libpython_filename( pylib_version_t *out, const char *p) { return extract_version_from_str(out, p, "libpython", ".so"); } //------------------------------------------------------------------------- void pyver_tool_t::do_find_python_libs(pylib_entries_t *result) const { // // Find all libpython3*so* present on disk // static const char lib_pattern[] = "libpython3*.so*"; static const char *dirs[] = { "/usr/lib/x86_64-linux-gnu", // Debian/Ubuntu "/usr/lib64", // RedHat - FHS #ifdef _DEBUG "/opt/test-python-libs" #endif }; qstring verbuf; for ( size_t i = 0; i < qnumber(dirs); ++i ) { const char *d = dirs[i]; out_verb("Searching for \"%s\" in \"%s\"\n", lib_pattern, d); { out_ident_inc_t iinc; char path[QMAXPATH]; qmakepath(path, sizeof(path), d, lib_pattern, nullptr); qffblk64_t fb; for ( int code = qfindfirst(path, &fb, 0); code == 0; code = qfindnext(&fb) ) { pylib_version_t version; if ( extract_version_from_libpython_filename(&version, fb.ff_name) ) { qmakepath(path, sizeof(path), d, fb.ff_name, nullptr); out_verb("Found: \"%s\" (version: %s)\n", path, version.str(&verbuf)); pylib_entry_t &e = result->get_or_create_entry_for_version(version); e.paths.push_back(path); } } } } } //------------------------------------------------------------------------- bool pyver_tool_t::do_path_to_pylib_entry( pylib_entry_t *entry, const char *path, qstring *errbuf) const { const char *fname = qbasename(path); const bool ok = fname != nullptr && qfileexist(path); if ( ok ) { extract_version_from_libpython_filename(&entry->version, fname); entry->paths.push_back(path); } else { errbuf->sprnt("Couldn't parse file name \"%s\"", fname); } return ok; } #define msg out #define warning out AS_PRINTF(1, 2) void ask_for_feedback(const char *format, ...) { } #include "../../ldr/elf/reader.cpp" //------------------------------------------------------------------------- static bool read_ident_and_header_and_get_dyninfo( dynamic_info_t *out_dyninfo, reader_t::dyninfo_tags_t *out_dyninfo_tags, reader_t &reader, qstring *errbuf) { if ( !reader.read_ident() ) { *errbuf = "Couldn't parse ELF file ident"; return false; } if ( !reader.read_header() ) { *errbuf = "Couldn't parse ELF file header"; return false; } dynamic_linking_tables_t dlt; if ( reader.read_section_headers() && reader.sections.has_valid_dynamic_linking_tables_info() ) { dlt = reader.sections.get_dynamic_linking_tables_info(); } else if ( reader.read_program_headers() && reader.pheaders.has_valid_dynamic_linking_tables_info() ) { dlt = reader.pheaders.get_dynamic_linking_tables_info(); } return dlt.is_valid() && reader.read_dynamic_info_tags(out_dyninfo_tags, dlt) && reader.parse_dynamic_info(out_dyninfo, *out_dyninfo_tags); } //------------------------------------------------------------------------- static bool find_libpython_dt_needed_info( char out_dt_needed_buf[MAXSTR], qoff64_t *out_dt_needed_off, const reader_t::dyninfo_tags_t &dyninfo_tags, reader_t &reader, const char *path, qstring *errbuf) { static const char needed_stem[] = "libpython"; for ( const auto &dyn : dyninfo_tags ) { if ( dyn.d_tag == DT_NEEDED ) { const qoff64_t off = reader.dyn_strtab.offset + dyn.d_un; input_status_t save_excursion(reader); if ( save_excursion.seek(off) == -1 ) { errbuf->sprnt("Couldn't seek to offset %" FMT_64 "u in \"%s\"", off, path); return false; } out_dt_needed_buf[0] = '\0'; qlread(reader.get_linput(), out_dt_needed_buf, MAXSTR); out_verb("DT_NEEDED at offset %" FMT_64 "u is: \"%s\"\n", off, out_dt_needed_buf); if ( strneq(out_dt_needed_buf, needed_stem, sizeof(needed_stem)-1) ) { *out_dt_needed_off = off; return true; } } } errbuf->sprnt("No DT_NEEDED starting with \"%s\" found", needed_stem); return false; } //------------------------------------------------------------------------- static bool patch_dt_needed( const char *path, const qstring &replacement, qstring *errbuf) { out_verb("Setting relevant DT_NEEDED of \"%s\" to \"%s\"\n", path, replacement.c_str()); out_ident_inc_t iinc; linput_t *linput = open_linput(path, /*remote=*/ false); if ( linput == nullptr ) { errbuf->sprnt("File not found: %s", path); return false; } linput_janitor_t lj(linput); reader_t reader(linput); dynamic_info_t dyninfo; reader_t::dyninfo_tags_t dyninfo_tags; if ( !read_ident_and_header_and_get_dyninfo( &dyninfo, &dyninfo_tags, reader, errbuf) ) { return false; } char dt_needed[MAXSTR]; qoff64_t dt_needed_off; if ( !find_libpython_dt_needed_info( dt_needed, &dt_needed_off, dyninfo_tags, reader, path, errbuf) ) { return false; } out_verb("Found DT_NEEDED; currently: \"%s\"\n", dt_needed); // count the maximum number of bytes we can store in there size_t room = 0; { input_status_t save_excursion(reader); if ( save_excursion.seek(dt_needed_off) == -1 ) { errbuf->sprnt("Couldn't seek to offset %" FMT_64 "u in \"%s\"", dt_needed_off, path); return false; } // find the end of the current DT_NEEDED uint8 byte; const int64 filesz = qlsize(reader.get_linput()); while ( qltell(reader.get_linput()) < filesz && reader.read_byte(&byte) == 0 ) if ( byte == 0 ) break; // then find the beginning of the next string, or the end of file while ( qltell(reader.get_linput()) < filesz && reader.read_byte(&byte) == 0 ) if ( byte != 0 ) break; room = qltell(reader.get_linput()) - dt_needed_off - 1; } const size_t nbytes = replacement.length(); out_verb("We have room for %" FMT_Z " bytes, and need to write %" FMT_Z "\n", room, nbytes); // and patch if ( room >= nbytes ) { FILE *fp = openM(path); if ( fp != nullptr ) { file_janitor_t fpj(fp); if ( qfseek(fp, dt_needed_off, SEEK_SET) == 0 ) { if ( !args.dry_run ) { // we want to write the zero as well! if ( qfwrite(fp, replacement.c_str(), nbytes+1) == nbytes+1 ) { out_verb("File \"%s\" successfully patched\n", path); } else { errbuf->sprnt("Couldn't write %" FMT_Z " bytes to \"%s\"", nbytes, path); return false; } } else { out("Would write %" FMT_Z " bytes (\"%s\") to file\n", nbytes, replacement.c_str()); } } else { errbuf->sprnt("Cannot seek to position %" FMT_64 "u in \"%s\"", dt_needed_off, path); return false; } } else { errbuf->sprnt("Couldn't open \"%s\" for writing", path); return false; } } else { errbuf->sprnt("Replacement \"%s\" has a length of %" FMT_Z " bytes, but there is only room for %" FMT_Z "" " bytes in the file. Cannot proceed.\n", replacement.c_str(), nbytes, room); return false; } return true; } //------------------------------------------------------------------------- bool pyver_tool_t::do_apply_version( const pylib_entry_t &entry, qstring *errbuf) const { qstring soname; for ( const auto &path : entry.paths ) { out_verb("Trying to find out DT_SONAME from file \"%s\"\n", path.c_str()); linput_t *linput = open_linput(path.c_str(), /*remote=*/ false); linput_janitor_t lj(linput); reader_t reader(linput); dynamic_info_t dyninfo; reader_t::dyninfo_tags_t dyninfo_tags; qstring nonfatal_errbuf; if ( read_ident_and_header_and_get_dyninfo( &dyninfo, &dyninfo_tags, reader, &nonfatal_errbuf) ) { for ( const auto &dyn : dyninfo_tags ) { if ( dyn.d_tag == DT_SONAME ) { soname = dyninfo.d_un_str(reader, dyn.d_tag, dyn.d_un); out_verb("Found DT_SONAME: \"%s\"\n", soname.c_str()); break; } } } else { out_verb("%s: %s", path.c_str(), nonfatal_errbuf.c_str()); } if ( !soname.empty() ) break; } if ( soname.empty() ) { *errbuf = "No SONAME found"; return false; } // Now, do patch struct ida_local patcher_t : public file_visitor_t { const qstring &lsoname; qstring *lerrbuf; patcher_t(const qstring &_soname, qstring *_errbuf) : lsoname(_soname), lerrbuf(_errbuf) {} virtual int visit_file(const char *path) { return patch_dt_needed(path, lsoname, lerrbuf) ? 0 : -1; } }; patcher_t patcher(soname, errbuf); return for_all_plugin_files(patcher, patcher.lerrbuf) == 0; } //------------------------------------------------------------------------- static int run_command(const char *_cmd, qstring *errbuf) { qstring cmd(_cmd); if ( args.dry_run ) cmd.insert("echo "); int rc = -1; out_verb("Running: \"%s\"\n", cmd.c_str()); FILE *fp = popen(cmd.c_str(), "r"); if ( fp != nullptr ) { char outbuf[MAXSTR]; /*ssize_t nread =*/ qfread(fp, outbuf, sizeof(outbuf)); rc = pclose(fp); if ( rc != 0 ) errbuf->sprnt("Error calling \"%s\"; output is: %s", cmd.c_str(), outbuf); } else { errbuf->sprnt("Command \"%s\" couldn't be run", cmd.c_str()); } return rc; } //------------------------------------------------------------------------- #define SLOT_SIZE 64 static bool split_debug_expand_libpython3_dtneeded_room( const char *path, qstring *errbuf) { qstring cmdline; char dt_needed[MAXSTR]; { qstring debug_path(path); debug_path.append(".debug"); char path_dir[QMAXPATH]; if ( !qdirname(path_dir, sizeof(path_dir), path) ) { errbuf->sprnt("Cannot obtain directory name for path \"%s\"", path); return false; } { char cwd[QMAXPATH]; qgetcwd(cwd, sizeof(cwd)); if ( qchdir(path_dir) == 0 ) { out_verb("Changed directory to: \"%s\"\n", path_dir); } else { errbuf->sprnt("Cannot chdir to \"%s\"", path_dir); return false; } cmdline.sprnt("objcopy --only-keep-debug %s %s", qbasename(path), qbasename(debug_path.c_str())); if ( run_command(cmdline.c_str(), errbuf) != 0 ) return false; cmdline.sprnt("strip -s -x %s", qbasename(path)); if ( run_command(cmdline.c_str(), errbuf) != 0 ) return false; cmdline.sprnt("objcopy --add-gnu-debuglink=%s %s", qbasename(debug_path.c_str()), qbasename(path)); if ( run_command(cmdline.c_str(), errbuf) != 0 ) return false; if ( qchdir(cwd) == 0 ) { out_verb("Back to directory: \"%s\"\n", cwd); } else { errbuf->sprnt("Cannot chdir back to \"%s\"", cwd); return -1; } } linput_t *linput = open_linput(path, /*remote=*/ false); if ( linput == nullptr ) { errbuf->sprnt("File not found: %s", path); return false; } linput_janitor_t lj(linput); reader_t reader(linput); dynamic_info_t dyninfo; reader_t::dyninfo_tags_t dyninfo_tags; if ( !read_ident_and_header_and_get_dyninfo( &dyninfo, &dyninfo_tags, reader, errbuf) ) { return false; } qoff64_t dt_needed_off; if ( !find_libpython_dt_needed_info( dt_needed, &dt_needed_off, dyninfo_tags, reader, path, errbuf) ) { return false; } } qstring replacement(dt_needed); replacement.resize(SLOT_SIZE, '_'); // call patchelf to replace the DT_NEEDED with a padded one out_verb("Found DT_NEEDED: \"%s\"; replacing with \"%s\"\n", dt_needed, replacement.c_str()); cmdline.sprnt("patchelf --replace-needed %s %s %s", dt_needed, replacement.c_str(), path); if ( run_command(cmdline.c_str(), errbuf) == 0 ) { out_verb("\"%s\" command successful. Restoring the " "original DT_NEEDED of \"%s\"\n", cmdline.c_str(), dt_needed); qstring padded_dt_needed(dt_needed); padded_dt_needed.resize(SLOT_SIZE, '\0'); if ( !patch_dt_needed(path, padded_dt_needed, errbuf) ) return false; } else { return false; } return true; }