主要是一个函数。 那么那么什么时候? (2015)

2021-06-15 00:48:03

它始于我的同事尽管已经知道如何编程,被迫在我的大学介绍介绍电脑科学课程。我们和他一起开玩笑,他需要如何制作有效的程序,但分级TAS无法弄清楚它是如何工作的。因此,这是要求,制作一个运行程序,该程序完成了分配,同时混淆了这一年级机构认为它不应该工作。考虑到这一点,我开始思考我以前使用过的C的伎俩,特别是一件事突出。这个诀窍的想法我会解释如何完成来自一个博客的博客,主要是一个函数,让我想到什么时候是主要不是一个函数?让我们找出来!

(如果要下载任何文件,可以下载我在这里写的所有文件的邮编记住,我在64位Linux上写下它们,您需要调整它们在我猜测的其他平台上)

我的问题解决过程通常是我想象大多数程序员所做的事情。第1步:谷歌搜索问题。第2步:单击似乎相关的第一页上的每个链接。如果没有解决,请尝试不同的查询并重复。值得庆幸的是,这个问题的答案是在这个stackoverflow答案的第一个搜索中。显然在1984年,一个奇怪的程序赢得了IOCCC,主要被宣布为一个短暂的主题[] = {...},以某种方式这件事并打印到屏幕上!太糟糕了它是为整个不同的建筑和编译器编写的,所以我真的没有容易找到它所做的事情,但从这只是一堆数字来看,我可以猜测那里的数字只需在寻找主函数时编译的一些短函数和链接器的二进制文件只是在它的位置抛出这一点。

凭借我们的假设,程序的代码只是所代表为阵列的主要函数的编译程序,让我们看看我们是否可以通过制作小程序来复制这一点,并查看我们是否可以执行此操作。

$ gcc -wall main_char.c -o firstmain_char.c:1:6:警告:'main'通常是函数[-wmain] char main [] ="您好世界!" ; ^ $ ./firstse派运

好的!有效!有点......所以我们的下一个目标是我们希望它实际上打印到屏幕上。思考回到我有限的ASM经验,我回顾说,编制的不同部分决定了不同的事情。与我们最相关的两个部分是.text部分和.data部分。 .text包含所有可执行代码,它是readonly,而.data包含可读和可写的代码,但它不是可执行的。在我们的情况下,我们只能填写主要功能的代码,因此在数据部分中放置的任何东西都是禁止的。我们需要找到一种方法来获得字符串"你好世界!"在主要功能内并参考它。

我开始考虑如何用尽可能少的代码打印一些东西。由于我知道目标系统将是64位Linux,我发现我可以调用系统写入呼叫,它会写入屏幕。现在回顾这一点,我正在编写代码,我不认为我需要用来用它来实现这个,但同时,我真的很高兴我得知我所做的事。入门写作内联GCC ASM是最难的部分,但是一旦我得到它的挂起,它就开始变得更容易。

尽管如此,开始并不容易。事实证明,我可以通过谷歌找到的大多数ASM知识都是如下所示:真正的旧,英特尔语法和32位系统。请记住,在我们的方案中,我们需要文件在64位系统上使用GCC编译,没有对编译器标志的任何特殊修改,因此没有特殊的编译标志,我们也不能包含任何自定义链接步骤和我们想要在&amp inline inline使用gcc; t语法。我的大部分时间都花在寻找有关64位系统的现代装配的信息!也许我的Google-fu缺乏:)这部分几乎都是所有的审判和错误。我的目标只是使用Write Syscall打印“Hello World!”使用GCC内联ASM到屏幕,为什么这么难?对于想要学习如何执行此操作的人,我推荐以下网站:Linux Syscall列表,Inline ASM中的介绍,以及英特尔和AT&amp之间的差异

最终,我的亚军代码开始表格,我有一些似乎工作的代码!请记住,我的目标是生产一个主要的主要是亚马逊的一系列印刷你好世界。

void main(){__asm__(//打印hello world" movl $ 1,%eax; \ n" / * 1是写在64位* /&#34上写的syscall号码; movl $ 1, %EBX; \ n" / * 1是stdout,是第一个参数* /" movl $消息,%esi; \ n" / *将字符串的地址加载到第二个参数中* /" movl $ 13,%edx; \ n" / *第三个论点是打印* /" syscall; \ n" //呼叫退出(所以它DON' t尝试运行字符串HELLO WORLD)//也许我可以刚刚使用RET?" MOVL $ 60,%EAX; \ N"" XORL%EBX,%EBX ; \ n"" syscall; \ n" //在主要功能和#34内存储Hello World;消息:.ascii \"您好世界!\\ n \& #34;;"); }

$ gcc -wall asm_main.c -o secondasm_main.c:1:6:警告:返回类型'main'不是'int'[-wmain] void main(){^ $ ./ secondhello world!

欢呼!它打印!让我们立即查看十六进制的编译代码,它应该与我们写的ASM代码一对一匹配。我继续前进,突破了对侧面的评论发生的事情。

(GDB)禁用主汇编器代码的主台:0x00000000004004ED< + 0&gt ;: push%rbp;编译器插入0x00000000004004EE< + 1&gt ;: mov%rsp,%rbp 0x00000000004004f1 + 4&gt ;: mov $ 0x1,%eax;它'我们的代码! 0x00000000004004f6 1 + 9计算值:MOV $为0x1,%EBX 0x00000000004004fb 1 + 14计算值:MOV $ 0x400510,%ESI 0x0000000000400500 1 + 19计算值:MOV $ 0xd中,%EDX 0x0000000000400505 1 + 24计算值:系统调用0x0000000000400507< + 26&gt ;: mov $ 0x3c,%eax 0x00000000000040050c< + 31&gt ;: syscall 0x000000000000400550&gt ;: rex.w; String Hello World 0x0000000000400511< + 36&gt ;: gs;它与0x0000000000400512自0x0000000000400512以来的乱码< + 37>:Insb(%dx),%es:(%rdi);它' s不是真正的asm 0x0000000000400513< + 38&gt ;:nerb(%dx),%es:(%rdi);所以它不能' t为0x0000000000400514< + 39&gt ;: OUTSL%DS:(%RSI),(%DX);拆卸0x0000000000400515 1 + 40计算值:和%分升,0x6f(%RDI)0x0000000000400518 1 + 43计算值:JB 0x400586 0x000000000040051a 1 + 45计算值:和%ECX,%FS:(%RDX)0x000000000040051d 1 + 48> :POP%RBP;编译器插入0x000000000040051e< + 49&gt ;:汇编程序转储的retqend。

这对我来说看起来像一个运作的主要态度!现在让我们去抓住它的十六进制内容,并将其转储为一个字符串,看看是否有效。我们可以再次使用GDB从Main获取十六进制。我愿意猜到必须有更好的方法,所以也许有人可以发表评论,让我知道:)我做的方式是加载GDB并像这样打印十六进制。上次我们拆卸主要,我们看到它是49个字节长,所以可以使用dump命令将十六进制保存到文件:

#示例如何打印十六进制(GDB)X / 49xb main0x4004ed< main&gt ;: 0x55 0x48 0x89 0x05 0xb8 0x01 0x00 0x0x4004f5< main + 8&gt ;: 0x00 0xbb 0x01 0x00 0x00 0x00 0x 0x100x4004fd< main + 16&gt ;: 0×05 0×40 0×00 0xba 0X0D 0×00 0×00 0x000x400505<主+ 24计算值:为0x0F为0x05 0xb8为0x3C 0×00 0×00 0×00 0x310x40050d<主+ 32计算值:位于0xDB为0x0F为0x05 0x48 0x65 0x6c 0x6c 0x6f0x400515<主+ 40计算值:0×20 0×57 0x6f 0x72 0x6c 0x64 0x21 0x0a0x40051d< main + 48&gt ;: 0x5d#示例如何将其保存到文件(gdb)转储内存hex.out main main + 49

现在我们有十六进制转储,我们可以将它们全部转换为整数我所知道的最简单的方式,它正在使用Python。在Python 2.6和2.7中,您可以使用以下内容将其转换为可供我们使用的方便int。

>>>进口阵列>>> hex_string =" 554889E5B80100000000BB01000000BE10054000BA0D000030F05B83C64210A5D" 。解码("十六进制和#34;)>>大批 。阵列(' b' hex_string)阵列(' b' [85,72,137,229,184,1,0,0,0,187,1,0,0,0 ,0,190,16,5,64,0,186,13,0,0,0,15,5,184,60,0,0,0,0,49,219,15,5,72,101,108 ,108,111,32,87,111,114,108,100,33,10,93])

我认为,如果我的bash foo和unix的知识更大,我可以找到更简单的方法来做这件事,但谷歌曲的东西像“十六进制编译的函数”这样的事情返回有关如何以各种语言打印十六进制的几个问题。无论如何,我们现在都有逗号分隔的函数数组,所以让我们把它放在一个新文件中,看看它是否有效!我继续评论每个不同的价值观的意思。

char main [] = {85,// push%RBP 72,137,229,// MOV%RSP,%RBP 184,1,0,0,0,0,// mov $ 0x1,%eax 187,1,0 ,0,0,// mov $ 0x1,%ebx 190,16,5,64,6,0,// mov $ 0x400510,%ESI 186,13,0,0,0,0,// mov $ 0xd,%EDX 15 ,5,// syscall 184,60,0,0,0,// mov $ 0x3c,%eax 49,219,// xor%ebx,%ebx 15,5,// syscall // hello world!\ n 72,101,108,108,111,32,87,111,114,108,100,33,10,//弹出%RBP 93 // RETQ};

$ gcc -wall compiled_array_main.c -o第三个媒体piled_array_main.cain.c:1:6:警告:'main'通常是函数[-wmain] char main [] = {^ $ ./thirdsegation故障

赛格虚假!我究竟做错了什么?是时候再次启动GDB并试图查看错误是什么。由于主要不再是一个功能,我们不能简单地只使用Break Main来在那里设置一个断点。相反,我们可以使用break _start在调用libc运行时启动的方法中获得一个断点(反过来呼叫主),我们可以看到我们传递到__libc_start_main的地址:

$ GDB ./third(GDB)破_start(GDB)运行(GDB)布局ASM┌────────────────────────────── ────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── >│0x400400< _ _start> XOR%EBP,%EB​​P││0x400402< _ _ _ _start + 2> MOV%RDX,%R9││0x400405< _start + 5> POP%RSI││0x400406< _start + 6> MOV%RSP,%RDX││0x400409< _ _start + 9> $ 0xfffffffffffffffff0,%rsp││0x40040d< _start + 13>推动%rax││0x40040e< _ _start + 14>按%RSP││0x40040f< _ _start + 15> MOV $ 0x400560,%r8│0x400416< _ _start + 22> MOV $ 0x4004f0,%rcx││0x40041d< _start + 29> MOV $ 0x601060,%RDI││0x400424< _start + 36> CallQ 0x4003E0< __ libc_start_main @ plt> │

从测试中,我发现按%RDI推出的值是主要的位置,但似乎这一次似乎有些东西。挂断,它在.data部分放在.data部分!早些时候我提到了.Text是什么是ReadOnly可执行代码进入的地方和.data是不可执行的读/写值的地方!代码正在尝试运行标记为不可执行的内存,这是SEGFault的原因。我应该如何说服编译器,即我的“主要”属于.text?!好吧,我的搜索变空,我相信这是路的尽头。是时候称之为一夜,我的冒险失败了。

但是,在没有找到解决方案的情况下我无法睡觉。在我发现一个非常明显而简单的解决方案,我继续搜索和搜索一下我在堆栈溢出的帖子上丢失了URL的一个非常明显和简单的解决方案。我所要做的就是声明的主要功能是Const将其更改为Const Char Main [] = {是我所需要的只是在正确的部分中得到它,所以让我们再试一次编译。

$ gcc -wall const_array_main.c -o spoleconst_array_main.c:1:12:警告:'main'通常是一个函数[-wmain] const char main [] = {^ $ ./fourthsl)�1�h�� H�.

ack!它现在在做什么!时间再次gdb,看看发生了什么:

所以看着代码我们可以看到主要的地址是在ASM中为_START在我的机器上看起来像我的机器Mov $ 0x4005a0,%rdi我们可以用它来通过做断裂来设置一个断点* 0x4005a0然后继续执行C:

(GDB)断开* 0x4005a0(gdb)c(gdb)x / 49i $ pc#$ pc是当前执行指令... 0x4005a4< main + 4&gt ;: mov $ 0x1,%eax 0x4005a9< main + 9> :MOV $ 0x1,%EBX 0x4005ae< main + 14&gt ;: mov $ 0x400510,%ESI 0x4005b3< main + 19&gt ;: mov $ 0xd,%edx 0x4005b8< main + 24&gt ;: syscall ...

我剪掉了一些并不重要的装配。如果您没有注意到出错,则推动打印的地址(0x400510)不是我们存储字符串“Hello World!\ n”的地址(0x4005C3)!它实际上仍然指向原始编译的可执行文件中的计算位置,而不是使用相对寻址打印它。这意味着我们需要修改装配代码,以便加载相对于当前地址的字符串的地址。正如它所致,在32位代码中完成,但谢天谢地难以使用64位ASM,所以我们可以让LEA指令更轻松。

void main(){__asm__(//打印hello world" movl $ 1,%eax; \ n" / * 1是write * /&#34的Syscall号码; MovL $ 1,%EBX; \ n" / * 1是stdout,是第一个参数* ///#34; movl $消息,%esi; \ n" / *将字符串的地址加载到第二个参数中* ///相反,使用它来将字符串//的地址从当前的指令和#34加载到16个字节; Leal 16(%EIP),%ESI; \ N"" MOVL $ 13,%EDX; \ N" / *第三个论点是打印* /" syscall; \ n" //呼叫退出(所以它并尝试运行字符串Hello World //也许我可以刚刚使用RET而不是#34; MOVL $ 60,%EAX; \ N"" XORL%EBX,%EBX; \ N"" SYSCALL; \ N" //将Hello World存储在主要功能和#34内;消息:.ascii \"您好世界!\\ n \";");}

改变了代码,以便您可以看到它。编译代码并检查它是否有效:

$ gcc -wall actival_str_asm.c -o fifthrelive_str_asm.c:1:6:警告:返回类型'main'不是'int'[-wmain] void main(){^ $ ./fifthello世界!

现在我们可以使用前面讨论的相同技术作为整数阵列将十六进制值提取。但这一次,我想通过使用INTS给我的完整4个字节来使它更加伪装和棘手。我们可以通过将信息作为int打印出来,而不是将十六进制转储到文件,然后将其复制到程序中。

GDB ./fifth(GDB)X / 13dw main0x4004ed<主计算值:-443987883 440 113408 -19226296320x4004fd<主+ 16计算值:4149 899584 84869120 155440x40050d<主+ 32计算值:266023168 1818576901 1461743468 16848287830x40051d<主+ 48> :-1017312735

我选择了第13号,因为主要是49字节长,49/4轮最多13轮才能安全。由于我们早期退出函数,它不应该有所作为。现在,剩下的就是将其复制并粘贴到我们的Compled_array_main.cn中并运行它。

Const Int Main [] = { - 443987883,440,113408,1922629632,4149,899584,84869120,15544,266023168,1818576901,1818576901,1461743468,168482878,1684828783, - 1017312735}; $ gcc -wall final_array.c -o sixthfinal_array.c:1:11:警告:'main'通常是函数[-wmain] const int main [] = {^ $ ./sixthhello世界! 所有这次我们一直忽略了关于主要不是函数的警告消息:) 当我的同事转向一个看起来的作业时,我猜这一切都会发生这种情况,这是他们将为不好的编码风格开除积分,并没有说它。