【问题标题】:How to write and execute PURE machine code manually without containers like EXE or ELF?如何在没有 EXE 或 ELF 等容器的情况下手动编写和执行 PURE 机器代码?
【发布时间】:2011-07-13 04:25:47
【问题描述】:

我只需要一个 hello world 演示来了解机器代码的实际工作原理。

虽然windows的EXE和linux的ELF接近机器码,但不是PURE

如何编写/执行 PURE 机器代码?

【问题讨论】:

  • 纯粹是什么意思?没有标题的东西?如果您在足够低的级别编写代码,则不需要链接任何库,因此可执行文件将只是您的代码,并设置了文件结构,以便操作系统可以加载它。
  • 除了操作系统运行的指令之外没有其他东西。
  • 相关但专门加载到另一个程序的虚拟内存中:stackoverflow.com/questions/3615392/…

标签: machine-code


【解决方案1】:

可以手动编写纯机器代码无需汇编

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

赢/MZ/PE:

shellcode2exe.py(采用 asciihex shellcode 并创建一个合法的 MZ PE exe 文件)脚本位置:

https://web.archive.org/web/20140725045200/http://zeltser.com/reverse-malware/shellcode2exe.py.txt

依赖:

https://github.com/radare/toys/tree/master/InlineEgg

提取

python setup.py build




sudo python setup.py install

【讨论】:

  • 好的,m2elf 现在支持内存分配;我刚刚用纯机器代码测试了“Hello World”,它可以工作。 PoC 位于上述 github 页面上 README 的底部
  • 您是从哪里得知这件事的?你有一些资源可以分享吗,我很感兴趣:-)
  • 对于 ELF 标头:elf.h、ELF 上的 Ange Albertini 信息图,以及 assemble->hexdump->analyze hacking。
  • 对于机器代码,我已经阅读了 Intel 手册的 Vol II(我也阅读了 Vol I 和 III,但 Vol II 是深入研究说明的那个)。我在我的一些博客文章 (xlogicx.net) 上演示了一些奇怪的 x86 技巧。如果您想进一步讨论,请给我发电子邮件。
  • 另外,如果您想使用 m2elf.pl 而无需进行太多设置,remnux v6 现在已经内置(remnux.org)
【解决方案2】:

大家都知道,我们平时写的应用是运行在操作系统上的。并由它管理。

表示操作系统在机器上运行。所以我认为那是你所说的纯机器代码。

因此,您需要研究操作系统的工作原理。

这里是一些用于引导扇区的 NASM 汇编代码,可以在 PURE 中打印“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 等。 因为很难找到带软盘的机器。

所以,步骤是 首先,您应该需要安装一个完整的虚拟机。 二、通过comment创建视觉软盘:bximage 第三,将 bin 文件写入该可视软盘。 最后,从那张可视软盘启动您的可视机器。

注意:在 https://wiki.osdev.org 中,有一些关于该主题的基本信息。

【讨论】:

  • 有没有更容易运行的演示,只是做一些非常简单的事情?
  • 如果您使用 NASM 编译了汇编代码,您可以使用 GUI 工具完成所有工作。你也可以用 MS windows 来做这件事。您需要获得流动的软件: 1、软盘映像写入器 2、Oracle VM VirtualBox 。关键是,在 VirtualBox 中,首先选择在启动顺序列表中选择的软盘并添加一个软盘控制器,然后加载由软盘镜像写入器创建的软盘镜像文件。如何使用这两个工具,你可以阅读手册或谷歌它。不难。
  • “很简单”?那家伙给了你 16 行汇编程序和一个运行它的命令。从头开始编写自己的操作系统没有比这更容易的了。我建议使用虚拟机路由而不是软盘。一方面,软盘驱动器现在很难买到。另一方面,在真机上启动自己的内核可能会损坏机器,但不会损坏模拟器。最后,无需一直重启机器即可轻松测试。
  • Assembly 不是“纯”机器码,它是机器码的抽象。
  • @compiler 这就像机器码一样简单,如果你认为这太难了,那么你可能没有足够的程序员经验来尝试机器码。
【解决方案3】:

听起来您正在寻找旧的16-bit DOS .COM file format.COM 文件的字节在程序段中的偏移量 100h 处加载(限制它们的最大大小为 64k - 256 字节),CPU 只是在偏移量 100h 处开始执行。没有标头或任何类型的任何必需信息,只有原始 CPU 指令。

【讨论】:

【解决方案4】:

真实机器码

运行测试需要什么:Linux x86 或 x64(在我的情况下,我使用的是 Ubuntu x64)

开始吧

这个程序集 (x86) 将值 666 移动到 eax 寄存器中:

movl $666, %eax
ret

让我们对其进行二进制表示:

操作码 movl(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

【讨论】:

  • 您从哪里获得 MOV 等的操作码?还有为什么你需要机器语言中的宽度命令?
  • 另外你知道是否有一个基本的basic跨浏览器文件格式,可以在任何平台上简单地双击执行这个机器代码(假设没有外部依赖) ?
【解决方案5】:

操作系统没有运行指令,CPU 运行(除非我们谈论的是确实存在的虚拟机操作系统,我正在考虑 Forth 或类似的东西)。然而,操作系统确实需要一些元信息才能知道,文件确实包含可执行代码,以及它期望其环境看起来如何。 ELF 不仅仅是 near 机器码。它机器码,连同一些信息让操作系统知道它应该让 CPU 实际执行该操作。

如果您想要比 ELF 更简单但 *nix 的东西,请查看 a.out 格式,它要简单得多。传统上,*nix C 编译器确实(仍然)将其可执行文件写入名为 a.out 的文件,如果没有指定输出名称。

【讨论】:

  • 是从纯机器语言制作可执行文件的最简单方法吗?是跨平台的吗?
  • @bluejayke:这是 *nixes 曾经支持的最简单的格式。然而,这些天它几乎不再支持了。 它绝对不是跨平台的。 如果您想采用最简单的裸机“格式”,您可以在 CPU 的复位向量地址处编写一个无头的原始指令流并将其写入BIOS/UEFI 固件闪存。但是,您必须先实现整个 BIOS,才能做一些有意义的事情。
  • @bluejayke:就最简单的格式而言,在良好的旧 DOS 中,这将是 COM 格式,它也只是一个原始指令流(就像固件一样)。然而,这些只能在 DOS 下工作。
  • 哦,有趣的是,如果确实编写了整个 BIOS,是否可以在另一个操作系统中加载它?
  • @bluejayke:如果您要将它加载到机器模拟器中,那么可以。这基本上就是所有虚拟机(如 VirtualBox、VMWare、Qemu 等)所做的。但是,BIOS 不是您可以在操作系统中作为常规进程执行的程序。有两个原因:在现代操作系统下运行的第一个进程被阻止尽一切可能;不允许对 BIOS 进行的低级访问。第二,BIOS 最初尝试做的许多事情会严重破坏操作系统已经完成的任务。
【解决方案6】:

以嵌入式系统为目标时,您可以制作 rom 或 ram 的二进制映像,该映像严格来说是程序中的指令和相关数据。并且通常可以将该二进制文件写入闪存/ROM 并运行它。

操作系统想要知道的远不止这些,开发人员通常希望在他们的文件中留下更多信息,以便他们以后可以调试或用它做其他事情(使用一些可识别的符号名称进行反汇编)。此外,嵌入式或在操作系统上,您可能需要将 .text 与 .data 与 .bss 与 .rodata 等分开,而 .elf 等文件格式为此提供了一种机制,首选用例是加载该 elf某种加载程序,无论是操作系统还是对微控制器的 rom 和 ram 进行编程的东西。

.exe 也有一些头信息。如前所述,.com 没有在地址 0x100h 加载并在那里分支。

要从可执行文件创建原始二进制文件,例如使用 gcc 创建的 elf 文件,您可以执行类似

的操作

objcopy file.elf -O 二进制文件.bin

如果程序是分段的(.text、.data 等)并且这些分段不是背靠背的,则二进制文件可能会变得非常大。再次以嵌入式为例,如果 rom 位于 0x00000000 并且数据或 bss 位于 0x20000000,即使您的程序只有 4 个字节的数据,objcopy 也会创建一个 0x20000004 字节的文件来填充 .text 和 .data 之间的间隙(因为它应该因为那是你要求它做的)。

你想做什么?读取 elf 或 intel hex 或 srec 文件非常简单,从中您可以看到二进制文件的所有位和字节。或者拆卸精灵或任何东西也会以人类可读的形式向你展示。 (objdump -D file.elf > file.list)

【讨论】:

    【解决方案7】:

    使用纯机器代码,您可以使用任何能够编写文件的语言。 甚至visual basic.net也可以写8、16、32、64位,同时在写的时候在int类型之间互换。

    您甚至可以根据需要设置让 vb 在循环中写出机器码 对于像 setpixel 这样的东西,x,y 会发生变化,而你有你的 argb 颜色。

    或者,定期在 Windows 中创建您的 vb.net 程序,并使用NGEN.exe 制作程序的本机代码文件。它可以一次性创建特定于 ia-32 的纯机器代码,而无需使用 JIT 调试器。

    【讨论】:

      【解决方案8】:

      这是很好的回答,但为什么有人要这样做可能会更好地指导答案。我认为最重要的原因是要完全控制他们的机器,尤其是缓存写入,以获得最佳性能,并防止任何操作系统共享处理器或虚拟化您的代码(从而减慢它的速度),尤其是在这些日子里窥探你的代码也是如此。据我所知,assembler 不处理这些问题,M$/Intel 和其他公司将其视为侵权或“针对黑客”。然而,这是非常错误的。如果您的汇编代码被移交给操作系统或专有硬件,那么真正的优化(可能在 GHz 频率下)将是遥不可及的。这对于科学和技术来说是一个非常重要的问题,因为我们的计算机在没有硬件优化的情况下无法充分发挥其潜力,并且通常计算低于它几个数量级。可能有一些解决方法或一些开源硬件可以实现这一点,但我还没有找到它。一分钱任何人的想法。

      【讨论】:

        【解决方案9】:

        下一个程序是我用Machine Code 16 bit(intel 8086)写的一个Hello World程序,如果你想了解机器码,我建议你先学习Assembly,因为Assembly中的每一行代码都转换为A机器码中的代码行。好吧,我知道我来自世界上少数仍然使用机器代码而不是汇编编程的人。

        顺便说一句,要运行它,请使用“.com”扩展名保存文件并在 DOSBOX 上运行!

        So, this is an Hello World Program.

        【讨论】:

        • 解释:“B4 00 B0 10 CD 10”将视频模式设置为EGA,16色,640x350。 “EB”是一个简短的 jmp 命令,它可以向前 jmp 127 个字节和向后 jmp 128 个字节。 “4B”是跳多少。 "24 30" ($0) 是该字符串开始位置的打印命令指示符。字符串末尾的“00”(null)告诉 print 方法它到达了字符串的末尾。 “AC 3C 24 72 02 EB F9”告诉方法将下一个字节从内存加载到 AX 堆栈中,如果 AL(AX 的一部分)等于“24”($),则转到“AC 38 D0 74 02 EB F2"......
        • ... 检查 AL 寄存器是否等于 DL 寄存器(我将 0 或 1 字符移入其中),如果是,则转到“AC 3C 00 74 06 B4 0E CD 10 EB F5 C3" 基本上打印下一个字节,直到下一个字节为“00”,在这种情况下,返回。 “B2 31 B3 0C”将DL寄存器设置为字符1,将bl(用于颜色)设置为0C(浅红色),“E8”告诉它调用“CB FF”中的方法。剩下的只是用户输入,将视频模式设置回 03,然后退出(“B4 4C CD 21”)。
        【解决方案10】:

        在 Windows(至少 32 位 Windows)上,您可以使用 .com 文件执行 RAW INSTRUCTIONS。

        例如,如果您将此字符串保存在带有 .com 扩展名的记事本中:

        X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
        

        它会打印一个字符串并启动你的杀毒软件。

        【讨论】:

        • 这不是机器码。这只是一个用于测试防病毒软件的 EICAR 测试字符串。出于测试目的,它将被检测为病毒。
        • 作为 com 文件运行时都是有效的 X86。
        • 普遍误解 EICAR 只是一个字符串。帕特里克是完全正确的,这个程序使用 int 21 来打印 $ 终止的字符串“EICAR-STANDARD-ANTIVIRUS-TEST-FILE。但是由于这个完整的中断不是 ASCII 可打印的,导致字符串的代码是自我修改的,因此可以使用 INT 21。阅读以下链接以获取引人入胜的完整分步分析:thestarman.pcministry.com/asm/eicar/eicarcom.html
        • 还应该注意的是,它确实不是“作为 com 文件运行时所有有效的 X86”。它可能会被发送到处理器执行,但不能保证处理器能够对代码执行任何操作,更糟糕的是它可能会运行未记录的命令,例如可能导致设备无法使用的制造商代码(理论上虽然不太可能)。
        猜你喜欢
        • 2012-07-29
        • 1970-01-01
        • 1970-01-01
        • 2020-09-22
        • 2021-02-03
        • 2012-02-08
        • 2013-08-08
        • 2010-11-17
        • 1970-01-01
        相关资源
        最近更新 更多