Почему я не могу передать вывод ассемблера на стандартный вывод?

[редактировать]
Это был просто своего рода эксперимент, который я провел, где я хотел посмотреть, смогу ли я обмануть ядро, чтобы выполнить эльфа из неназванного канала с подстановкой процесса с помощью /lib64/ld-linux-x86-64.so.2, я знал, что это был выстрел в темноте, но я просто надеялся узнать, сможет ли кто-нибудь дать мне ответ, почему это не сработало

$ /lib64/ld-linux-x86-64.so.2 <(gcc -c -xc <(echo $'#include <stdio.h>\n\nint main(){\nprintf("I work\\n");\nreturn 0;\n}') -o /dev/stdout)
/tmp/ccf5sMql.s: Assembler messages:
/tmp/ccf5sMql.s: Fatal error: can't write /dev/stdout: Illegal seek
as: BFD version 2.25.1-22.base.el7  assertion fail elf.c:2660
as: BFD version 2.25.1-22.base.el7  assertion fail elf.c:2660
/tmp/ccf5sMql.s: Fatal error: can't close /dev/stdout: Illegal seek
/dev/fd/63: error while loading shared libraries: /dev/fd/63: file too short

Я подумал, что это могло быть возможно из-за разных результатов, которые я получал.

$ /lib64/ld-linux-x86-64.so.2 <(gcc -fPIC -pie -xc <(echo $'#include 
<stdio.h>\n\nint main(){\nprintf("I work\\n");\nreturn 0;\n}') -o 
/dev/stdout|cat|perl -ne 'chomp;printf')
/dev/fd/63: error while loading shared libraries: /dev/fd/63: ELF load 
command past end of file
$ /lib64/ld-linux-x86-64.so.2 <(gcc -fPIC -pie -xc <(echo $'#include 
<stdio.h>\n\nint main(){\nprintf("I work\\n");\nreturn 0;\n}') -o 
/dev/stdout|cat|perl -0 -ne 'chomp;printf')
/dev/fd/63: error while loading shared libraries: /dev/fd/63: ELF file ABI 
version invalid

Поэтому я поиграл с ASM и заметил, что вы не можете собрать или связать вывод с stdout.

$ as /tmp/lol.s -o /dev/stdout
/tmp/lol.s: Assembler messages:
/tmp/lol.s: Fatal error: can't write /dev/stdout: Illegal seek
as: BFD version 2.25.1-22.base.el7  assertion fail elf.c:2660
as: BFD version 2.25.1-22.base.el7  assertion fail elf.c:2660


as /tmp/lol.s -o /tmp/test.o
$ ld /tmp/test.o -o what -lc
ld: warning: cannot find entry symbol _start; defaulting to 00000000004002a0


$ exec 9< <(ld /tmp/test.o -o /dev/stdout -lc)
ld: warning: cannot find entry symbol _start; defaulting to 00000000004002a0
ld: final link failed: Illegal seek

Учитывая код следующим образом:

.file   "63"
.section        .rodata
.LC0:
.string "I work"
.text
.globl  main
.type   main, @function
main:
.LFB0:
.cfi_startproc
pushq   %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq    %rsp, %rbp
.cfi_def_cfa_register 6
movl    $.LC0, %edi
call    puts
movl    $0, %eax
popq    %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size   main, .-main
.ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-16)"
.section        .note.GNU-stack,"",@progbits
.file   "63"
.section        .rodata

Может кто-нибудь сказать мне, почему невозможно собрать объекты или связать объекты с stdout? Пожалуйста, будьте как можно глубже. Чтобы увидеть весь процесс, к которому идет компилятор для генерации этого кода, вы можете использовать следующее:

$ exec 7< <(gcc -c -xc <(echo $'#include <stdio.h>\n\nint main(){\nprintf("I work\\n");\nreturn 0;\n}') -o /dev/stdout)

Если вы собираете и связываете сборку, которую я предоставил ранее, и хотите выполнить ее правильно, вам нужно вызвать /lib64/ld-linux-x86-64.so.2 /path/to/output, иначе она просто скажет, что интерпретатор плохой эльф,

# ./what
bash: ./what: /lib/ld64.so.1: bad ELF interpreter: No such file or directory

# /lib64/ld-linux-x86-64.so.2 ./what
I work

1 ответ

Решение

Вы не можете передать вывод ассемблера в stdout, потому что с незапамятных времен (вероятно, 1960-х) ассемблеры обычно работают в два прохода (и не только на входе, но и на выходе). Таким образом, требуется умение искать (и вход, и выход, используя l seek (2)). В противном случае им потребуется хранить большую часть входных и выходных данных в памяти.

Помните, что объектный файл содержит не только данные (например, машинные инструкции, постоянные только для чтения), но и информацию о перемещении.

/tmp/lol.s: фатальная ошибка: невозможно записать /dev/stdout: незаконный поиск

Это показывает, что as Программа должна искать файлы (например, используя lseek (2)).

Возможно, вы хотите сгенерировать машинный код в памяти. Для этого используйте некоторую библиотеку компиляции JIT, такую ​​как libgccjit или asmjit.

Кстати, вы можете понять, как gcc компилирует простую программу на Си. Для этого скомпилируйте его с gcc -v и обратите внимание, что какая-то вещь crt0 связана.

Если вы решили использовать каналы по соображениям производительности, используйте вместо этого некоторую файловую систему tmpfs. Файлы там остаются в памяти (поэтому теряются при отключении) и быстро, потому что дисковый ввод-вывод не выполняется.

Вы даже можете создать какой-нибудь файл C в такой файловой системе, а затем спросить gcc скомпилировать его (возможно, как плагин). Смотрите также это.

... Если бы я мог обмануть ядро ​​в выполнении эльфа из безымянного канала

Нет, ты не можешь. Исполняемый файл ELF также должен быть доступен для поиска, потому что ядро ​​во время execve(2) устанавливает свежее виртуальное адресное пространство, используя что-то близкое к mmap(2) внутри. Другими словами, execve настраивает несколько отображений памяти.

Изучите виртуальное адресное пространство ваших процессов. Прочитайте proc (5), затем попробуйте cat /proc/$$/maps (и заменить $$ с более интересным пидом).

Чтение операционных систем: Вам должно быть интересно три легкие пьесы (свободно загружаемые).

Другие вопросы по тегам