Почему я не могу передать вывод ассемблера на стандартный вывод?
[редактировать]
Это был просто своего рода эксперимент, который я провел, где я хотел посмотреть, смогу ли я обмануть ядро, чтобы выполнить эльфа из неназванного канала с подстановкой процесса с помощью /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
(и заменить $$
с более интересным пидом).
Чтение операционных систем: Вам должно быть интересно три легкие пьесы (свободно загружаемые).