20150501: Hacky Easter 2015 - writeup

tags: netsec, ctf, hacking-lab, hacky, easter, 2015, he2015
QR code decoder
all keys in lowercase only
  1. Puzzword
  2. Search out the password in the image
    
    • anagram(missing letters {a,c,e,h,k,r,z}) = hackerz
    New York Times, March 12 2015
    An Easter Egg of the famous Hacky Easter white-hat hacking competition, was leaked last Tuesday by the famous hacker group "Bunnonymous". Experts confirmed its authenticity, but could not crack the code hidden it in yet.
    
    Can you do better?
    
    • QR code != img, but = HTML table
    • clue in puzzle title: "media" ie. CSS @media
    • inspect CSS styling in HTML src style: class='h5' styled differently when page is printed
    • …
      .x5 { height: 4%; width: 4%; background: #fff;}
      @media print {
      	…
      	.x5 { height: 4%; width: 4%; background: #000;}
      }
    • simply print the page to reveal QR code for this level
    You intercepted a message sent by a nerd from the office nearby. On first sight, there's nothing suspicious in it. However, you are almost sure that something secret must be hidden in it. Can you find out what?
    
    legostego.lxf
    
    • fmt: .lxf ‐ open-with: LEGO Digital Designer
    • obs. 3 layers present
    • QR code in *mid* layer ie. strip off top layer
    @ /assets/www/challenge04.html:
    Calculate/convert the following values, and sort them in ascending order
    
    1. 10101111000
    2. 8 YiB [bytes]
    3. Speed of Light [m/s]
    4. 127.0.0.1 as integer
    5. ΠΠ
    6. java.lang.Integer.MAX_VALUE
    7. 13 MiB [bytes]
    8. 220
    9. ZmlmdHk=
    10. sqrt(1296)
    11. 3032408
    12. Middle C [Hz]
    1. bin2dec: 1400
    2. 8 yobibytes = 9.67140656 × 1024 bytes
    3. 299792458 m/s
    4. 2130706433
    5. ≈ 36.4621596072
    6. 2147483647
    7. 13631488 bytes
    8. 1048576
    9. base64_decode("ZmlmdHk=") = fifty = 50
    10. 36
    11. 100000
    12. 261.626 Hz
    • xs = [1400,9671406559999999577423872,299792458,2130706433,36.4621596072,2147483647,13631488,1048576,50,36,100000,261.626]
    • sorted(zip(xs,xrange(1,len(xs)+1)))
    • sorted in ascending order:
      1. 36
      2. 36.4621596072
      3. 50
      4. 261.626
      5. 1400
      6. 100000
      7. 1048576
      8. 13631488
      9. 299792458
      10. 2130706433
      11. 2147483647
      12. 9.67140656 × 1024
    • extract original indices:
    • map(lambda (x,y): y, sorted(zip(xs,xrange(1,len(xs)+1))))
    • var h = "10,5,9,12,1,11,8,7,3,4,6,2";
    • for (var i=0; i<10000; i++) {h = CryptoJS.SHA1(h);} decryptScrambledEggWithKey('' + h);
    In this challenge, you need to play with your phone a bit. Try to find out what controls the four bars, and make them reach the full width (all at the same time).
    
    • extract & decompile APK
    • @ /assets/www/challenge05.html:
      • requestLevels is invoked every 1000ms to query ps://sensors endpt
      • sensorFeedback(json) sets lvls by parsing JSON response vars {l1,l2,l3,l4,k}
      …
      function requestLevels() {
      	window.location.href="ps://sensors";
      }
      function sensorFeedback(json) {
      	var jsonResp = JSON.parse(json);
      	setLevel(1, jsonResp.l1);
      	setLevel(2, jsonResp.l2);
      	setLevel(3, jsonResp.l3);
      	setLevel(4, jsonResp.l4);
      	if (jsonResp.k) {
      		decryptScrambledEggWithKey(jsonResp.k);
      		clearInterval(intervalId);
      	}
      }
      var intervalId = setInterval(requestLevels, 1000);
      …
      
    • ps://sensors is mentioned @/src/ps/hacking/hackyeaster/android/c.java
    • …
      	if ("ps://sensors".equals(s))
      	{
      		Activity.e(a);
      		return true;
      	}
      …
      
    • static e(Activity a) is defined @ /src/ps/hacking/hackyeaster/android/Activity.java
    • private SensorManager g;
      …
      static void e(Activity activity)
      {
      	activity.d();
      }
      private void d()
      {
      	h = g.getDefaultSensor(1); //TYPE_ACCELEROMETER
      	i = g.getDefaultSensor(2); //TYPE_MAGNETIC_FIELD
      	g.registerListener(this, h, 0);
      	g.registerListener(this, i, 0);
      	…
      }
      public int c()
      {
      	Intent intent = registerReceiver(null, new IntentFilter("android.intent.action.BATTERY_CHANGED")); //current battery level
      	int i1 = intent.getIntExtra("level", -1);
      	int j1 = intent.getIntExtra("scale", -1);
      	if (i1 == -1 || j1 == -1)
      	{
      		return 50;
      	} else
      	{
      		return Math.round((i1 * 100) / j1); // to %
      	}
      }
      private void j()
      {
      	int i1 = 100;
      	if (l == null || m == null) goto _L2; else goto _L1
      _L1:
      	float af[] = new float[9];
      	if (!SensorManager.getRotationMatrix(af, new float[9], l, m)) goto _L2; else goto _L3
      _L3:
      	float af1[] = new float[3];
      	SensorManager.getOrientation(af, af1);
      	int j1 = 5 * (int)Math.ceil((double)((int)(System.currentTimeMillis() / 1000L) % 100) / 5D);
      	int k1 = Math.round(100F * (Math.abs(af1[1]) + Math.abs(af1[2])));
      	int l1;
      	int i2;
      	int j2;
      	int k2;
      	String s;
      	String s1;
      	MessageDigest messagedigest;
      	String s2;
      	if (k1 <= 20)
      	{
      		l1 = i1;
      	} else
      	if (k1 > 300)
      	{
      		l1 = 0;
      	} else
      	{
      		l1 = Math.round((100 * (300 - k1)) / 280);
      	}
      	i2 = Math.round(100F * Math.abs(af1[0]));
      	if (i2 <= 20)
      	{
      		j2 = i1;
      	} else
      	{
      		j2 = 0;
      		if (i2 <= 300)
      		{
      			j2 = Math.round((100 * (300 - i2)) / 280);
      		}
      	}
      	k2 = c();
      	Exception exception;
      	if (k2 < 95)
      	{
      		i1 = k2;
      	}
      	s = "";
      	if (i1 + (j2 + (j1 + l1)) != 400)
      	{
      		break MISSING_BLOCK_LABEL_246;
      	}
      	s1 = (new StringBuilder(String.valueOf(j1))).append("f").append(l1).append("u").append(j2).append("n").append(i1).toString();
      	messagedigest = MessageDigest.getInstance("SHA1");
      	messagedigest.update(s1.getBytes());
      	s2 = Base64.encodeToString(messagedigest.digest(), 2);
      	s = s2;
      _L5:
      	a.loadUrl((new StringBuilder("javascript:sensorFeedback('{\"k\": \"")).append(s).append("\", \"l1\": ").append(j1).append(", \"l2\": ").append(l1).append(", \"l3\": ").append(j2).append(", \"l4\": ").append(i1).append("}')").toString());
      	l = null;
      	m = null;
      _L2:
      	return;
      	exception;
      	if (true) goto _L5; else goto _L4
      _L4:
      }
      public void onSensorChanged(SensorEvent sensorevent)
      {
      	if (sensorevent.sensor.getType() == 1) //TYPE_ACCELEROMETER
      	{
      		l = sensorevent.values;
      		g.unregisterListener(this, h);
      		h = null;
      	}
      	if (sensorevent.sensor.getType() == 2) //TYPE_MAGNETIC_FIELD
      	{
      		m = sensorevent.values;
      		g.unregisterListener(this, i);
      		i = null;
      	}
      	j();
      }
      …
      
    • Activity: e(a) → d() → callback-onSensorChanged() → j() → c() → ret
    • mapping: sensorFeedback({k: s, l1: j1, l2: l1, l3: j2, l4: i1}), where:
      • s = base64(SHA1(j1+"f"+l1+"u"+j2+"n"+i1))
    1. Current time
    2. Accelerometer
    3. Compass
    4. Battery percentage
    The Doc's in trouble again, and you must come to his rescue! As you jump into his time machine, you realize that a password is needed to start it. Just in that moment of despair, you receive an audio message from the Doc, through space and time:
    
    dah-dah-dit dit dah-dah-dah di-dah-dit dah-dah-dit dit dah-dah dah-di-dah-dit di-di-dah-dit di-dah-di-dit dah-di-dah-dah
    • Python Morse code decoder
    • #!/usr/bin/env python
      
      import sys
      import codecs
      
      if __name__ == "__main__":
      	morse_lookup = {"dah": "T", "dah-dah": "M", "dah-dah-dah": "O", "dah-dah-dit": "G", "dah-dah-dit-dah": "Q", "dah-dah-dit-dit": "Z", "dah-dit": "N", "dah-dit-dah": "K", "dah-di-dah-dah": "Y", "dah-di-dah-dit": "C", "dah-dit-dit": "D", "dah-dit-dit-dah": "X", "dah-dit-dit-dit": "B", "dit": "E", "dit-dah": "A", "dit-dah-dah": "W", "dit-dah-dah-dah": "J", "dit-dah-dah-dit": "P", "di-dah-dit": "R", "di-dah-di-dit": "L", "dit-dit": "I", "dit-dit-dah": "U", "di-di-dah-dit": "F", "dit-dit-dit": "S", "dit-dit-dit-dah": "V", "dit-dit-dit-dit": "H"}
      	ms = "dah-dah-dit dit dah-dah-dah di-dah-dit dah-dah-dit dit dah-dah dah-di-dah-dit di-di-dah-dit di-dah-di-dit dah-di-dah-dah"
      	print "".join(morse_lookup[m] for m in ms.split()).lower()
      
    • georgemcfly
    • not quite done yet! enter georgemcfly into Egg-O-Matic performs a HTTP GET request to http://hackyeaster.hacking-lab.com/hackyeaster/time?m=4&k=georgemcfly
    • rendering:
    • so need to modify HTTP GET param m, according to given hint in img, by +3 ie. m=7
    • curl -sL "http://hackyeaster.hacking-lab.com/hackyeaster/time?m=7&k=georgemcfly" | jq -r '.egg' | base64 -d
    This egg is hidden in a street-view like viewer. Peek around the area and find it!
    
    • extract & decompile APK reveals:
    • QR code @ /res/raw/quito2_u.jpg
    This egg is hidden within an online spreadsheet. Go find it's URL, and extract the egg out of it.
    
    Spreadsheet ID:
    
    1QPkfrnSVRAhQKL7AZx_HVXWrRXDvwCnVX2ih0jYp1CA
    • @ https://docs.google.com/spreadsheets/d/1QPkfrnSVRAhQKL7AZx_HVXWrRXDvwCnVX2ih0jYp1CA
    • 019322411206192510235121371516171814218224
      31111100101111101101001000
      191101001111101111001001011
      100101010100100000101000000
      11111011101111001111001011
      211111110111111101011101001
      221101100111001011111110001
      80000000000000010011000000
      160100110110000010011111000
      41111000111111101011011000
      21000000111100111010000000
      120101100100100100111100000
      111000000101111111010011111
      250111011111001011100001011
      240000010111000011000111000
      71111111111011011101001011
      200000100101000101101001000
      51111100101011011101001000
      180010100011110000011001000
      141011111100110010011110111
      131000010101101101010101101
      171100111111101011101101000
      230111010111101001110010000
      151011100101011001110011000
      61000100111000001010000000
      90111110110101011000110111
    • task: sort row + col # (highlighted in red), together with their associated row/col entries respectively, in asc order
    • copy/paste HTML tbl above into lvl08-grid.txt
    • #!/usr/bin/env python
      #-*- coding: utf-8 -*-
      
      import sys
      import codecs
      from PIL import Image
      
      sys.stdout = codecs.getwriter("utf8")(sys.stdout)
      sys.stderr = codecs.getwriter("utf8")(sys.stderr)
      
      def parse(filepath):
      	row_idx, col_idx, grid = [], [], []
      	with codecs.open(filepath, "r", "utf-8") as f:
      		for i, line in enumerate(f):
      			line = line.strip()
      			if not line: continue
      			if i == 0:
      				col_idx = map(int, line.split("\t"))[1:]
      				col_idx = map(lambda i: i-1, col_idx)
      				length = len(col_idx)
      				grid = [[0 for x in xrange(length)] for x in xrange(length)]
      			else:
      				toks = map(int, line.split("\t"))
      				row_idx.append(toks[0]-1)
      				toks = toks[1:]
      				for j in xrange(length):
      					grid[i-1][j] = toks[j]
      	grid_tmp = [[0 for x in xrange(length)] for x in xrange(length)]
      	for i,c in enumerate(col_idx):
      		for j in xrange(length):
      			grid_tmp[j][c] = grid[j][i]
      	for i,r in enumerate(row_idx):
      		for j in xrange(length):
      			grid[r][j] = grid_tmp[i][j]
      	# for i in xrange(length):
      	# 	for j in xrange(length):
      	# 		print>>sys.stderr, str(grid[i][j])+"\t",
      	# 	print>>sys.stderr
      	return grid
      
      def enlarge(grid):
      	length = len(grid)
      	if length > 0: length = min(length, len(grid[0]))
      	grid_large = [[0 for x in xrange(length*length)] for x in xrange(length*length)]
      	for i in xrange(length):
      		for j in xrange(length):
      			for x in xrange(length):
      				for y in xrange(length):
      					grid_large[i*length+x][j*length+y] = grid[i][j]
      	return grid_large
      
      def save(grid, outfile):
      	length = len(grid)
      	if length > 0: length = min(length, len(grid[0]))
      	im1 = Image.new("RGB", (length,length))
      	pix1 = im1.load()
      	w, h = im1.size
      	for i in xrange(h):
      	    for j in xrange(w):
      	   		pix1[j,i] = tuple([(1-grid[i][j])*255]*3)
      	im1.save(outfile, outfile.split(".")[-1].upper())
      	return im1
      
      if __name__ == "__main__":
      	grid = parse("lvl08-grid.txt")
      	grid = enlarge(grid)
      	save(grid, "lvl08-grid.png")
      
    Egg number nine is hidden here in the app. You've already seen it, haven't you?
    
    Go catch it and squint like a fish.
    
    • img @ /res/drawable/id_launcher.png
    • correct fisheye by following a GIMP tutorial
    In order to get this egg, you need to search on the web site. Rumors say that Thumper himself has bagged it.
    
    • under "Eggs"
    • (change) name to Thumper
    You caught a Thunderbird mail box, which contains an easter egg. Go find it!
    
    Mail.zip
    
    • in file Inbox, search for attachment:"signature.zip"
    • carve signature.zip via its base64-encoding
      pbpaste | base64 -d >signature.zip
    • egg can be found inside archive: unzip -l signature.zip
    • Archive:  signature.zip
        Length      Date    Time    Name
      ---------  ---------- -----   ----
          64915  09-17-2014 20:20   egg_11.png
      ---------                     -------
          64915                     1 file
      

    This is your chance to become a Certified Easter Hacker (CEH)! Complete the following little test. Passing score is 100%

    Question 1
    What is the name of the popular port scanner, implemented by Fyodor?

    Question 2
    In the context of PKI systems, the shorthand "CRL" stands for "certificate __________ list".

    Question 3
    A group of 100 people plans to use symmetric encryption for secure communication. How many keys are needed to let everybody communicate with each other?

    Question 4
    Which hash sizes are supported by the SHA2 family? Choose two!
    192 bit
    384 bit
    448 bit
    512 bit

    Question 5
    Which port number is used by Kerberos?

    • given form is messed up, eg.
      • Q2: specifies numeric-only
      • Q3: max-width = 3
      • Q4: uses radio btn; only allowed ≤ 1 option
      • Q5: correct port # is not one of the choices
    • correct ans to given qns
      1. nmap
      2. revocation
      3. 100*(100-1)/2 = 4950
      4. 384, 512
      5. 88
      6. + <button type="submit" name="success" value="false">Submit Test</button>
    • curl -sL -X POST "http://hackyeaster.hacking-lab.com/hackyeaster/ceh" -d "q1=nmap&q2=revocation&q3=4950&q4=384&q4=512&q5=88&success=true" returns:
    • …
      	Congratulations, you passed!!!
      	<div class="eggImage" style="background-image: url('./images/egg_12_an8snbui1FeLO3Ugw89N.png')"></div>
      …
      
    • wget "http://hackyeaster.hacking-lab.com/hackyeaster/images/egg_12_an8snbui1FeLO3Ugw89N.png"
    Welcome to Leet TV! Click the image below to play our trailer
    
    
    • extract stills from leettv.mp4@30fps: ffmpeg -i file/leettv.mp4 -vf fps=30 leettv@%d.png
    • decode QR codes in these imgs: zbarimg -q leettv@[0-9]*.png | sed 's|^QR-Code:||g' | sort | uniq >>leettv-qrcode.lst
    • note leettv-qrcode.lst entry: http://bit.ly/1BJENx8
    • http://hackyeaster.hacking-lab.com/hackyeaster/leettv_qbEtJZKLTLB3jByIWSpE.wav
    • exiftool leettv_qbEtJZKLTLB3jByIWSpE.wav
    • Title  : The Hint
      Comment: siht esrever ot yrt
      
    • echo -ne "siht esrever ot yrt" | rev says "try to reverse this"
    • so follow as instructed: sox leettv_qbEtJZKLTLB3jByIWSpE.wav leettv_qbEtJZKLTLB3jByIWSpE-rev.wav reverse
    • audio content: eight forty-two
    • so extract frame leettv.mp4@08:42: ffmpeg -i file/leettv.mp4 -ss 00:08:42.001 -vframes 1 leettv@0842.png
    Wise Rabbit says:
    
    An egg I give you for free,
    it's below, as you can see.
    But something got lost
    add a dimension you must!
    
    
    
    Your gallery needs some nice easter snapshots! What about a nice grasslands panorama, or a still life of a tomato?
    
    • lol...taking a photo using mobile device's camera creates:
    • extract & decompile APK
    • @ /src/ps/hacking/hackyeaster/android/Activity.java
    • …
      protected void onActivityResult(int i1, int j1, Intent intent)
      {
      	super.onActivityResult(i1, j1, intent);
      	if (i1 != 1336) goto _L2; else goto _L1
      _L1:
      	f = false;
      	if (j1 != -1 || intent == null || !"ps.hacking.zxing.client.android.SCAN".equals(intent.getAction())) goto _L4; else goto _L3
      _L3:
      	a(intent);
      _L6:
      	return;
      _L4:
      	a(2, "");
      	return;
      _L2:
      	if (i1 != 1337) goto _L6; else goto _L5
      _L5:
      	File file;
      	file = new File(Environment.getExternalStorageDirectory(), "HackyEaster");
      	if (!file.exists() && !file.mkdirs())
      	{
      		Toast.makeText(this, "Couldn't create folder on sdcard.", 0).show();
      	}
      	if (!file.exists() || intent == null || intent.getExtras() == null) goto _L6; else goto _L7
      _L7:
      	File file1;
      	Bitmap bitmap;
      	Bitmap bitmap1;
      	Canvas canvas;
      	int k1;
      	int l1;
      	long l2;
      	long l3;
      	long l4;
      	int i2;
      	file1 = new File(file, (new StringBuilder("Snapshot_")).append(e.nextInt()).append(".png").toString());
      	bitmap = (Bitmap)intent.getExtras().get("data");
      	bitmap1 = bitmap.copy(android.graphics.Bitmap.Config.ARGB_8888, true);
      	canvas = new Canvas(bitmap1);
      	k1 = bitmap.getHeight();
      	l1 = bitmap.getWidth();
      	l2 = 0L;
      	l3 = 0L;
      	l4 = 0L;
      	i2 = 0;
      _L8:
      	if (i2 >= l1)
      	{
      		int j2;
      		int k2;
      		String as[];
      		if (k1 > l1 && (double)l2 > 0.90000000000000002D * (double)(l3 + l4))
      		{
      			byte abyte2[] = Base64.decode(Base64.decode(j, 0), 0);
      			Bitmap bitmap4 = BitmapFactory.decodeByteArray(abyte2, 0, abyte2.length);
      			int k3 = bitmap4.getHeight();
      			canvas.drawBitmap(bitmap4, (l1 - bitmap4.getWidth()) / 2, k1 - k3 / 2, null);
      		} else
      		if (l1 > k1 && (double)l3 > 0.90000000000000002D * (double)(l4 + l2))
      		{
      			byte abyte1[] = Base64.decode(Base64.decode(j, 0), 0);
      			Bitmap bitmap3 = BitmapFactory.decodeByteArray(abyte1, 0, abyte1.length);
      			int j3 = bitmap3.getHeight();
      			canvas.drawBitmap(bitmap3, (l1 - bitmap3.getWidth()) / 2, -j3 / 2, null);
      		} else
      		{
      			byte abyte0[] = Base64.decode(k, 0);
      			Bitmap bitmap2 = BitmapFactory.decodeByteArray(abyte0, 0, abyte0.length);
      			int i3 = bitmap2.getHeight();
      			canvas.drawBitmap(bitmap2, (l1 - bitmap2.getWidth()) / 2, (k1 - i3) / 2, null);
      		}
      		try
      		{
      			FileOutputStream fileoutputstream = new FileOutputStream(file1);
      			bitmap1.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, fileoutputstream);
      			fileoutputstream.flush();
      			fileoutputstream.close();
      		}
      		catch (Exception exception) { }
      		as = new String[1];
      		as[0] = file1.toString();
      		MediaScannerConnection.scanFile(this, as, null, new e(this));
      		return;
      	}
      	j2 = 0;
      label0:
      	{
      		if (j2 < k1)
      		{
      			break label0;
      		}
      		i2++;
      	}
      	  goto _L8
      	k2 = bitmap.getPixel(i2, j2);
      	l2 += Color.red(k2);
      	l3 += Color.green(k2);
      	l4 += Color.blue(k2);
      	j2++;
      	break MISSING_BLOCK_LABEL_383;
      }
      …
      public void onCreate(Bundle bundle)
      	{
      		super.onCreate(bundle);
      		j = getString(0x7f080007);
      		k = getString(0x7f080008);
      		if (!isTaskRoot())
      		{
      			Intent intent = getIntent();
      			String s = intent.getAction();
      			if (intent.hasCategory("android.intent.category.LAUNCHER") && s != null && s.equals("android.intent.action.MAIN"))
      			{
      				super.finish();
      				return;
      			}
      		}
      		ps.hacking.hackyeaster.android.i.a(this);
      		a();
      		g = (SensorManager)getSystemService("sensor");
      		b();
      		a.setOnKeyListener(new a(this, this));
      	}
      …
      
    • this seems to be the routine in Activity.java responsible for the functionality here
    • j and k look particularly interesting
    • @ /src/ps/hacking/hackyeaster/android/R$string.java (inner class string within generated android.R)
    • public static final int ft = 0x7f080007;
      public static final int rick = 0x7f080008;
      
    • String constants for j=0x7f080007 and k=0x7f080008 are ft and rick respectively (rick = Rick Astley, whose face is on the photo taken)
    • time to lookup the (base64-encoded) string vals @ /res/values/strings.xml; just for ft only !
    • <string name="ft">  </string>
      
    • remember to base64 --decode 2x !
    • import java.io.*;
      import java.lang.*;
      import java.util.*;
      import org.apache.commons.io.*;
      
      class Challenge15 {
      	public static void main(String[] args) throws Exception
      	{
      		File file_ft= new File("file/lvl15-ft.base64");
      		String ft = FileUtils.readFileToString(file_ft);
      		byte[] decoded = org.apache.commons.codec.binary.Base64.decodeBase64(
      			org.apache.commons.codec.binary.Base64.decodeBase64(
      				ft.getBytes()
      			));
      		String filename = "lvl15-qr.png";
      		FileOutputStream output = new FileOutputStream(new File(filename));
      		IOUtils.write(decoded, output);
      		System.out.println("=> "+filename);
      	}
      }
      
    Ghosts only come out when it's dark...
    
    
    
    • turn off the lights: click on light bulb in far lower right corner @ webpage footer
    • JS code snippet implementing this behaviour:
    • function (){skelD=!skelD,skelD?($("#dark").show(),$("#chall16 img").attr("src","images/banner_challenge_16_dark.jpg"),$("#chall16 a").attr("href","challenge16-dark.html")):($("#dark").hide(),$("#chall16 img").attr("src","images/banner_challenge_16.jpg"),$("#chall16 a").attr("href","challenge16.html"))}
      
    • clicking on Challenge 16 now leads to challenge16-dark.html, containing another puzzle desc:
    • as given in img, key: spooky; using GOST Russian symmetric key block cipher (GOST 28147-89) in Cipher Block Chaining (CBC) mode of op on ciphertxt, joined as a single contiguous string
    • #!/usr/bin/env php
      
      <?
      	$key = "spooky"; while(strlen($key)<32) {$key.="\0";}
      	$ciphertxt = "d5++xytj6RiGwmqEecm63Kow7RZGAAHhVFsksHFuj/Anap7pWHDZ1XQw8DAApUENR5ExOGUKTzGOtvSAlCHkHq6NneL6ZUTXej8Taxz+kHK9w9U8dxTOSksZ4HKS2YYD";
      	$ciphertxt_b64 = base64_decode($ciphertxt);
      	$iv_size = mcrypt_get_iv_size(MCRYPT_GOST, MCRYPT_MODE_CBC);
      	$iv = ""; while(strlen($iv)<$iv_size) {$iv.="\0";}
      	$plaintxt = mcrypt_decrypt(MCRYPT_GOST, $key, $ciphertxt_b64, MCRYPT_MODE_CBC, $iv);
      	echo "key: $key".PHP_EOL;
      	echo "ciphertxt: $ciphertxt".PHP_EOL;
      	echo "plaintxt: $plaintxt".PHP_EOL;
      ?>
      
    • @ http://hackyeaster.hacking-lab.com/hackyeaster/images/egg_16_a3eIIACKSy02sJ6LxXeh.png
    Sharpen your eyes, and diff 2 img below
    
    
    
    
    • QR code is always the flag in these series of challenges;
    • so can guess must somehow convert 2 input img → single B/W img
    • normalise to B/W using transformation:
      • pix @ corresponding pos match = 0 ie. RGB(0,0,0) = black,
      • pix @ corresponding pos mismatch = 1 ie. RGB(255,255,255) = white
      #!/usr/bin/env python
      #-*- coding: utf-8 -*-
      
      import sys
      import codecs
      import operator
      import numpy as np
      from PIL import Image, ImageFilter
      
      def mk_agent_xor():
      	im1 = Image.open("img/difference1.bmp").convert("RGB")
      	#np.asarray(im1)
      	pix1s = im1.load()
      	im2 = Image.open("img/difference2.bmp").convert("RGB")
      	pix2s = im2.load()
      	w1, h1 = im1.size
      	for y in xrange(h1):
      		for x in xrange(w1):
      			pix1 = pix1s[x,y]
      			pix2 = pix2s[x,y]
      			if pix1 != pix2:
      				pix1s[x,y] = (255,255,255)
      			else:
      				pix1s[x,y] = (0,0,0)
      	im1.save("difference0.bmp")
      
    • XOR (as instructed) QR code w/ target , each found on one side of Agent's glasses, yields
    • #!/usr/bin/env python
      #-*- coding: utf-8 -*-
      
      import sys
      import codecs
      import operator
      import numpy as np
      from PIL import Image, ImageFilter
      
      def crop_agent_xor():
      	im = Image.open("difference0.bmp").convert("RGB")
      	#qr#
      	left, top, right, bottom = 245, 140, 280, 175
      	img = im.crop((left, top, right, bottom))
      	#img = im.filter(ImageFilter.{BLUR,CONTOUR,DETAIL,EDGE_ENHANCE,EDGE_ENHANCE_MORE,EMBOSS,FIND_EDGES,SMOOTH,SMOOTH_MORE,SHARPEN})
      	#img.thumbnail(sz)
      	#img.show()
      	img.save("img/lvl17-qr1.jpg")
      	#tgt#
      	left, top, right, bottom = 333, 140, 368, 175
      	img = im.crop((left, top, right, bottom))
      	img.save("img/lvl17-qr2.jpg")
      
      def mk_qr_code():
      	im1 = Image.open("img/lvl17-qr1.jpg").convert("RGB")
      	#im1.getcolors(), im1.format, im1.size, im1.mode
      	im2 = Image.open("img/lvl17-qr2.jpg").convert("RGB")
      	pix2s = list(im2.getdata())
      	w2, h2 = im2.size
      	pix2s = [pix2s[i*w2:(i+1)*w2] for i in xrange(h2)]
      	pix1s = im1.load()
      	w1, h1 = im1.size
      	for y in xrange(h1):
      		for x in xrange(w1):
      			pix1 = pix1s[x,y]
      			pix2 = pix2s[x][y]
      			pix = []
      			for ci in xrange(len(pix1)):
      				pix.append(pix1[ci] ^ pix2[ci])
      			pix1s[x,y] = tuple(pix)
      	im1.save("img/lvl17-qr.jpg")
      
    In this challenge, you need to get access to a website
    
    Inspect sharks.pcapng, in order to get the credentials needed
    
    • view sharks.pcapng in WireShark
    • apply bpf filter: tcp stream eq 3
    • Follow (this) TCP Stream
    • filter for only 10.11.0.52:62046 → 10.11.0.48:8080
    • Credentials: sharkman:sharks_have_j4ws
    • curl -sL -u sharkman:sharks_have_j4ws -H "Authorization: Basic c2hhcmttYW46c2hhcmtzX2hhdmVfajR3cw==" -X POST "http://hackyeaster.hacking-lab.com/hackyeaster/sharks/auth" -d "user=supershark&pass=hashed%21%21%21&hash=b3f3ca462d3fa58b74d6982af14d8841b074994a"
    • …
        <body>
      	<div class="title">Sharks on Wire</div>
      	<div class="imageBg">
      	  <img src="egg_18_j9rU8ma6nBfz986gJZmf.png" class="image"/>
      	</div>
        </body>
      …
      
    • curl -sL -u sharkman:sharks_have_j4ws -H "Authorization: Basic c2hhcmttYW46c2hhcmtzX2hhdmVfajR3cw==" "http://hackyeaster.hacking-lab.com/hackyeaster/sharks/egg_18_j9rU8ma6nBfz986gJZmf.png" -o egg_18_j9rU8ma6nBfz986gJZmf.png
    Time for paper and scissors! The PDF contains some paper strips. Your task is to combine them in such a way that a passphrase appears
    
    Hint: The passphrase does *not* use all characters available, and it has no spaces
    
    • egg img containing QR code is encrypted using AES; 256-bit key len; op in CBC mode, then base64-encoded
    • following Python script gen all possible valid str combo, assuming:
      • all given paper strips have to be used
      • all letters that appear on paper strips are used in passphrase
      • no symbols are found in passphrase, as they are obscured
    • #!/usr/bin/env python
      #-*- coding: utf-8 -*-
      
      import sys
      import codecs
      import string
      import itertools
      from functools32 import lru_cache
      
      def gen_wordlst(charset, maxlen):
      	files = {}
      	for letter in charset:
      		files[letter] = open("c19-wordlist%d-%c"%(maxlen,letter), "w")
      	for w in itertools.product(charset, repeat=maxlen):
      		print>>files[w[0]], "".join(w)
      	for v in files.values():
      		if v:
      			print>>sys.stderr, "=>", v.name
      			v.close()
      
      horzs = ["s.a.e", "eldpr", "qt.ia", "pupar", "ny.hs"]
      verts = ["lr.se", "epdke", ".por.", ".sob.", "ahmed"]
      def mkstrip(is_horz=True, mode=1):
      	strips = []
      	for p in itertools.permutations(horzs if is_horz else verts):
      		pp = [["" for x in xrange(5)] for x in xrange(5)]
      		for i,s in enumerate(p):
      			for j,c in enumerate(s):
      				if is_horz:
      					pp[i][j] = c
      				else: #vert
      					pp[j][i] = c
      		if mode == 1:
      			pp = ["".join(p) for p in pp]
      		else:
      			pp = "".join("".join(p) for p in pp)
      		strips.append(pp)
      	return strips
      
      def perm_horz(width=5):
      	"""permute horizontal strips to form grid
      		width: # grids to layout on same line
      	"""
      
      	strips = mkstrip(is_horz=True, mode=1)
      	for i in xrange(0,len(strips),width):
      		hh = []
      		for j in xrange(len(strips[i])):
      			hh = []
      			for k in xrange(width):
      				if i+k < len(strips):
      					hh.append(strips[i+k][j])
      			print "\t".join(hh).replace(".", " ")
      		print
      
      def perm_vert(width=5):
      	"""permute vertical strips to form grid
      		width: # grids to layout on same line
      	"""
      
      	strips = mkstrip(is_horz=False, mode=1)
      	for i in xrange(0,len(strips),width):
      		vv = []
      		for j in xrange(len(strips[i])):
      			vv = []
      			for k in xrange(width):
      				if i+k < len(strips):
      					vv.append(strips[i+k][j])
      			print "\t".join(vv).replace(".", " ")
      		print
      
      @lru_cache(maxsize=None)
      def align_build(a,b,c,s):
      	""" recursively build all possible valid str
      		from given a,b
      	"""
      
      	if c == min(len(a),len(b)): return s
      	ss = []
      	if a[c] != ".":
      		r = align_build(a,b,c+1,""+s+a[c])
      		if r: ss.append(r)
      	if b[c] != ".":
      		r = align_build(a,b,c+1,""+s+b[c])
      		if r: ss.append(r)
      	return "\n".join(ss)
      
      def align(idx):
      	""" gen str by align-merge horz+vert strip
      		from cartesian pdt of horz + vert strips
      		- simulate overlap of horz + vert strips
      	"""
      
      	horzd = mkstrip(is_horz=True, mode=2)
      	vertd = mkstrip(is_horz=False, mode=2)
      	# print "|horzd|=", len(horzd), ";|vertd|=", len(vertd) #120
      	# for hs in horzd: print "".join(hs).replace(".", " ")
      	# print
      	# for vs in vertd: print "".join(vs).replace(".", " ")
      	for i,ab in enumerate(itertools.product(horzd, vertd)):
      		if i == int(idx): #idx((a,b))
      			a, b = ab
      			res = align_build(a,b,0,"")
      			if len(res) > 0:
      				print "".join(res)
      				break
      
      if __name__ == "__main__":
      	# gen_wordlst("abdehiklmnopqrstuy", 5)
      	# perm_horz()
      	# perm_vert()
      	idx = 10290 #paperstripsmadebyshredder found @ this idx
      	if len(sys.argv)>1: idx = int(sys.argv[1])
      	align(idx)
      
    • apply word segmentation via
      • eg. echo paperstripsmadebyshredder | python -m wordsegment
      • papers trips made by shredder
      • it is evident that this approach is not perfect
      • but can at least offer some insight into possible words that can be made from the given paper strips
      • to string together, to form a sensible passphrase
    • passphrase: "paper strips made by shredder"
    • paperstripsmadebyshredder
    Robots have placed an egg on this web server. If you wanna find it, you need to think and act like a bot
    
    
    
    • @ http://hackyeaster.hacking-lab.com/robots.txt
    • User-agent: EasterBot
      Disallow: /
      Allow: /hackyeaster/bots/bots.
      
      User-agent: *
      Disallow: /
      
    • visit @ http://hackyeaster.hacking-lab.com/hackyeaster/bots/bots.html, supplying HTTP header: {"User-agent": "EasterBot"}

      curl -sL -A "EasterBot" "http://hackyeaster.hacking-lab.com/hackyeaster/bots/bots.html"

    • Robot Interaction Language (ROILA)
    • for w in bama waboki pisal fatatu fomu wosebi seju sowu seju bamas mufe wafub fomu mowewe; do egrep "^${w}\b" file/roila-wordlist.txt; done | cut -f2 | tr '\n' ' '
    • you must make word of addition two and two this be name of page
    • ie. 2+2 = four.html

    • visit @ http://hackyeaster.hacking-lab.com/hackyeaster/bots/four.html, supplying HTTP header: {"User-agent": "EasterBot"}

      curl -sL -A "EasterBot" "http://hackyeaster.hacking-lab.com/hackyeaster/bots/four.html"

    • <html>
        <head>
      	<title>Bots</title>
      	<meta name="description" content="Robots talk in ROILA language: eman egap eht esrever tsum">
      	<meta name="keywords" content="secret, page, robots, fun, hacky easter, blrt, five, beep">
      	…
        </head>
        <body style="background: white; border: 20px solid white;">
      	<div style="width: 100%; height: 100%; background: url('./robotbg2.jpg') no-repeat center center fixed; -webkit-background-size: contain; -moz-background-size: contain; -o-background-size: contain; background-size: contain;">&#160;</div>
        </body>
      </html>
      
    • using MeTA (hint), together with attr:content of <meta name="description">: eman egap eht esrever tsum
    • reverse string: eman egap eht esrever tsum via echo -ne "eman egap eht esrever tsum" | rev yields: must reverse the page name
    • so reverse(page name = four) = ruof.html
    • …
        <body style="background: white; border: 20px solid white;">
      	<div style="position: absolute; left:50%; top: 50%; margin-left: -187px; margin-top: -187px; width: 375px; height: 375px; background: url('./egg_20_j5fir8U6g8.png'); background-size: 375px 375px; background-repeat: no-repeat;">&#160;</div>
        </body>
      </html>
      
    Tired of boring QR codes, Dr. Bunny C. Easter developed an alternative. He's proudly introducing the "Cony Code" now! Crack the code in order to get another easter egg!
    
    Hint: 110 is blue, the rest's up to you...
    
    
    
    • blue = RGB(0,0,255) ≡ 110
    • looks like bit flip ie. {0→1, 255→0}
    • #!/usr/bin/env python
      #-*- coding: utf-8 -*-
      
      import sys
      import codecs
      from PIL import Image, ImageFilter
      
      reduced = []
      im1 = Image.open("img/conycode.png").convert("RGB")
      pix1s = im1.load()
      w1, h1 = im1.size
      for y in xrange(8,h1,17):
      	xyc = []
      	for x in xrange(8,w1,17):
      		pix1 = pix1s[x,y]
      		xyc.append((x,y,pix1))
      	reduced.append(xyc)
      
      ans = ""
      for r in reduced:
      	for xyc in r:
      		_, _, pix1 = xyc
      		ans += "".join(map(lambda c: "1" if c==0 else "0", list(pix1)))
      
      print "".join(chr(int(ans[i:i+8], 2)) for i in xrange(0,len(ans),8))
      
    prove your skills in hash cracking:
    
    • Standard algorithms (MD5, various SHA)
    • One iteration only, and no salting
    • Click the hint for each hash!
    • For hashes 3 and 4, use the following word list
    1. Numeric PIN (16 digits)

      raLu6+eAmFelf2/uSy/67iTq57E=

      hint

      digits {0,1,7,9} are marked; assume this is charset

      hash: ada2eeebe7809857a57f6fee4b2ffaee24eae7b1

      txt: 1199019170177790

    2. Single word (lowercase only)

      a791KNndKVmnr7N4mEJfZ1VfZ/Z3mHyufoYhCiyKDb38JI7C17JAEPRAutwiI7S1

      hint

      name of song by Rick Astley

      hash: 6bbf7528d9dd2959a7afb37898425f67555f67f677987cae7e86210a2c8a0dbdfc248ec2d7b24010f440badc2223b4b5

      txt: hopelessly

    3. Complex word (1 upper, 1 substitution, ending with punctuation + digit)

      uAgUxeDzhrBjcWP9iv6pKQ==

      hint

      hash: b80814c5e0f386b0637163fd8afea929

      txt: Disc0very.5

    4. Multi-Word (lowercase only)

      l5HL4K6RmgMwmUota6Jrjww6HaFcc7zl/KOUlYgabJA=

      hint

      hash: 9791cbe0ae919a0330994a2d6ba26b8f0c3a1da15c73bce5fca39495881a6c90

      txt: enginebulbgoatimportant

    #!/usr/bin/env python
    #-*- coding: utf-8 -*-
    
    import os #os.linesep
    import sys
    import codecs
    import hashlib
    import base64
    import itertools
    import random
    import string
    import binascii
    
    def hmatch(txt, hash):
    	def name_of(var):
    		import inspect
    		callers_local_vars = inspect.currentframe()\
    			.f_back.f_locals.items()
    		return [var_name \
    			for var_name, var_val in callers_local_vars \
    			if var_val is var][0]
    	hash = hash.lower()
    	md5 = hashlib.md5(txt).hexdigest()
    	sha1 = hashlib.sha1(txt).hexdigest()
    	sha224 = hashlib.sha224(txt).hexdigest()
    	sha256 = hashlib.sha256(txt).hexdigest()
    	sha384 = hashlib.sha384(txt).hexdigest()
    	sha512 = hashlib.sha512(txt).hexdigest()
    	hashes = [md5,sha1,sha224,sha256,sha384,sha512]
    	hnames = map(name_of, hashes)
    	hashes = map(str.lower, hashes)
    	if hash in hashes:
    		i = hashes.index(hash)
    		print "\n".join([
    			"txt: %s [%d]"%(txt,len(txt)),
    			"%s: %s [%d]"%(hnames[i],hashes[i],len(hashes[i]))
    		])
    		return True
    	return False
    
    if __name__ == "__main__":
    	def h(hash):
    		#binascii.hexlify(base64.b64decode(hash))
    		return base64.b64decode(hash).encode("hex")
    	hash1 = h("raLu6+eAmFelf2/uSy/67iTq57E=")
    	hash2 = h("a791KNndKVmnr7N4mEJfZ1VfZ/Z3mHyufoYhCiyKDb38JI7C17JAEPRAutwiI7S1")
    	hash3 = h("uAgUxeDzhrBjcWP9iv6pKQ==")
    	hash4 = h("l5HL4K6RmgMwmUota6Jrjww6HaFcc7zl/KOUlYgabJA=")
    	for i, hash in enumerate([hash1, hash2, hash3, hash4]):
    		print>>sys.stderr, "#%d:"%(i+1), hash
    
    	### Hash 1 ###
    	charset, maxlen = [0,1,7,9], 16
    	def h1_rand(charset, maxlen):
    		return "".join(["%d"%random.choice([charset]) \
    			for i in xrange(maxlen)])
    	def h1_iter(w, charset, maxlen, hash):
    		if len(w) == maxlen:
    			return hmatch(hash, w)
    		for c in charset:
    			if h1_iter(w+str(c), charset, maxlen, hash):
    				break
    	h1_iter("", charset, maxlen, hash1)
    	txt = h1_rand(charset, maxlen)
    	while not hmatch(txt, hash1):
    		txt = h1_rand(charset, maxlen)
    
    	### Hash 3 ###
    	intab, outtab = "aegiost", "4361057" #leetspeak
    	leet = string.maketrans(intab, outtab)
    	with open("file/lvl22-wordlist.txt") as f:
    		words = map(string.strip, f.readlines())
    	while True:
    		word = list(random.choice(words))
    		def cfind(s, ch):
    			return [i for i,ltr in enumerate(s) if ltr==ch]
    		possible = []
    		for c in intab:
    			possible.extend(cfind(word, c))
    		if possible == []: continue
    		idx = random.choice(possible)
    		used = set()
    		used.add(idx)
    		word[idx] = word[idx].translate(leet)
    		idx = random.randint(0, len(word)-1)
    		while idx in used:
    			idx = random.randint(0, len(word)-1)
    		used.add(idx)
    		word[idx] = word[idx].upper()
    		punct = random.choice(string.punctuation)
    		digit = random.choice(string.digits)
    		txt = "".join(word)+punct+digit
    		if hmatch(txt, hash3): sys.exit()
    
    Did you beat the Swordmaster in Monkey Island? Even if, it ain't gonna help you this time. Get to know the mighty Nerd Master!
    
    Connect to hackyeaster.hacking-lab.com:1400, and start the battle.
    
    Here's an insult to start with: Go 127.0.0.1 to your mummy.
    
      #!/usr/bin/env python
      #-*- coding: utf-8 -*-
      
      import os #os.linesep
      import sys
      import codecs
      import json
      import telnetlib
      import hashlib
      from random import randint
      
      def find_answer(question, data):
      	for entry in data:
      		if entry["question"] == question:
      			return entry["answer"].encode("utf-8")
      	data.append({"question": question, "answer": ""})
      	return ""
      
      def ask_question(data):
      	for entry in data:
      		if entry["answer"] == "":
      			return entry["question"].encode("utf-8")
      	return data[randint(0,len(data)-1)]["question"].encode("utf-8")
      
      def add_answer(question, answer, data):
      	for entry in data:
      		if entry["question"] == question:
      			entry["answer"] = answer
      			return
      
      def show(data):
      	for entry in data:
      		print>>sys.stderr, u"Q: %s; A: %s" \
      		%(entry["question"], entry["answer"])
      
      if __name__ == "__main__":
      	filepath = u"challenge23.json"
      	with open(filepath) as file:
      		data = json.load(file)
      	host, port = "hackyeaster.hacking-lab.com", 1400
      	nr = "\r\n"
      	tn = telnetlib.Telnet(host, port)
      	tn.read_until("Do you feel brave enough to challenge the mighty nerdmaster? (y|n)")
      	tn.write("y" + nr)
      	tn.read_until("---- YOUR TURN ----\n")
      	while True:
      		try:
      			my_question = ask_question(data)
      			tn.write(my_question + nr)
      			print "I ask: " + my_question
      			answer = tn.read_until("---- MY TURN ----\n").split("\n")[0]
      			print "It Answered: " + answer
      			if answer != "We've had this one before.":
      				add_answer(my_question, answer, data)
      			print
      			print "---- MY TURN ----"
      			print
      			question = tn.read_until("\n").split("\n")
      			print "It asked: " + question[0]
      			my_answer = find_answer(question[0], data)
      			tn.write(my_answer + nr)
      			print "I answer: " + my_answer
      			response = tn.read_until("---- YOUR TURN ----\n")
      			print "resp:", response
      		except EOFError as e:
      			print ">> Connection closed"
      			break
      	with open(filepath, "w") as outfile:
      		json.dump(data, outfile)
      		print "%s updated"%filepath
      	BUF_SIZE = 65536  #64KB chunk
      	sha1 = hashlib.sha1()
      	with open(filepath, "rb") as f:
      		while True:
      			data = f.read(BUF_SIZE)
      			if not data: break
      			sha1.update(data)
      	print "sha1sum({}): {}".format(filepath, sha1.hexdigest())
      
      question: "Go 127.0.0.1 to your mummy."
      answer: "Won't work. I only support IPv6."
      
      question: "Pna lbh ernq guvf?"
      answer: "EBG13 vf sbe ynzref."
      
      question: "Tell me your name, hobo. I need to check your records."
      answer: "My name is bob'; DROP TABLE VALJ;--"
      
      question: "You'll be 0xdeadbeef soon."
      answer: "Not as long as I have my 0xcafebabe."
      
      question: "After loosing to me, your life won't be the same anymore."
      answer: "A Life? Cool! Where can I download one of those?"
      
      question: "You must be jealous when seeing my phone's display."
      answer: "Not really - Your pixels are so big, some of them have their own region code!"
      
      question: "You're so slow, you must have been written in BASIC."
      answer: "At least I don't have memory leaks like you."
      
      question: "Ping! Anybody there?"
      answer: "ICMP type 3, code 13: Communication Administratively Prohibited"
      
      question: "This fight is like a hash function - it works in one direction only."
      answer: "Too bad you picked LM hashing."
      
      question: "You should leave your cave and socialize a bit."
      answer: "I'm not anti-social. I'm just not user friendly."
      
      question: "1f u c4n r34d th1s u r s70p1d."
      answer: "You better check your spelling. Stoopid has two 'o's."
      
      question: "I have more friends than you."
      answer: "Yeah, but only until you update your Facebook profile with a real picture of you!"
      
      question: "Af7ter th1s f1gh7, I w1ll pwn ur b0x3n."
      answer: "Check your settings - you seem to have chosen the Klingon keyboard layout."
      
      question: "format C:"
      answer: "Specified drive does not exist."
      
      question: "I bet you don't even understand binary."
      answer: "Sure I do. Me and you, we are 10 different kind of persons."
      
      question: "I'll check you out - any last words?"
      answer: "svn:ignore"
      
      Respect! you've beaten the mighty nerd master! Here's your egg:
      http://hackyeaster.hacking-lab.com/hackyeaster/images/egg_23_j7vzfUzftszdf754fXDS.png
      
    Crypto Chiefs Ltd. developed a new hash function, which takes a 'divide and conquer' approach and combines several well-known hash functions ("Split, Hash, And Merge"). The inventors claim that with this approach, their function becomes more secure. Can you prove they are wrong?
    
    Create a string which produces the following hash:
    	757c479895d6845b2b0530cd9a2b11
    
    spec: shamhash.pdf
    
      #!/usr/bin/env python
      #-*- coding: utf-8 -*-
      
      import sys
      import codecs
      import itertools
      import hashlib
      from hashlib import sha1, sha256, sha512
      from Crypto.Hash import MD2, MD5
      
      if __name__ == "__main__":
      	ans = "afpmqtaaqidtaww8ntangecaaf9pt3"
      	charset = "abcdefghijklmnopqrstuvwxyz0123456789"
      	begin, end = 0, 6
      	hashes = [(MD2, "757c47"), (MD5, "9895d6"), (sha1, "845b2b"), (sha256, "0530cd"), (sha512, "9a2b11")]
      	for algo, frag in hashes:
      		if algo in [MD2, MD5]:
      			begin += 6
      			end += 6
      			continue
      		print>>sys.stderr, algo.__name__
      		for i in itertools.product(charset, repeat=6):
      			word = "".join(i)
      			try: whash = algo.new(word).hexdigest()
      			except AttributeError:
      				m = algo()
      				m.update(word)
      				whash = m.hexdigest()
      			if frag == whash[begin:end]:
      				print word, whash
      				ans += word
      				break
      		begin += 6
      		end += 6
      	print "ans:", ans
      
    • enter ans → redirect to http://hackyeaster.hacking-lab.com/hackyeaster/sham_m7f3ngUfg6LLzf530zub.html
    jadandida.zip
    
    • JAD: ref. JAva Decompiler
    • IDA: ref. Interactive DisAssembler
    • lib/libhe2015_Lizzle.dll::LizzleDLL::{Fizzle,Shizzle} loaded & used
    • +2 other pure Java functions: {bizzle,rizzle}
    • main method
      1. takes an input `key`, k
      2. then subsequently runs a total of 10 rounds × k = fizzle(rizzle(shizzle(bizzle(k))))
      3. s3cr3t.bin (assumes this is/contains egg w/ QR code) successfully decrypt()-ed if result "v3O] pmWm<Y(0=21".equals(k)
    • use JD-GUI to decompile Java .class files → .java source code
    • use IDA Pro v6.6 to generate C code from DLL, to analyse behaviour of imported native functions
    • after careful analysis, and also by trying several "test"/"dummy" input values,
      • String bizzle(String s) case-sensitively increments char w/ wrap-around, ie. tr "a-yzA-YZ" "b-zaB-ZA"
      • String rizzle(String s) swaps case of all letters in s, ie. tr "[:upper:][:lower:]" "[:lower:][:upper:]"
      • size_t Shizzle(char *a1, char *a2) reverses a1a2, iff #a1 == 16 chars
      • size_t Fizzle(char *a1, char *a2) performs char transformation in printable ASCII range on a1, then swaps halves of result, producing a2
    • /*Java*/
      String bizzle(String s)
      {
      	char[] chars = s.toCharArray();
      	for(int i = 0; i < chars.length; i++)
      	{
      		char c = chars[i];
      		if((c >= 'a') && (c < 'z'))
      		{
      			c = (char)(c + '\001');
      		}
      		else if(c == 'z')
      		{
      			c = 'a';
      		}
      		else if((c >= 'A') && (c < 'Z'))
      		{
      			c = (char)(c + '\001');
      		}
      		else if(c == 'Z')
      		{
      			c = 'A';
      		}
      		chars[i] = c;
      	}
      	return new String(chars);
      }
      String rizzle(String s)
      {
      	char[] chars = s.toCharArray();
      	for(int i = 0; i < chars.length; i++)
      	{
      		char c = chars[i];
      		if(Character.isUpperCase(c))
      		{
      			chars[i] = Character.toLowerCase(c);
      		}
      		else if(Character.isLowerCase(c))
      		{
      			chars[i] = Character.toUpperCase(c);
      		}
      	}
      	return new String(chars);
      }
      
      /*DLL*/
      size_t Fizzle(char *a1, char *a2)
      {
      	size_t result; // eax@1
      	int v3; // ecx@3
      	int v4; // eax@5
      	int v5; // edx@8
      	int v6; // edx@8
      	char v7; // al@10
      	char v8; // dl@10
      	char v9; // al@10
      	char v10; // dl@10
      	char v11; // al@10
      	char v12; // dl@10
      	char v13; // al@10
      	char v14; // dl@10
      	char v15; // al@10
      	char v16; // dl@10
      	char v17; // al@10
      	char v18; // dl@10
      	char v19; // al@10
      	char v20; // dl@10
      	result = strlen(a1);
      	if ( result == 16 )
      	{
      		v3 = 0;
      		do
      		{
      			while ( 1 )
      			{
      				v4 = a1[v3];
      				if ( (int)(v4 - 32) > 'Z' )
      					break;
      				a2[v3] = (v4 + v3 * v3 - 27) % 91 + 32;
      				++v3;
      				if ( v3 == 16 )
      					goto LABEL_7;
      			}
      			a2[v3++] = v4;
      		}
      		while ( v3 != 16 );
      LABEL_7:
      		if ( a2 & 3 )
      		{
      			v7 = a2[8];
      			a2[8] = a2;
      			v8 = a2[1];
      			a2 = v7;
      			v9 = a2[9];
      			a2[9] = v8;
      			v10 = a2[2];
      			a2[1] = v9;
      			v11 = a2[10];
      			a2[10] = v10;
      			v12 = a2[3];
      			a2[2] = v11;
      			v13 = a2[11];
      			a2[11] = v12;
      			v14 = a2[4];
      			a2[3] = v13;
      			v15 = a2[12];
      			a2[12] = v14;
      			v16 = a2[5];
      			a2[4] = v15;
      			v17 = a2[13];
      			a2[13] = v16;
      			v18 = a2[6];
      			a2[5] = v17;
      			v19 = a2[14];
      			a2[14] = v18;
      			v20 = a2[7];
      			a2[6] = v19;
      			result = a2[15];
      			a2[15] = v20;
      			a2[7] = result;
      		}
      		else
      		{
      			v5 = a2[8];
      			a2[8] = a2;
      			result = a2[4];
      			a2 = v5;
      			v6 = a2[12];
      			a2[12] = result;
      			a2[4] = v6;
      		}
      		a2[16] = 0;
      	}
      	else
      	{
      		a2 = 0;
      	}
      	return result;
      }
      size_t Shizzle(char *a1, char *a2)
      {
      	size_t result;
      	result = strlen(a1);
      	if ( result == 16 )
      	{
      		a2[15] = *a1;
      		a2[14] = a1[1];
      		a2[13] = a1[2];
      		a2[12] = a1[3];
      		a2[11] = a1[4];
      		a2[10] = a1[5];
      		a2[9] = a1[6];
      		a2[8] = a1[7];
      		a2[7] = a1[8];
      		a2[6] = a1[9];
      		a2[5] = a1[10];
      		a2[4] = a1[11];
      		a2[3] = a1[12];
      		a2[2] = a1[13];
      		a2[1] = a1[14];
      		result = a1[15];
      		a2[16] = 0;
      		a2 = result;
      	}
      	else
      	{
      		a2 = 0;
      	}
      	return result;
      }
      
    • Python code to undo effects of these functions (called in reverse order to invocation of their original counterparts)
    • #!/usr/bin/env python
      #-*- coding: utf-8 -*-
      
      import sys
      import codecs
      import string
      
      sys.stdout = codecs.getwriter("utf8")(sys.stdout)
      sys.stderr = codecs.getwriter("utf8")(sys.stderr)
      
      def unfizzle_undo(c, idx):
      	c = ord(c)
      	c -= 32
      	n = 0
      	while True:
      		cc = c + n * 91 + (27 - (idx * idx))
      		if 32 <= cc <= 122: break
      		n += 1
      	c = cc
      	c = chr(c)
      	return c
      def unfizzle(s):
      	ss = list(s)
      	ss[:8], ss[8:] = ss[8:], ss[:8]
      	for i in xrange(15,-1,-1):
      		ch = ss[i]
      		if ord(ch) > ord("z"): continue
      		else: ss[i] = unfizzle_undo(ch, i)
      	return "".join(ss)
      def unrizzle(s):
      	ret = []
      	for c in s:
      		if c in string.ascii_letters:
      			if c.isupper(): ret.append(c.lower())
      			elif c.islower(): ret.append(c.upper())
      		else: ret.append(c)
      	return "".join(ret)
      def unshizzle(s): return s[::-1]
      def unbizzle(s):
      	ret = []
      	for c in s:
      		added = False
      		for lst in [string.ascii_lowercase, string.ascii_uppercase]:
      			if c in lst:
      				ret.append(lst[(lst.index(c)-1+len(lst))%len(lst)])
      				added = True
      				break
      		if not added: ret.append(c)
      	return "".join(ret)
      
      if __name__ == "__main__":
      	k = "v3O] pmWm<Y(0=21"
      	for i in xrange(10):
      		k = unbizzle(unshizzle(unrizzle(unfizzle(k))))
      	print k
      
    • provide key: jadnIdal0vecod3n to original JadAndIda.class, which decrypts s3cr3t.bineggizzle_25.png
    Welcome to Clumsy Cloud™!
    If your files are the eggs, then we are the hen™.
    
    We encrypt all your files, with a strong passphrase. The passphrase is kept securely in this app, protected by a PIN.
    

    Download Files

    Enter your PIN to download your secret files.

    ----




    Backup

    Worried about losing your phone? Simply create a backup of your passphrase.

    • looking up lvl-specific param eg. "ovaederecumsale" or "8QeNdEdkspV6+1I77SEEEF4aWs5dl/auahJ46MMufkg="; or even "egg_26.png" reveals relevant code block
    • @ /src/src/ps/hacking/hackyeaster/android/Activity.java:
    • …
      private int a(String s, Context context)
      {
      	try
      	{
      		SecretKeySpec secretkeyspec = new SecretKeySpec(a(s, "ovaederecumsale", 10000), "AES");
      		Cipher cipher = Cipher.getInstance("AES");
      		cipher.init(2, secretkeyspec);
      		String s1 = new String(cipher.doFinal(Base64.decode("8QeNdEdkspV6+1I77SEEEF4aWs5dl/auahJ46MMufkg=", 0)));
      
      		DownloadManager downloadmanager = (DownloadManager)getSystemService("download");
      		android.app.DownloadManager.Request request = new android.app.DownloadManager.Request(Uri.parse((new StringBuilder("http://hackyeaster.hacking-lab.com/hackyeaster/pin?p=")).append(s1).toString()));
      		request.setTitle("Hacky Easter");
      		request.setDescription("Egg Download");
      		request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "egg_26.png");
      		registerReceiver(new d(this), new IntentFilter("android.intent.action.DOWNLOAD_COMPLETE"));
      		downloadmanager.enqueue(request);
      		Toast.makeText(context, "Download started", 0).show();
      	}
      	catch (Exception exception)
      	{
      		return 1;
      	}
      	return 0;
      }
      …
      public static byte[] a(String s, String s1, int i1)
      {
      	MessageDigest messagedigest = MessageDigest.getInstance("SHA1");
      	byte abyte0[] = (new StringBuilder(String.valueOf(s1))).append(s).toString().getBytes();
      	int j1 = 0;
      	do
      	{
      		if (j1 >= i1)
      		{
      			byte abyte1[] = new byte[16];
      			System.arraycopy(abyte0, 0, abyte1, 0, 15);
      			return abyte1;
      		}
      		abyte0 = messagedigest.digest(abyte0);
      		j1++;
      	} while (true);
      }
    • public static byte[] a(String s, String s1, int i1) == hashing routine;
    • while private int a(String s, Context context) performs decryption, due to 2 in cipher.init(2, secretkeyspec), using supplied PIN
    • reusing these 2 routines highlighted in red; except swapping Android for Java8 Base64 decoding:
    • import java.util.Base64;
      …
      	Base64.Decoder decoder = Base64.getDecoder();
      	byte[] dec64 = decoder.decode("8QeNdEdkspV6+1I77SEEEF4aWs5dl/auahJ46MMufkg=");
      	String s1 = new String(cipher.doFinal(dec64));
      …
      
    • then exhaustively enum all 4-digit PIN combo, looking out for any ASCII-readable txt
    • import java.util.Base64;
      …
      	for(int i=0; i<9999; i++)
      	{
      		String PIN = String.format("%04d", i);
      		String ciphertxt = crypt(PIN);
      		if (ciphertxt != null
      		&& ciphertxt.matches("^\\p{ASCII}*$"))
      		{
      			System.out.println(PIN+": "+ciphertxt);
      			break;
      		}
      	}
      …
      
    • finds PIN=7113, linked to passwd=wirestarter54321
    • @ http://hackyeaster.hacking-lab.com/hackyeaster/pin?p=wirestarter54321
    You intercepted messages exchanged by evil Dr. Hopper and his agents. They used a One Time Pad for achieving perfect secrecy. Lucky for you, they have miserably failed, since the same key was used multiple times
    
    Check out the ciphertexts, and try to decrypt them
    
    Hint: The plaintexts consist of lowercase letters and spaces only
    
    60c46964f83879618e2878de539f6f4a6271d716
    63c37a6ca177792092602cc553c9684b
    68d82c6bf4767f79dd617f9642d768057f63c1
    6c8a7b6ce06a3161dd6a60d755d42d4d6d67
    71c26929e96931698e2865d816d3624b687cd6
    6cda6d6df87764709c6c7bd357d361556d77
    • employ atk technique known as "crib drag"
      • define:crib - known/guessed piece of plaintext
      • Outline
      1. Guess a word that might appear in 1 of the ciphertext, eg. "the "
      2. Encode (word from step 1) as hex string
      3. XOR 2 ciphertexts
      4. XOR (hex string from step 2) @ each pos of (XOR(2 cipher-texts (from step 3)))
      5. 5a. When result from step 4 == readable English text, guess the English word and expand crib search
      6. 5b. Otherwise, try XOR of crib word @ nxt pos
    • key: 05aa0c0981181100fd080cb636bf0d250c13b878
    • plaintext
    • enemy has the bonbon
      five oh oh seven
      mr bunny is the spy
      i wear a black hat
      the hq is in london
      ipadyoupadweallpad