DISCLAIMER: accessing or spying on networks without permission to do so is illegal in many jurisdictions. The author does not condone or encourage anyone to break laws. And should this article inspire you to become a cyber-crimefighter and you get caught and killed… well, that’s not a bad way to go.
Agent, we have a mission! The bad guys seem to have set up a server where they are discussing their secrets. We can’t risk being caught and exposed, so you’ll have to design an automated robot to do the job. Here’s the task:
- Recon (snoop on the network, to learn the protocol)
- Infiltrate (connect to the server)
- Put on a disguise (respond to events / use the Perl 6 ecosystem)
- Send regular reports to the agency (timed events)
1) Recon (snoop on the network, to learn the protocol)
The bad guys are using an IRC server for communication. Unfortunately, our Lab did not have the time to do the research, so we’ll have to go raw. You’ll need any IRC Client and something that can snoop on the network traffic. We have preliminary results using XChat and WireShark, see if you can replicate them.
Fire up WireShark and enable listening on your network device, on my machine it’s named eth2
(and I had to start Wireshark as root
, to get permissions to capture). Go to Capture -> Interfaces and click Start button for the appropriate interface:
Using your IRC client, now connect to the IRC server the bad guys are using—which is irc.freenode.net
on port 6667
—and join a test channel, say, #perl6-recon
. Once that is done, click the Stop Running Live Capture button in Wireshark.
We’re done collecting our data, Agent. Let’s take a look at what we got. Type tcp.port == 6667
in the filter field:
We want to figure out how to make our robot do what we’ve just done: connect to the server and join a channel. Sort the captured data by time and look for what the client is sending to the server. We’ll want to send the same thing:
Ignoring other chatter, it seems we should be successful if we send the following data to the server:
NICK Perl6NotABot
USER Perl6NotABot Perl6NotABot irc.freenode.net :Not a bot
JOIN #perl6-recon
Let’s do just that!
2) Infiltrate (connect to the server)
Fire up your favourite code editor and let’s write some Perl 6. It’s time to infiltrate the system!
1 my ( $nick, $channel ) = 'P6NotABot', '#perl6-recon'; 2 await IO::Socket::Async.connect('irc.freenode.net', 6667).then({ 3 given .result { 4 .print(qq:to/END/ 5 NICK $nick 6 USER $nick $nick irc.freenode.net :Not a bot 7 JOIN $channel 8 END 9 ); 10 react { whenever .Supply { .say } } 11 } 12 });
Try this code out on your computer. You should see a whole bunch of output from the server. Let’s break down what the code does:
On line 1 we simply store the name of the spy bot and the channel we’re joining into variables. Line 2 is more interesting: the IO::Socket::Async.connect('irc.freenode.net', 6667)
bit creates an asynchronous socket that attempts to connect irc.freenode.net
server on port 6667
. That returns us a Promise and since we really, really want that socket, we await
that promise’s completion right away. When that happens, it means we have a connected socket; we’re moved along to the .then
that is given a code block as an argument, which gets executed. Let’s take a closer look at that block (note: if you’re getting errors with line 10, your Rakudo is likely too old; upgrade or use .chars-supply
instead of .Supply
):
1 { 2 given .result { 3 .print(qq:to/END/ 4 NICK $nick 5 USER $nick $nick irc.freenode.net :Not a bot 6 JOIN $channel 7 END 8 ); 9 react { 10 whenever .Supply { 11 .say 12 } 13 } 14 } 15 }
Line 2 is a given
block with .result
as the given. It’s a bare method call, which means it’s called on the $_
topical variable, which in this case is our socket Promise, thus the given
block is operating on the result of that promise, which is our connected socket. Inside the given
block, on line 3, we have a .print
method executed, again on the $_
, which now is our async socket. The qq:to/END/ ... END
bit is a HEREDOC—a multi-line chunk of text—that all gets sent to the server. And that bit should look familiar: it’s the same stuff we snooped from the network when connecting using a regular IRC client. We’ve used our nickname on the USER
line a couple of times for it to serve us as both user name and anything else the server needs.
On line 9 we have a react
block that, unsurprisingly, reacts to events. We’re interested in when some stuff heads our way from the socket, which is why we ask to do stuff whenever
we have .Supply
. At the moment we simply ask it to print that stuff on screen with the .say
method called on the topical variable—this is all the output from the server you saw on screen if you ran this program—but let’s bring out bigger guns and do something more fun, shall we?
3) Put on a disguise (respond to events)
Agent, our spy bot needs to act as if it were a human! We can’t have it sit silently—the bad guys will know right away something is up. Since, for safety, we can’t respond to all queries ourselves, our robot needs to be smart enough to do it on its own. It seems a mammoth task to implement in such a short a time, but luckily, I have a contact who can assist us. They developed a super secret weapon called Text::Markov
. Head over to http://modules.perl6.org/ and see if you can locate that weapon. Got it? For the record, if you ever need quick assess to docs and specs, just use the /repo/
part of URL along with the name, for example: http://modules.perl6.org/repo/Text::Markov
Now, install Text::Markov
. You should be able to do so by running panda install
Text::Markov
command. This module will allow our spy bot to respond to any bad guys who attempt to talk to it. Responding means watching for something, so fire up your spy bot again and try talking in the channel its in. Then look at what the server is sending to the bot:
:Baddie!~Bad@localhost PRIVMSG #evil :I have a great plan to do evil stuff!
:Baddie!~bad@localhost PRIVMSG #evil :P6NotABot, hey, who are you?
We’ll guestimate that to send a message, we need to start our line with a colon, send our nick, followed by an exclamation sign, followed by user name, at sign, our hostname, word PRIVMSG
, channel name, and the message we want to send prefixed by another colon. And anything said in the channel follows the same format. First, let’s try watching for lines containing PRIVMSG
from the server and parse out the actual text said, which we’ll send right back. Here’s our code:
1 my ( $nick, $channel ) = 'P6NotABot', '#perl6-recon'; 2 await IO::Socket::Async.connect('irc.freenode.net', 6667).then({ 3 my $sock = .result; 4 $sock.print(qq:to/END/ 5 NICK $nick 6 USER $nick $nick irc.freenode.net :Really not a bot 7 JOIN $channel 8 END 9 ); 10 11 react { 12 whenever $sock.Supply { 13 .say; 14 15 /^':' <-[:]>+ 'PRIVMSG ' $channel ' :' (.+)/ 16 and $sock.print( 17 ":$nick!~$nick@localhost PRIVMSG $channel :You said $0" 18 ); 19 } 20 } 21 });
First, note how we got rid of the given
block and are simply storing the connected socket in the $sock
variable—this will let us access it more easily later in the code. In the whenever
block, along with printing all the data the server is sending us in the terminal (line 13), we’re also doing a regex match that looks for things that look like stuff said in our channel. The (.+)
portion captures what was said and we parrot it back into the socket. Since Perl short-circuits conditionals, simply using and
on line 16 will cause the $sock.print
code to execute only when the regex matches. Try this code out and talk in the channel. The bot should respond to you.
Now, simply parroting back what the bad guys are saying will get our spy-bot spotted and kicked out fast. We need to be smarter, and this is where Text::Markov
comes in. Looking at its documentation at http://modules.perl6.org/repo/Text::Markov, we see we need to feed it lines with .feed
method and we can get it to produce output via .read
method. The plan is this then: we’ll feed the Markov chain all the text messages that occur in the channel and make the bot respond to the channel only when someone addresses it by mentioning its name. The code becomes this:
1 use Text::Markov; 2 3 my ( $nick, $channel ) = 'P6NotABot', '#perl6-recon'; 4 5 my $mc = Text::Markov.new; 6 /\S/ and $mc.feed($_) for 'story.txt'.IO.lines; 7 8 await IO::Socket::Async.connect('irc.freenode.net', 6667).then({ 9 my $sock = .result; 10 $sock.print(qq:to/END/ 11 NICK $nick 12 USER $nick $nick irc.freenode.net :Really not a bot 13 JOIN $channel 14 END 15 ); 16 17 react { 18 whenever $sock.Supply { 19 .say; 20 if /^':' <-[:]>+ 'PRIVMSG ' $channel ' :' $<said>=(.+)/ { 21 $mc.feed( ~$<said> ); 22 $<said> ~~ /$nick/ and $sock.print( 23 ":$nick!~$nick@localhost PRIVMSG $channel " 24 ~ ":{$mc.read.substr(0, 200)}\n" 25 ); 26 } 27 } 28 } 29 });
Let’s break this down. On line 1 we’re use
ing the Text::Markov
module to include its functionality in our code. On line 5, we added a new variable $mc
and store the Text::Markov
object in it that we obtain by calling .new
method on Text::Markov
class. Now, normally the bare Text::Markov
will take a bit to “learn” new text and until it does so, it’ll do a lot of repeats. To prevent that, I saved a short detective story into a text file called story.txt
and on line 6 I’m reading all lines from that file and .feed
ing the Markov chain all lines that aren’t blank. Much of the following code is the same as before; let’s jump straight to line 20.
Notice the slight change in the regex: I’ve used $<said>=(.+)
instead of bare (.+)
, so that we could have a meaningful name for the captured stuff instead of the cryptic $0
. On line 21, I’m feeding the match into the Markov chain (the ~
before the variable forces it into a string). Then on line 22, I have another regex that checks whether the text that was said contains the nick of the bot. If the regex matches, our program proceeds to $sock.print
portion of line 22 and outputs the message generated by the Text::Markov
module. Line 23 has the prefix the server expects that we’ve been using. On line 24, the ~ is the string concatenation operator. Inside that string, however, notice how we’re actually executing some Perl 6 code! It’s the curly braces { }
that allow us to do so. I’m getting a line of text via .read
method on our Markov object, and then I’m shortening it to at most 200 characters with .substr
method call, since if it’s too long, the IRC server will kick our bot out.
Try this code out (remember to create a file called story.txt
and fill it with some text). Try addressing the bot by mentioning its nickname. It should produce some interesting text. You can also try commenting out line 6 and trying to address the bot then. Notice how without having fed the Markov chain some content, the results it produces are uninspiring.
4) Send regular reports to the agency (timed events)
Responding to users on the network is great and all, but we have a job to do, Agent. As a proof of concept, we’ll simply regularly append a time-stamped string into a file, to notify the agency that the bot is still alive and well. Let’s take a look at the code for that:
1 use Text::Markov; 2 3 my ( $nick, $channel ) = 'P6NotABot', '#perl6-recon'; 4 5 my $mc = Text::Markov.new; 6 /\S/ and $mc.feed($_) for 'story.txt'.IO.lines; 7 8 await IO::Socket::Async.connect('localhost', 6667).then({ 9 my $sock = .result; 10 $sock.print(qq:to/END/ 11 NICK $nick 12 USER $nick $nick irc.freenode.net :Really not a bot 13 JOIN $channel 14 END 15 ); 16 17 Supply.interval( 5 ).tap({ 18 spurt 'report.txt', "[{DateTime.now}] Still alive!\n", :append; 19 }); 20 21 react { 22 whenever $sock.Supply { 23 .say; 24 if /^':' <-[:]>+ 'PRIVMSG ' $channel ' :' $<said>=(.+)/ { 25 $mc.feed( ~$<said> ); 26 ~$<said> ~~ /$nick/ and $sock.print( 27 ":$nick!~$nick@localhost PRIVMSG $channel " 28 ~ ":{$mc.read.substr(0, 200)}\n" 29 ); 30 } 31 } 32 } 33 });
If you’re not seeing much difference, it’s because there isn’t! Lines 17–19 is all we added. We’re .tap
ping a Supply
that emits an event every five seconds. The code block we give to .tap
uses spurt
in :append
mode to append a string to file named report.txt
. The string it spurts uses DateTime
type’s method .now
to obtain the time stamp. And there you have it—doing stuff every five seconds!
Conclusion
You’ve now seen how easy it is to do event loops in Perl 6, connect to a network resource, read from and write to files, as well as use code libraries developed by third parties. In just 33 lines of liberally-written code, we have something that connects to an IRC server and respond to specific messages, while doing work in intervals as well.
There’s more evil in the world, Agent! Be sure to read all the documentation referenced throughout this blog post. See if you can improve your robot.
Together with the power of Perl 6… We’ll save the world.