Мне нужно восстановить исходный код из исполняемого файла - PullRequest
4 голосов
/ 11 сентября 2011

Сейчас середина ночи, и я случайно переписал всю свою работу, набрав

gcc source.c -o source.c

У меня все еще есть оригинальный двоичный файл, и я надеюсь только разобрать его, но я незнать, как или какой лучший инструмент использовать, чтобы получить наиболее читаемый результат.Я знаю, что это не подходящее место для публикации, но я подчеркиваю.Может кто-нибудь помочь мне, пожалуйста?

Ответы [ 6 ]

4 голосов
/ 14 сентября 2011

Спасибо за загрузку файла.Как я и подозревал, он был разорван, поэтому имена функций остались.Помимо стандартного стандартного кода я мог определить функции main, register_broker, connect_exchange (неиспользуемые и пустые) и handle_requests.

Я провел немного времени в IDA Pro, и это было не слишкомтрудно восстановить функцию main().Во-первых, вот оригинальный, неизмененный список main() от IDA: http://pastebin.com/sBxhRJMM

Чтобы продолжить, вам необходимо ознакомиться с соглашением о вызовах AMD64 .Подводя итог, первые четыре аргумента передаются в RDI (EDI), RSI (ESI), RDX (EDX) и RCX (ECX).Остальное передается в стеке, но все вызовы в main() используют только до четырех аргументов, поэтому нам не нужно об этом беспокоиться.

IDA помеченно помечает аргументы стандартных функций C и дажепереименовал некоторые локальные переменные.Однако, это может быть улучшено и прокомментировано далее.Например, поскольку мы находимся в main(), мы знаем, что argc (первый аргумент) происходит от EDI (поскольку это int означает 32-битный, он использует только младшую половину RDI) и argvпроисходит от RSI (это указатель, поэтому он использует все 8 байтов регистра).Итак, мы можем переименовать локальные переменные, в которые копируются EDI и RSI:

mov     [rbp+argc], edi
mov     [rbp+argv], rsi

Далее идет простой условный блок:

cmp     [rbp+argc], 2
jz      short loc_400EB3            
mov     rax, cs:stderr@@GLIBC_2_2_5 
mov     rdx, rax                    
mov     eax, offset aUsage ; "Usage"
mov     rcx, rdx        ; s         
mov     edx, 5          ; n         
mov     esi, 1          ; size      
mov     rdi, rax        ; ptr       
call    _fwrite                     
mov     edi, 1          ; status    
call    _exit                       

Здесь мы сравниваем argc с 2, и еслиРовно, прыгаем дальше в коде.Если оно не равно, мы называем fwrite().Первый аргумент для этого находится в rdi, и rdi загружается из rax, который содержит адрес константной строки «Использование».Второй аргумент находится в esi и равен 1, третий в edx и равен 5, четвертый в rcx, который загружается из rdx, который имеет значение stderr@@GLIBC_2_2_5, что в основном необычноссылка на переменную stderr из libc.Собрав все воедино, мы получим:

fwrite("Usage", 1, 5, stderr);

Исходя из моего опыта, я могу сказать, что, скорее всего, это встроенный fprintf, поскольку 5 - это длина строки.Т.е. исходный код, вероятно, был:

fprintf(stderr, "Usage");

Следующий вызов простой exit(1);.Комбинируя и сравнение, мы получаем:

if ( argc != 2 )
{
  fprintf(stderr, "Usage");
  exit(1);
}

Продолжая в том же духе, мы можем идентифицировать другие вызовы и переменные, которые они используют.Все это утомительно описывать, поэтому я загрузил закомментированную версию разборки, где я попытался показать эквивалентный код C для каждого вызова.Вы можете увидеть это здесь: http://pastebin.com/p5sRSwgQ

Из этой закомментированной версии не очень сложно представить возможную версию main():

int main(int argc, char **argv)
{
  if ( argc != 2 )
  {
    fprintf(stderr, "Usage");
    exit(1);
  }
  char name[256];
  gethostname(name, sizeof(name));
  struct hostent* _hostent = gethostbyname(name);
  struct in_addr *_addr0 = (struct in_addr *)(_hostent->h_addr_list[0]);
  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port = htons(0);
  addr.sin_addr.s_addr = _addr0->s_addr;
  char *tmp = (char *)malloc(6);
  sprintf(tmp, "%d", addr.sin_port);
  char *ip_str = inet_ntoa(*_addr0);
  char *newbuf = (char *)malloc(strlen(argv[1]) + strlen(ip_str) + strlen(tmp) + 5);
  strcpy(newbuf, "r");
  strcat(newbuf, " ");
  strcat(newbuf, argv[1]);
  strcat(newbuf, " ");
  strcat(newbuf, ip_str);
  strcat(newbuf, " ");
  strcat(newbuf, tmp);
  register_broker(newbuf);
  int fd = socket(PF_INET, SOCK_STREAM, 0);
  if ( fd < 0 )
  {
    perror("Error creating socket");
    exit(1);
  }
  if ( bind(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0 )
  {
    perror("Error binding socket");
    exit(1);
  }
  if ( listen(fd, 0x80) != 0 )
  {
    perror("Error listening on socket");
    exit(1);
  }
  handle_requests(fd);
}

Восстановление двух других функций осталосьупражнение для читателя:)

4 голосов
/ 11 сентября 2011

Есть несколько инструментов (вы можете искать с помощью Google), но я бы посоветовал перекодировать его.Время, которое вы потратите на рефакторинг того, что вернет дизассемблер, вероятно, больше, чем перекодирование.

Я знаю, что это кажется очевидным, но правильный ответ будет таким: восстановление из резервной копии (которую вы должны иметь)

3 голосов
/ 11 сентября 2011

К сожалению, действительно нет хорошего способа перейти от двоичного кода к источнику.Вы можете попробовать Бумеранг , но я действительно не ожидаю хороших результатов.

2 голосов
/ 11 сентября 2011

Во-первых, найдите исходный файл резервной копии.Большинство редакторов создают файлы с именем .bak или filename.c~ при каждом сохранении файла.На компьютере с Windows судебно-медицинский инструмент может получить последние исходные файлы.Инструмент, который я написал, getfile, раньше предлагался NTI, но был приобретен Armor Holdings несколько лет назад - не знаю, доступен ли он до сих пор.

Если код работает, то часто запускает его подутилита strace() (стандартный компонент дистрибутивов Linux) может помочь с некоторыми аспектами декодирования программы, особенно если она ориентирована на ввод-вывод.Увы, если в программе в основном внутреннее манипулирование данными, это не очень полезно.Strace() создает журнал системных вызовов и параметров, переданных программой;иногда это неоценимый инструмент для понимания поведения программы.например, strace date производит (частично - я пропустил запуск библиотеки времени выполнения):

clock_gettime(CLOCK_REALTIME, {1315760058, 681379835}) = 0
open("/etc/localtime", O_RDONLY)        = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=2819, ...}) = 0
fstat64(3, {st_mode=S_IFREG|0644, st_size=2819, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb78b5000
read(3, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\4\0\0\0\4\0\0\0\0"..., 4096) = 2819
_llseek(3, -24, [2795], SEEK_CUR)       = 0
read(3, "\nPST8PDT,M3.2.0,M11.1.0\n", 4096) = 24
_llseek(3, 2818, [2818], SEEK_SET)      = 0
close(3)                                = 0
munmap(0xb78b5000, 4096)                = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb78b5000
write(1, "Sun Sep 11 09:54:18 PDT 2011\n", 29Sun Sep 11 09:54:18 PDT 2011) = 29
close(1)                                = 0
munmap(0xb78b5000, 4096)                = 0
close(2)                                = 0

Как только у вас появится что-то, что стоит сохранить:

  • Добавьте несколькосвоего рода управление исходным кодом (git, svn, cvs, ...) может быть более одного
  • Используйте инструмент автоматической сборки, например make, чтобы избежать глупых ошибок
  • Создавайте резервные копии один раз зав то время как.Даже когда я работаю в клиенте с каменными ножами и медведями, я могу отправлять мне исходные файлы по электронной почте для механизма резервного копирования последней очереди.
1 голос
/ 11 сентября 2011

Вы можете использовать dcc . Но в следующий раз вы должны использовать Git;)

0 голосов
/ 11 сентября 2011

Вы можете попробовать выполнить дизассемблирование с помощью objdump -d <filename>.

. Вы также можете просмотреть имена символов с помощью утилиты nm, чтобы увеличить объем памяти и помочь перекодировать источник.

Рекламный ролик IDA Pro дизассемблер / отладчик популярен в программном реверс-инжиниринге.К сожалению, реверс-инжиниринг двоичного файла - это медленная и сложная работа.

...