Как найти перемещение нагрузки для двоичного файла пирога? - PullRequest
4 голосов
/ 08 марта 2019

Мне нужно получить базовый адрес стека внутри моего запущенного процесса.Это позволило бы мне печатать необработанные трассировки стека, которые будут понятны addr2line (исполняемый двоичный файл удаляется, но addr2line имеет доступ к символам).Мне удалось сделать это, изучив заголовок эльфа argv[0]: я прочитал точку входа и вычел ее из &_start:

#include <stdio.h>
#include <execinfo.h>
#include <unistd.h>
#include <elf.h>
#include <stdio.h>
#include <string.h>
void* entry_point = NULL;
void* base_addr = NULL;
extern char _start;

/// given argv[0] will populate global entry_pont
void read_elf_header(const char* elfFile) {
  // switch to Elf32_Ehdr for x86 architecture.
  Elf64_Ehdr header;
  FILE* file = fopen(elfFile, "rb");
  if(file) {
    fread(&header, 1, sizeof(header), file);
    if (memcmp(header.e_ident, ELFMAG, SELFMAG) == 0) {
        printf("Entry point from file: %p\n", (void *) header.e_entry);
        entry_point = (void*)header.e_entry;
        base_addr = (void*) ((long)&_start - (long)entry_point);
    }
    fclose(file);
  }
}

/// print stacktrace
void bt() {
    static const int MAX_STACK = 30;
    void *array[MAX_STACK];
    auto size = backtrace(array, MAX_STACK);
    for (int i = 0; i < size; ++i) {
        printf("%p ", (long)array[i]-(long)base_addr );
    }
    printf("\n");
}

int main(int argc, char* argv[])
{
    read_elf_header(argv[0]);
    printf("&_start = %p\n",&_start);
    printf("base address is: %p\n", base_addr);
    bt();

    // elf header is also in memory, but to find it I have to already have base address
    Elf64_Ehdr * ehdr_addr = (Elf64_Ehdr *) base_addr;
    printf("Entry from memory: %p\n", (void *) ehdr_addr->e_entry);

    return 0;
}

Пример вывода:

Entry point from file: 0x10c0
&_start = 0x5648eeb150c0
base address is: 0x5648eeb14000
0x1321 0x13ee 0x29540f8ed09b 0x10ea 
Entry from memory:  0x10c0

А потом яможно

$ addr2line -e a.out 0x1321 0x13ee 0x29540f8ed09b 0x10ea
/tmp/elf2.c:30
/tmp/elf2.c:45
??:0
??:?

Как получить базовый адрес без доступа к argv?Возможно, мне придется распечатать следы до main() (инициализация глобальных).Поворот ASLR или PIE не возможен.

Ответы [ 2 ]

4 голосов
/ 10 марта 2019

Как получить базовый адрес без доступа к argv?Мне может потребоваться распечатать трассировки перед main ()

Есть несколько способов:

  1. Если смонтировано /proc (что почти всегда так), вы можетепрочитайте заголовок ELF из /proc/self/exe.
  2. Вы можете использовать dladdr1(), как показывает ответ Антти Хаапала.
  3. Вы можете использовать _r_debug.r_map, который указывает на связанный список загруженных ELFизображений.Первая запись в этом списке соответствует a.out, а ее l_addr содержит искомое перемещение.Это решение эквивалентно dladdr1, но не требует связывания с libdl.

Не могли бы вы предоставить пример кода для 3?

Конечно:

#include <link.h>
#include <stdio.h>

extern char _start;
int main()
{
  uintptr_t relocation = _r_debug.r_map->l_addr;
  printf("relocation: %p, &_start: %p, &_start - relocation: %p\n",
         (void*)relocation, &_start, &_start - relocation);
  return 0;
}

gcc -Wall -fPIE -pie t.c && ./a.out
relocation: 0x555d4995e000, &_start: 0x555d4995e5b0, &_start - relocation: 0x5b0

2 и 3 одинаково переносимы?

Я думаю, они примерно одинаково переносимы: dladdr1 - это расширение GLIBC, которое также присутствует в Solaris._r_debug предшествует Linux и также будет работать на Solaris (я на самом деле не проверял, но я верю, что это будет).Может работать и на других платформах ELF.

4 голосов
/ 09 марта 2019

Этот фрагмент кода выдает то же значение, что и ваш base_addr в Linux:

#define _GNU_SOURCE
#include <dlfcn.h>
#include <link.h>

Dl_info info;
void *extra = NULL;
dladdr1(&_start, &info, &extra, RTLD_DL_LINKMAP);
struct link_map *map = extra;
printf("%#llx", (unsigned long long)map->l_addr);

Страница руководства dladdr1 гласит следующее RTLD_DL_LINKMAP:

RTLD_DL_LINKMAP

Получить указатель на карту ссылок для соответствующего файла. Аргумент extra_info указывает на указатель на структуру link_map (т.е. struct link_map **), определяемую как:

  struct link_map {
      ElfW(Addr) l_addr;  /* Difference between the
                             address in the ELF file and
                             the address in memory */
      char      *l_name;  /* Absolute pathname where
                             object was found */
      ElfW(Dyn) *l_ld;    /* Dynamic section of the
                             shared object */
      struct link_map *l_next, *l_prev;
                          /* Chain of loaded objects */
      /* Plus additional fields private to the
         implementation */
  };

Обратите внимание, что -ldl требуется для связи с подпрограммами динамической загрузки.

...