我只需要一个“Hello World”演示程序,以了解机器码实际上是如何工作的。
虽然Windows的 EXE 和Linux的 ELF 接近机器码,但它们不是纯的。
我怎样才能编写/执行纯的机器码?
我只需要一个“Hello World”演示程序,以了解机器码实际上是如何工作的。
虽然Windows的 EXE 和Linux的 ELF 接近机器码,但它们不是纯的。
我怎样才能编写/执行纯的机器码?
你可以手动编写纯机器代码,无需汇编
Linux/ELF: https://github.com/XlogicX/m2elf。这仍在进行中,我昨天刚开始研究。
“Hello World”的源文件如下:
b8 21 0a 00 00 #moving "!\n" into eax
a3 0c 10 00 06 #moving eax into first memory location
b8 6f 72 6c 64 #moving "orld" into eax
a3 08 10 00 06 #moving eax into next memory location
b8 6f 2c 20 57 #moving "o, W" into eax
a3 04 10 00 06 #moving eax into next memory location
b8 48 65 6c 6c #moving "Hell" into eax
a3 00 10 00 06 #moving eax into next memory location
b9 00 10 00 06 #moving pointer to start of memory location into ecx
ba 10 00 00 00 #moving string size into edx
bb 01 00 00 00 #moving "stdout" number to ebx
b8 04 00 00 00 #moving "print out" syscall number to eax
cd 80 #calling the linux kernel to execute our print to stdout
b8 01 00 00 00 #moving "sys_exit" call number to eax
cd 80 #executing it via linux sys_call
WIN/MZ/PE:
shellcode2exe.py(将ASCII十六进制格式的Shellcode转换为合法的MZ PE可执行文件)脚本位置:
https://web.archive.org/web/20140725045200/http://zeltser.com/reverse-malware/shellcode2exe.py.txt
依赖:
https://github.com/radare/toys/tree/master/InlineEgg
extract
python setup.py build
sudo python setup.py install
运行测试所需的条件: Linux x86或x64(在我的情况下,我正在使用Ubuntu x64)
让我们开始吧
这个汇编(x86)将值666移动到eax寄存器中:
movl $666, %eax
ret
让我们将其转换为二进制表示:
操作码movl(带有32位操作数的mov)的二进制表示=1011
指令width的二进制表示=1
寄存器eax的二进制表示=000
有符号32位二进制中数字666的表示方式为=00000000 00000000 00000010 10011010
666 转换为 little endian 的形式=10011010 00000010 00000000 00000000
指令ret(返回)的二进制表示为=11000011
因此,我们最终的纯二进制指令如下所示:
1011(movl)1(width)000(eax)10011010000000100000000000000000(666)
11000011(ret)
将其全部组合在一起:
1011100010011010000000100000000000000000
11000011
要执行它,二进制代码必须放置在具有执行权限的内存页面中,我们可以使用以下C代码来实现:
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
/* Allocate size bytes of executable memory. */
unsigned char *alloc_exec_mem(size_t size)
{
void *ptr;
ptr = mmap(0, size, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANON, -1, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
exit(1);
}
return ptr;
}
/* Read up to buffer_size bytes, encoded as 1's and 0's, into buffer. */
void read_ones_and_zeros(unsigned char *buffer, size_t buffer_size)
{
unsigned char byte = 0;
int bit_index = 0;
int c;
while ((c = getchar()) != EOF) {
if (isspace(c)) {
continue;
} else if (c != '0' && c != '1') {
fprintf(stderr, "error: expected 1 or 0!\n");
exit(1);
}
byte = (byte << 1) | (c == '1');
bit_index++;
if (bit_index == 8) {
if (buffer_size == 0) {
fprintf(stderr, "error: buffer full!\n");
exit(1);
}
*buffer++ = byte;
--buffer_size;
byte = 0;
bit_index = 0;
}
}
if (bit_index != 0) {
fprintf(stderr, "error: left-over bits!\n");
exit(1);
}
}
int main()
{
typedef int (*func_ptr_t)(void);
func_ptr_t func;
unsigned char *mem;
int x;
mem = alloc_exec_mem(1024);
func = (func_ptr_t) mem;
read_ones_and_zeros(mem, 1024);
x = (*func)();
printf("function returned %d\n", x);
return 0;
}
来源: https://www.hanshq.net/files/ones-and-zeros_42.c
我们可以使用以下命令进行编译:
gcc source.c -o binaryexec
执行程序:
./binaryexec
然后我们输入第一组指令:
1011100010011010000000100000000000000000
按回车键
并通过执行返回指令:
11000011
按回车键
最后按ctrl+d结束程序并获取输出结果:
函数返回666
众所周知,我们通常编写的应用程序是在操作系统上运行并由其管理。
这意味着操作系统在计算机上运行。因此,我认为你所说的是纯机器码。
所以,你需要学习操作系统如何工作。
这里有一些 NASM 汇编代码,可以在纯净环境下打印 "Hello world"。
org
xor ax, ax
mov ds, ax
mov si, msg
boot_loop:lodsb
or al, al
jz go_flag
mov ah, 0x0E
int 0x10
jmp boot_loop
go_flag:
jmp go_flag
msg db 'hello world', 13, 10, 0
times 510-($-$$) db 0
db 0x55
db 0xAA
你可以在这里找到更多资源:http://wiki.osdev.org/Main_Page。
结束。
如果您已安装了nasm并有一个软盘,您可以
nasm boot.asm -f bin -o boot.bin
dd if=boot.bin of=/dev/fd0
接下来,你可以从这个软盘启动,并且你会看到消息。 (注意:你应该将计算机的第一次启动设为软盘。)
事实上,我建议你在完整的虚拟机中运行该代码,例如:bochs,virtualbox等。 因为很难找到带软盘驱动器的机器。
所以,步骤如下: 第一,你需要安装一个完整的虚拟机。 第二,通过命令创建一个虚拟软盘:bximage 第三,在虚拟软盘中写入bin文件。 最后,从虚拟软盘启动你的虚拟机。
注意:在 https://wiki.osdev.org 上,有一些关于该主题的基础信息。
听起来你正在寻找旧的16位DOS .COM
文件格式。一个.COM
文件的字节被加载到程序段的偏移量100h处(将其限制在最大大小为64k-256字节),然后CPU就直接从偏移量100h开始执行。这种格式没有头部或者任何必需的信息,只有原始的CPU指令。
操作系统并不运行指令,而是由CPU来执行(除非我们谈论的是虚拟机操作系统,虚拟机操作系统确实存在,我想到的是Forth或类似的语言)。然而,操作系统需要一些元信息来知道一个文件是否包含可执行代码,以及它希望环境看起来像什么。ELF不仅仅是接近于机器码,它就是机器码,加上一些信息,让操作系统知道它应该让CPU实际执行那个东西。
如果您想要比ELF更简单但与*nix相关的格式,请查看a.out格式,这个格式要简单得多。传统的*nix C编译器(仍然)将可执行文件写入名为a.out的文件中,如果未指定输出名称。
当针对嵌入式系统时,您可以将ROM或RAM的二进制图像限定为程序中的指令和相关数据。通常可以将该二进制写入闪存/ROM并运行它。
操作系统希望了解更多信息,并且开发人员通常希望在文件中留下更多内容,以便以后进行调试或执行其他操作(使用一些可识别的符号名称进行反汇编)。此外,在嵌入式系统或操作系统上,您可能需要将.text与.data、.bss与.rodata等分离,而.elf等文件格式提供了机制来实现这一点,首选用例是通过某种加载器加载该elf文件,无论是操作系统还是编程ROM和RAM的微控制器。
.exe也具有一些头部信息。如前所述,.com文件在0x100h地址处加载并跳转到那里。
要从可执行文件创建原始二进制文件,例如由gcc创建的elf文件,您可以执行以下操作:
objcopy file.elf -O binary file.bin
如果程序被分段了(.text,.data等),并且这些段不是相邻的,那么二进制文件就会变得非常大。再次以嵌入式为例,如果rom在0x00000000处,而数据或bss在0x20000000处,即使您的程序只有4个字节的数据objcopy也会创建一个0x20000004字节的文件来填补.text和.data之间的空隙(因为这正是您要求它做的)。
你想做什么?阅读elf、intel hex或srec文件非常简单,从中可以看到二进制文件的所有位和字节。或者反汇编elf或其他格式,也可以以人类可读的形式展示给你。(objdump -D file.elf > file.list)
通过纯机器代码,您可以使用任何具有编写文件能力的语言。 即使是Visual Basic.net也可以在写入时在8、16、32、64位之间交换int类型。
您甚至可以设置vb根据需要循环输出机器代码, 例如setpixel,其中x、y更改并且您有argb颜色。
或者,在Windows中定期创建您的vb.net程序,并使用NGEN.exe
制作程序的本地代码文件。它会一次性创建特定于ia-32的纯机器代码,将JIT调试器抛在一边。
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
它将打印一个字符串并触发您的防病毒软件。