I have this piece of ASM:

section .text
    global _start

_start:
    xor  eax, eax
    push eax         ; 0 to finish the /bin//sh string
    push 0x68732f2f  ; //sh
    push 0x6e69622f  ; /bin
    mov  ebx, esp
    mov  al, 0xb
    int  0x80

Which works fine if I do:

$ nasm -f elf shellcode.asm && ld -o shellcode shellcode.o
$ ./shellcode
$ # the new shell

But now, with the hex translation:

$ objdump -s shellcode

shellcode:     file format elf32-i386

Contents of section .text:
  8048060 31c05068 2f2f7368 682f6269 6e89e3b0  1.Ph//shh/bin...
  8048070 0bcd80                               ...

Used here:

const char shellcode[] = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80";

int main(){
    (*(void(*)()) shellcode)();
    return 0;
}

The result is:

$ gcc -o shellcode shellcode.c
$ ./shellcode
Segmentation fault

I tried to practice my ASM and re-create the one provided here. The difference, which I can't explain, is that on his payload, he's pushing the an extra 0 and the address of the /bin//sh string into the stack.

I though that because I was using the fastcall convention, I didn't have to setup the stack, but apparently there's more going on here.

Does anyone know why the shellcode refuses to run and why setting the stack changes "correct" it?


Edits:

When I try to run the program from gdb I have the following result:

$ gdb ./shellcode
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
0x080484b3 in shellcode ()

And here's the readelf command output:

$ readelf -l -S shellcode
There are 30 section headers, starting at offset 0x1150:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000020 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          080481cc 0001cc 000040 10   A  6   1  4
  [ 6] .dynstr           STRTAB          0804820c 00020c 000045 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          08048252 000252 000008 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         0804825c 00025c 000020 00   A  6   1  4
  [ 9] .rel.dyn          REL             0804827c 00027c 000008 08   A  5   0  4
  [10] .rel.plt          REL             08048284 000284 000010 08   A  5  12  4
  [11] .init             PROGBITS        08048294 000294 000023 00  AX  0   0  4
  [12] .plt              PROGBITS        080482c0 0002c0 000030 04  AX  0   0 16
  [13] .text             PROGBITS        080482f0 0002f0 000192 00  AX  0   0 16
  [14] .fini             PROGBITS        08048484 000484 000014 00  AX  0   0  4
  [15] .rodata           PROGBITS        08048498 000498 00001c 00   A  0   0  4
  [16] .eh_frame_hdr     PROGBITS        080484b4 0004b4 00002c 00   A  0   0  4
  [17] .eh_frame         PROGBITS        080484e0 0004e0 0000b0 00   A  0   0  4
  [18] .init_array       INIT_ARRAY      08049f08 000f08 000004 00  WA  0   0  4
  [19] .fini_array       FINI_ARRAY      08049f0c 000f0c 000004 00  WA  0   0  4
  [20] .jcr              PROGBITS        08049f10 000f10 000004 00  WA  0   0  4
  [21] .dynamic          DYNAMIC         08049f14 000f14 0000e8 08  WA  6   0  4
  [22] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4
  [23] .got.plt          PROGBITS        0804a000 001000 000014 04  WA  0   0  4
  [24] .data             PROGBITS        0804a014 001014 000008 00  WA  0   0  4
  [25] .bss              NOBITS          0804a01c 00101c 000004 00  WA  0   0  1
  [26] .comment          PROGBITS        00000000 00101c 00002b 01  MS  0   0  1
  [27] .shstrtab         STRTAB          00000000 001047 000106 00      0   0  1
  [28] .symtab           SYMTAB          00000000 001600 000430 10     29  45  4
  [29] .strtab           STRTAB          00000000 001a30 00024e 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

Elf file type is EXEC (Executable file)
Entry point 0x80482f0
There are 9 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
  INTERP         0x000154 0x08048154 0x08048154 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x00590 0x00590 R E 0x1000
  LOAD           0x000f08 0x08049f08 0x08049f08 0x00114 0x00118 RW  0x1000
  DYNAMIC        0x000f14 0x08049f14 0x08049f14 0x000e8 0x000e8 RW  0x4
  NOTE           0x000168 0x08048168 0x08048168 0x00044 0x00044 R   0x4
  GNU_EH_FRAME   0x0004b4 0x080484b4 0x080484b4 0x0002c 0x0002c R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO      0x000f08 0x08049f08 0x08049f08 0x000f8 0x000f8 R   0x1

 Section to Segment mapping:
  Segment Sections...
   00
   01     .interp
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
   04     .dynamic
   05     .note.ABI-tag .note.gnu.build-id
   06     .eh_frame_hdr
   07
   08     .init_array .fini_array .jcr .dynamic .got

Comparison with this shellcode:

Here're the differences:

Hamza (working)     |   me

ASM

xor    eax, eax     |   xor    eax, eax
push   eax          |   push   eax
push   0x68732f2f   |   push   0x68732f2f
push   0x6e69622f   |   push   0x6e69622f
mov    ebx, esp     |   mov    ebx, esp
push   eax          |
push   ebx          |
mov    esp, ecx     |
mov    al, 0xb      |   mov    al, 0xb
int    0x80         |   int    0x80

Stack before the interrupt (each line is a word)

&(/bin//sh)         | /bin
0                   | //sh
/bin                | 0
//sh                |
0                   |

Registers before the interrupt

eax    0xb          |   0xb
ebx    &(/bin//sh)  |   &(/bin//sh)
ecx    esp          |   ?

As you can see, the stack changes by having the string's address and an extra 0, and his ecx register is set to the last esp value. And those differences make his shellcode to work (both direcly with nasm & ld and inside the C program, without any change).


Edit:

Some progress, there's one extra instruction when the payload run within the C program:

(gdb)  x/14i 0x80484a0
   ...
   0x80484b3 <shellcode+19>:    mov    $0xb,%al
   0x80484b5 <shellcode+21>:    int    $0x80
   0x80484b7 <shellcode+23>:    add    %al,(%ecx)

While I don't know (yet) why the ecx register is incremented with the return value of the interrupt, clearing the register with xor ecx, ecx fixes the issue.

Here's the working asm:

section .text
    global _start

_start:
    xor  eax, eax
    push eax
    push 0x68732f2f
    push 0x6e69622f
    mov  ebx, esp
    xor  ecx, ecx
    mov  al, 0xb
    int  0x80
  • possibly your string bin,sh doesnt have a null terminator and push 0 is fulfilling it – blabb Sep 2 '17 at 17:17
  • afaik it does, that's why I push eax the first time... – nobe4 Sep 2 '17 at 17:23
  • is your data section executable? post output of readelf -l -S.What does gdb say when you run the compiled program under it? – Igor Skochinsky Sep 2 '17 at 18:00
  • Hey @IgorSkochinsky: see my edit. It seems that my .data is writable and allocable but not executable. Could it be it? Also when I try to readelf from the asm-generated executable, I only have the .text header that is executable. – nobe4 Sep 2 '17 at 18:24
  • so according to gdb it does run. what do you mean by "setting the stack changes "correct" it"? – Igor Skochinsky Sep 2 '17 at 19:11
up vote 6 down vote accepted

The problem with you shellcode and the why it differs if you run from C program or not is the initial values.

The registers that are used when executing execv (second page) are:

  • eax = 0x0b
  • ebx = ptr to filename
  • ecx = ptr to argv
  • edx = ptr to environment variables

But actually the important one are only ebx and eax and the two other we can nullify.

If you run your shellcode only the default value for the registers is 0x0 so we are getting nullification of ecx & edx for free.

enter image description here

it's not the case when you execute your shellcode from C program.

enter image description here

As you can see registers has already some initial values so you need to prepare them correctly. This is why adding xor ecx,ecx fixes the issue.

As for the example that you took from ecx is assigned to stack pointer but on the stack there's a value that points to "/bin/sh" which is also ok (and probably even better than 0x00)

Okay, so according to gdb the shellcode does run, so it's probably placed into .text. You should try to step through the shellcode (e.g. using stepi) to see on what instruction it faults.

  • OK I did that and there is an extra instruction on the payload, I'll post an update. – nobe4 Sep 2 '17 at 21:03

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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