NdH2k14: Collector badge challenge write-up
One of the many fantastic things about the Nuit Du Hack security Convention is the annual special challenge that comes with the badge.
The first person who’ll solve it will win the “Black Badge“, a life pass to all upcoming Nuit du Hack events !
This year, the challenge was a bit particular : it was a limited edition (200 pieces), and it was not free. But God it was fun.
The badge was an Arduino compatible board inspired from a famous project called Usnoobie. It’s basically an ATMega328p with a mini-usb port that can flash itself by simulating an USBAsp programmer. More details on the Flash memory structure later.
Last but not least, the badge comes with a handy set of tools and instructions that you could find on the creator’s online repository (@virtualabs)
Step 0 : Summoning the soldering iron
I’ll rapidly pass on the soldering part : about 15 to 20 components to solder on the board, it’s pretty strayforward. Unless you’re diagnosed with Parkinson, it should not take more than two hours to get the thing working.
Since I started soldering using a two-handed sword and mittens, it was not that easy, but well… It works. I’ll let you see for yourself and move on to the fun part.
Step 1 : Tu quoque mi fili
Once plugged, the board is recognized as a USB device; Virtualabs gave us a cute console written in Python to talk to the monster. The board’s product and vendor’s ids are respectively 0x05dc and 0x16c0, which are shared IDs for use with libusb.
- # python console.py
- *** Ndh2k14 serial console ***
- – waiting for device …
- – device detected, spawning console
- Welcome to Ndh2k14 Secure Chip.
- New here ? type access code ‘HelpPlz!’.
- Access code: HelpPlz!
- === Security level 1 ===
- Decrypt this and use it as an access code to enter level 2:
- DyHmXoHv
- Access code:
Anyone a bit familiar with security challenges will recognize a Caesar cipher. And what’s the most historical elegant way to defeat Julius ? Bruting it like his son, of course.
- code = “DyHmXoHv”
- def bruteforce():
- charset = “0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”
- for i in range(len(charset)):
- nc = ”
- for c in code:
- j = charset.index(c)
- nc += charset[(j + i) % len(charset)]
- yield(nc)
- for attempt in bruteforce():
- self.badge.send(attempt)
After a few tries, the code unlocking the next level comes up :
- Access code: AvEjUlEs
- === Security level 2 ===
On to the next part.
Step 2 : Good old WEP basic encryption
The security level 2 gives us a nice looking RC4 challenge :
- === Security level 2 ===
- – Hope you have your RSA token …
- Enter OTP (number, KEY and IV are 4 bytes long):
- IV: 5748524f
- RC4(RANDOM, IV+KEY): 9937c0cf5244188d
- ANSWER:
- Timeout reached.
- Access code:
After a few tries, one would notice that the IV’s and ciphered texts change over time. But not that much, since the same IV’s would come up every third or fourth try. Here is a list of IV’s and associated ciphered texts that would come up from the board :
- (“e7ea483d5d68ab54”,“TlYk”),
- (“55c1ab1aa03129c5”,“u8J7”),
- (“cf473fbe817ca98b”,“0zt6”),
- (“904a4d1afdb1bff7”,“aDHv”),
- (“d9cebc9e6a0da621”,“qkZu”),
- (“06937a2ab43edef8”,“cNcc”),
- (“3f05b477b874e824”,“Nv3H”),
- (“8c1aeabcde6adbaf”,“7LGM”),
- (“5617b6bcaadbad26”,“Qfvk”),
- (‘d662e9854ec7b192’,“fUYM”),
- (‘5d4f5d59a3e6d4bb’,“k8D6”),
- (‘1faeaf3384eb70fb’,‘w5mt’),
- (‘2b9abbb3af1929b5’,‘zLaF’),
- (“5963ae9f728cb753”,“5BRO”),
- (“b89a660d907bacc6”,“n2Ai”),
- (“9faabe2ceebb24bc”,“0M1K”),
- (“e653f5ccab6dd730”,“mKqb”),
I first tried the smart way : since RC4 is a stream cipher, I could try sniffing as many IV’s as I could in order to find the key, attacking the RC4’s KSA as in the popular FMS attack.
I did not succeed using this method. So I decided to take the most recurring IV and ciphered text couple (the first one in the list above), and brute the 4 bytes long key. I would only feed the “printable” deciphered texts to the badge. The resulting list of printable passcodes was about 380 long, which took around 5 minutes to get me to the next level.
Generating the list :
- charset = “0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”
- def RC4(data, key):
- x = 0
- s = range(256)
- for i in range(256):
- x = (x + s[i] + ord(key[i % len(key)])) % 256
- s[i], s[x] = s[x], s[i]
- x = y = 0
- out = “”
- for c in data:
- x = (x + 1) % 256
- y = (y + s[x]) % 256
- s[x], s[y] = s[y], s[x]
- out += chr(ord(c) ^ s[(s[x] + s[y]) % 256])
- return out
- def brute():
- return (”.join(candidate)
- for candidate in itertools.chain.from_iterable(itertools.product(charset, repeat=i)
- for i in range(4,5)))
- encrypted = (“e7ea483d5d68ab54”,“TlYk”)
- for key in brute():
- (encoded,iv) = encrypted
- encoded = encoded.decode(‘hex’)
- decoded = RC4(encoded, “%s%s” % (iv, key))
- if all(e in charset for e in decoded):
- print “%s %s” % (key,a)
The access code for the next level was ‘Pu1lD0wn’.
Step 3 : the Extra-Terrestrial
This is what you could read on the next level :
- Access code: Pu1lD0wn
- === Security level 3 ===
- – Badge phone home, obviously.
- Access code:
Well, thank you sir.
One of the first things I tried with the badge was to identify the request types (IN and OUT) and request fields that were used to communicate with the board via control transfers. I wanted to see if the badge was “speaking” using different codes from the python console that came with the badge. And thankfully it did.
- for bRequest in range(256):
- try:
- ret = dev.ctrl_transfer(0xC0, bRequest, 0, 0, 1)
- print “bRequest “,bRequest
- print ret
- sleep(0.02)
- except:
- pass
0xC0 is the request type IN. Here is the trimmed output :
- bRequest 0
- array(‘B’)
- […]
- bRequest 97
- array(‘B’, [111])
- bRequest 98
- array(‘B’, [80])
- bRequest 99
- array(‘B’, [78])
- bRequest 100
- array(‘B’)
- […]
- bRequest 255
- array(‘B’)
Seems like the board is talking through 97-99. I then tried printing what was sent :
- for bRequest in range(97,100):
- try:
- ret = dev.ctrl_transfer(0xC1, bRequest, 0, 0, 4000)
- print “bRequest “,bRequest
- print ”.join(chr(x) for x in ret)
- sleep(0.02)
- except:
- pass
Here is the output :
- bRequest 97
- ZK
- –
- |��a�:�i�‘%}���1]>N���S@�k������x[��B}5��6jy��E�
- /�Gb���{���bH�}�]��b�/>>���:����� �����M����I5�>
- bRequest 98
- Pu1lD0wn
- k�����>wl�Z��A5Q�.�<�_ardware much electroni��8_�榌����)�������K��v�f*9i�>qe���[�/�”��
- J
- bRequest 99
- N
The 98 and 99 requests were not moving (except the 99 wich would either send ‘Y’ or ‘N’, which I did not find interesting for this step), but the 97 request field would send me different data each time. I started guessing that the board would gloriously send me it’s internal RAM after a few tries. I then started reading more and more from the badge on the same request field:
- for i in range(200):
- d = device.ctrl_transfer(0xC1, 97, 0, 0, 4000)
- ret = ”.join(chr(x) for x in d)
- print ret
From what I was seeing, I was confirming that it was indeed the RAM :
- # python fuzz.py | strings
- 1]>N
- <“$[
- wIfT}
- Pu1lD0wn
- SASSEMBLE ME ?
- ode: s c hardware much electroni
- bIJ2+
- 1s`I
- MJ$<
- x7X
- f:ErH
- 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%02x%02x%02x%02x%02x%02x%02x%02x
- IV:
- RC4(RANDOM, IV+KEY):
- ANSWER:
- Timeout reached.
- =so1
- %02X%02X%02X%02X%02X%02X%02X%02X
- Access code:
- d@l4
- Welcome to Ndh2k14 Secure Chip.
- […]
What if I sent the last code I just obtained ? Would the next access code be decrypted in RAM ? Guess what 🙂
- device.ctrl_transfer(bmRequestTypeOut, 0x61, 0, 0, “Pu1lD0wn”)
- for i in range(200):
- d = device.ctrl_transfer(0xC1, 97, 0, 0, 4000)
- ret = ”.join(chr(x) for x in d)
- print ret
- # python fuzz.py | strings
- 1lD0wn
- 1]>N
- <“$[
- === Se
- curity level 3 ===
- – Badge phone home, obviously.
- Next access code is: B1p!8lP!
- Pu1lD0wn
- Z(Pu1lD0wn
- SASSEMBLE ME ?
- === Secu h
- ardware much electroni
- bIJ2+
- 1s`I
- MJ$<
- x7X
- f:ErH
- 3456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%02x%02x%02x%02x%02x%02x%02x%02x
- IV:
- RC4(RANDOM, IV+KEY):
What a lucky m*therf*cker am I. On to the next level.
Step 4 : This is the end
This one is the one that took away my sleep, my soul and my sanity. Here is the output of the Security level 4 :
- Access code: B1p!8lP!
- === Security Level 4 ===
- Last auth code:
This is were the fun begins.
I forgot to mention that you could obtain the microcontroller’s flash memory via avrdude, since the board is capable of flashing itself by emulating a USBAsp programmer. Booting the board in “flash mode” using the RESET button :
- avrdude –p atmega328p –c usbasp –U flash:r:atmega328p.flash.hex:i
This command gave you the whole flash memory of the board’s microcontroller, which was later added to the board’s Github repository.
With the code in hand, and the serious look of the “Last auth code” displayed by the board, there was no other choice but to reverse it using a debugger or a simulator.
I first tried a few projects, like simavr, which would have allowed me to attach a gdb to the simulator, but I faced a few problems when came the USB communication emulation part. After a few days trying to patch the project, my kernel and my brain, I decided to use more user-friendly tools and give Atmel Studio a try. Turns out the version 6 of the tool does not allow to live-debug a simulator running a raw hex dump, but the version 4 does. Using some documentation and a lot of coffee, I started executing the program step by step. I also gave IDA a try, which allowed me to get a clear graphical view of the functions I was running into.
Here’s an overall diagram of the whole flash program:
The board starts up at 0x7000, and the bootloader either jumps to 0x3800 if the RESET button is pressed, or to 0x0000 on default behavior.
So we need to start at 0x0. After a few lines of initialization, we end up in a loop that looks like a part of a receive function (0x070B).
- +0000081F: E184 LDI R24,0x14 Load immediate
- +00000820: B199 IN R25,0x09 In from I/O location
- +00000821: 7194 ANDI R25,0x14 Logical AND with immediate
- +00000822: F431 BRNE PC+0x07 Branch if not equal
- +00000823: 5081 SUBI R24,0x01 Subtract immediate
- +00000824: F7D9 BRNE PC–0x04 Branch if not equal
- +00000825: 92100580 STS 0x0580,R1 Store direct to data space
- +00000827: 9210057A STS 0x057A,R1 Store direct to data space
- +00000829: 91DF POP R29 Pop register from stack
- +0000082A: 91CF POP R28 Pop register from stack
- +0000082B: 9508 RET Subroutine return
After 20 loops, we RET into the function I called “DidIReceivedSomething”, a.k.a 0x082C :
- +0000082C: 930F PUSH R16 Push register on stack
- +0000082D: 931F PUSH R17 Push register on stack
- +0000082E: 93CF PUSH R28 Push register on stack
- +0000082F: 93DF PUSH R29 Push register on stack
- +00000830: D000 RCALL PC+0x0001 Relative call subroutine
- +00000831: D000 RCALL PC+0x0001 Relative call subroutine
- +00000832: B7CD IN R28,0x3D In from I/O location
- +00000833: B7DE IN R29,0x3E In from I/O location
- +00000834: C00A RJMP PC+0x000B Relative jump
- +00000835: 836B STD Y+3,R22 Store indirect with displacement
- +00000836: 837C STD Y+4,R23 Store indirect with displacement
- +00000837: 8389 STD Y+1,R24 Store indirect with displacement
- +00000838: 839A STD Y+2,R25 Store indirect with displacement
- +00000839: 940E070B CALL 0x0000070B Call subroutine
- +0000083B: 819A LDD R25,Y+2 Load indirect with displacement
- +0000083C: 8189 LDD R24,Y+1 Load indirect with displacement
- +0000083D: 817C LDD R23,Y+4 Load indirect with displacement
- +0000083E: 816B LDD R22,Y+3 Load indirect with displacement
- +0000083F: 9120055A LDS R18,0x055A Load direct from data space
- +00000841: 9130055B LDS R19,0x055B Load direct from data space
- +00000843: 2B23 OR R18,R19 Logical OR
- +00000844: F781 BRNE PC–0x0F Branch if not equal
- +00000845: 018B MOVW R16,R22 Copy register pair
- +00000846: 3069 CPI R22,0x09 Compare with immediate
- +00000847: 0571 CPC R23,R1 Compare with carry
- +00000848: F010 BRCS PC+0x03 Branch if carry set
- +00000849: E008 LDI R16,0x08 Load immediate
- +0000084A: E010 LDI R17,0x00 Load immediate
- +0000084B: E021 LDI R18,0x01 Load immediate
- +0000084C: E030 LDI R19,0x00 Load immediate
- +0000084D: 9330055B STS 0x055B,R19 Store direct to data space
- +0000084F: 9320055A STS 0x055A,R18 Store direct to data space
- +00000851: E222 LDI R18,0x22 Load immediate
- +00000852: E031 LDI R19,0x01 Load immediate
- +00000853: 01A8 MOVW R20,R16 Copy register pair
- +00000854: 01BC MOVW R22,R24 Copy register pair
- +00000855: 01C9 MOVW R24,R18 Copy register pair
- +00000856: 940E0E6D CALL 0x00000E6D Call subroutine
- +00000858: 9300055C STS 0x055C,R16 Store direct to data space
- +0000085A: C002 RJMP PC+0x0003 Relative jump
- +0000085B: 940E070B CALL 0x0000070B Call subroutine
- +0000085D: 9180055A LDS R24,0x055A Load direct from data space
- +0000085F: 9190055B LDS R25,0x055B Load direct from data space
- +00000861: 9702 SBIW R24,0x02 Subtract immediate from word
- +00000862: F7C1 BRNE PC–0x07 Branch if not equal
- +00000863: C002 RJMP PC+0x0003 Relative jump
- +00000864: 940E070B CALL 0x0000070B Call subroutine
- +00000866: 9180056E LDS R24,0x056E Load direct from data space
- +00000868: FF84 SBRS R24,4 Skip if bit in register set
- +00000869: CFFA RJMP PC–0x0005 Relative jump
- +0000086A: 9210055B STS 0x055B,R1 Store direct to data space
- +0000086C: 9210055A STS 0x055A,R1 Store direct to data space
- +0000086E: E081 LDI R24,0x01 Load immediate
- +0000086F: E090 LDI R25,0x00 Load immediate
- +00000870: 900F POP R0 Pop register from stack
- +00000871: 900F POP R0 Pop register from stack
- +00000872: 900F POP R0 Pop register from stack
- +00000873: 900F POP R0 Pop register from stack
- +00000874: 91DF POP R29 Pop register from stack
- +00000875: 91CF POP R28 Pop register from stack
- +00000876: 911F POP R17 Pop register from stack
- +00000877: 910F POP R16 Pop register from stack
- +00000878: 9508 RET Subroutine return
We land on line 42 (coincidence? I think not), and compare the result of the previous function with 0x02. In order to “get out” of the 0x082c, we need to fill r24 with 0x02. We then RET (line 63) into 0x0879 on line 24.
- +00000879: 92EF PUSH R14 Push register on stack
- +0000087A: 92FF PUSH R15 Push register on stack
- +0000087B: 930F PUSH R16 Push register on stack
- +0000087C: 931F PUSH R17 Push register on stack
- +0000087D: 93CF PUSH R28 Push register on stack
- +0000087E: 93DF PUSH R29 Push register on stack
- +0000087F: 018C MOVW R16,R24 Copy register pair
- +00000880: 01EB MOVW R28,R22 Copy register pair
- +00000881: C011 RJMP PC+0x0012 Relative jump
- +00000882: 30C8 CPI R28,0x08 Compare with immediate
- +00000883: 05D1 CPC R29,R1 Compare with carry
- +00000884: F028 BRCS PC+0x06 Branch if carry set
- +00000885: 9728 SBIW R28,0x08 Subtract immediate from word
- +00000886: E088 LDI R24,0x08 Load immediate
- +00000887: 2EE8 MOV R14,R24 Copy register
- +00000888: 2CF1 MOV R15,R1 Copy register
- +00000889: C003 RJMP PC+0x0004 Relative jump
- +0000088A: 017E MOVW R14,R28 Copy register pair
- +0000088B: E0C0 LDI R28,0x00 Load immediate
- +0000088C: E0D0 LDI R29,0x00 Load immediate
- +0000088D: 01B7 MOVW R22,R14 Copy register pair
- +0000088E: 01C8 MOVW R24,R16 Copy register pair
- +0000088F: 940E082C CALL 0x0000082C Call subroutine
- +00000891: 0D0E ADD R16,R14 Add without carry
- +00000892: 1D1F ADC R17,R15 Add with carry
- +00000893: 9720 SBIW R28,0x00 Subtract immediate from word
- +00000894: F769 BRNE PC–0x12 Branch if not equal
- +00000895: E081 LDI R24,0x01 Load immediate
- +00000896: E090 LDI R25,0x00 Load immediate
- +00000897: 91DF POP R29 Pop register from stack
- +00000898: 91CF POP R28 Pop register from stack
- +00000899: 911F POP R17 Pop register from stack
- +0000089A: 910F POP R16 Pop register from stack
- +0000089B: 90FF POP R15 Pop register from stack
- +0000089C: 90EF POP R14 Pop register from stack
- +0000089D: 9508 RET Subroutine return
This function is the “DidWeSendSomethingViaUSB” function. In order to simulate the successful behavior of the badge, we need to jump to the “return 1” of this function on line 28. We then RET into the main function, which :
- Polls the USB for requests
- Sends the “Access code: ” string when receiving an IN request (0x08E9)
- Sends the “New here ? type access code HelpPlz!” string when it’s the first time you fire up the badge (0x08E9)
- Waits for an OUT request (receiving of a passcode) in 0x0DBD
- +00000E1B: E28F LDI R24,0x2F Load immediate
- +00000E1C: E795 LDI R25,0x75 Load immediate
- +00000E1D: 9701 SBIW R24,0x01 Subtract immediate from word
- +00000E1E: F7F1 BRNE PC–0x01 Branch if not equal
- +00000E1F: C000 RJMP PC+0x0001 Relative jump
- +00000E20: 0000 NOP No operation
- +00000E21: 9A25 SBI 0x04,5 Set bit in I/O register
- +00000E22: E088 LDI R24,0x08 Load immediate
- +00000E23: B983 OUT 0x03,R24 Out to I/O location
- +00000E24: 940E0937 CALL 0x00000937 Call subroutine
- +00000E26: E08B LDI R24,0x0B Load immediate
- +00000E27: E095 LDI R25,0x05 Load immediate
- +00000E28: 940E089E CALL 0x0000089E Call subroutine
- +00000E2A: E28D LDI R24,0x2D Load immediate
- +00000E2B: E095 LDI R25,0x05 Load immediate
- +00000E2C: 940E089E CALL 0x0000089E Call subroutine
- +00000E2E: 940E0DBD CALL 0x00000DBD Call subroutine
- +00000E30: 940E0947 CALL 0x00000947 Call subroutine
- +00000E32: CFFB RJMP PC–0x0004 Relative jump
- +00000E33: 1B99 SUB R25,R25 Subtract without carry
- +00000E34: E079 LDI R23,0x09 Load immediate
- +00000E35: C004 RJMP PC+0x0005 Relative jump
- +00000E36: 1F99 ROL R25 Rotate Left Through Carry
- +00000E37: 1796 CP R25,R22 Compare
- +00000E38: F008 BRCS PC+0x02 Branch if carry set
- +00000E39: 1B96 SUB R25,R22 Subtract without carry
- +00000E3A: 1F88 ROL R24 Rotate Left Through Carry
- +00000E3B: 957A DEC R23 Decrement
- +00000E3C: F7C9 BRNE PC–0x06 Branch if not equal
- +00000E3D: 9580 COM R24 One‘s complement
- +00000E3E: 9508 RET Subroutine return
We obviously want to go into line 17. Let’s squeeze out the calls of 0x089E and go directly into 0x0DBD. This one is the “IsTheCodeIReceivedALevelCode” function, the function comparing the received password in order to determine which level has been reached.
- +00000DBD: 930F PUSH R16 Push register on stack
- +00000DBE: 931F PUSH R17 Push register on stack
- +00000DBF: 93CF PUSH R28 Push register on stack
- +00000DC0: 93DF PUSH R29 Push register on stack
- +00000DC1: B7CD IN R28,0x3D In from I/O location
- +00000DC2: B7DE IN R29,0x3E In from I/O location
- +00000DC3: 9761 SBIW R28,0x11 Subtract immediate from word
- +00000DC4: B60F IN R0,0x3F In from I/O location
- +00000DC5: 94F8 CLI Global Interrupt Disable
- +00000DC6: BFDE OUT 0x3E,R29 Out to I/O location
- +00000DC7: BE0F OUT 0x3F,R0 Out to I/O location
- +00000DC8: BFCD OUT 0x3D,R28 Out to I/O location
- +00000DC9: ED89 LDI R24,0xD9 Load immediate
- +00000DCA: E094 LDI R25,0x04 Load immediate
- +00000DCB: 940E089E CALL 0x0000089E Call subroutine
- +00000DCD: 018E MOVW R16,R28 Copy register pair
- +00000DCE: 5F0F SUBI R16,0xFF Subtract immediate
- +00000DCF: 4F1F SBCI R17,0xFF Subtract immediate with carry
- +00000DD0: E089 LDI R24,0x09 Load immediate
- +00000DD1: 01F8 MOVW R30,R16 Copy register pair
- +00000DD2: 9211 ST Z+,R1 Store indirect and postincrement
- +00000DD3: 958A DEC R24 Decrement
- +00000DD4: F7E9 BRNE PC–0x02 Branch if not equal
- +00000DD5: E069 LDI R22,0x09 Load immediate
- +00000DD6: E070 LDI R23,0x00 Load immediate
- +00000DD7: 01C8 MOVW R24,R16 Copy register pair
- +00000DD8: 940E08A9 CALL 0x000008A9 Call subroutine
- +00000DDA: E048 LDI R20,0x08 Load immediate
- +00000DDB: E050 LDI R21,0x00 Load immediate
- +00000DDC: 01B8 MOVW R22,R16 Copy register pair
- +00000DDD: 01CE MOVW R24,R28 Copy register pair
- +00000DDE: 960A ADIW R24,0x0A Add immediate to word
- +00000DDF: 940E09DE CALL 0x000009DE Call subroutine
- +00000DE1: EE67 LDI R22,0xE7 Load immediate
- +00000DE2: E074 LDI R23,0x04 Load immediate
- +00000DE3: 01CE MOVW R24,R28 Copy register pair
- +00000DE4: 960A ADIW R24,0x0A Add immediate to word
- +00000DE5: 940E0A1C CALL 0x00000A1C Call subroutine
- +00000DE7: 2B89 OR R24,R25 Logical OR
- +00000DE8: F021 BREQ PC+0x05 Branch if equal
- +00000DE9: 01C8 MOVW R24,R16 Copy register pair
- +00000DEA: 940E0226 CALL 0x00000226 Call subroutine
- +00000DEC: C023 RJMP PC+0x0024 Relative jump
- +00000DED: EF60 LDI R22,0xF0 Load immediate
- +00000DEE: E074 LDI R23,0x04 Load immediate
- +00000DEF: 01CE MOVW R24,R28 Copy register pair
- +00000DF0: 960A ADIW R24,0x0A Add immediate to word
- +00000DF1: 940E0A1C CALL 0x00000A1C Call subroutine
- +00000DF3: 2B89 OR R24,R25 Logical OR
- +00000DF4: F021 BREQ PC+0x05 Branch if equal
- +00000DF5: 01C8 MOVW R24,R16 Copy register pair
- +00000DF6: 940E043D CALL 0x0000043D Call subroutine
- +00000DF8: C017 RJMP PC+0x0018 Relative jump
- +00000DF9: EF69 LDI R22,0xF9 Load immediate
- +00000DFA: E074 LDI R23,0x04 Load immediate
- +00000DFB: 01CE MOVW R24,R28 Copy register pair
- +00000DFC: 960A ADIW R24,0x0A Add immediate to word
- +00000DFD: 940E0A1C CALL 0x00000A1C Call subroutine
- +00000DFF: 2B89 OR R24,R25 Logical OR
- +00000E00: F021 BREQ PC+0x05 Branch if equal
- +00000E01: 01C8 MOVW R24,R16 Copy register pair
- +00000E02: 940E02F7 CALL 0x000002F7 Call subroutine
- +00000E04: C00B RJMP PC+0x000C Relative jump
- +00000E05: E062 LDI R22,0x02 Load immediate
- +00000E06: E075 LDI R23,0x05 Load immediate
- +00000E07: 01CE MOVW R24,R28 Copy register pair
- +00000E08: 960A ADIW R24,0x0A Add immediate to word
- +00000E09: 940E0A1C CALL 0x00000A1C Call subroutine
- +00000E0B: 2B89 OR R24,R25 Logical OR
- +00000E0C: F019 BREQ PC+0x04 Branch if equal
- +00000E0D: 01C8 MOVW R24,R16 Copy register pair
- +00000E0E: 940E05CE CALL 0x000005CE Call subroutine
- +00000E10: 9661 ADIW R28,0x11 Add immediate to word
- +00000E11: B60F IN R0,0x3F In from I/O location
- +00000E12: 94F8 CLI Global Interrupt Disable
- +00000E13: BFDE OUT 0x3E,R29 Out to I/O location
- +00000E14: BE0F OUT 0x3F,R0 Out to I/O location
- +00000E15: BFCD OUT 0x3D,R28 Out to I/O location
- +00000E16: 91DF POP R29 Pop register from stack
- +00000E17: 91CF POP R28 Pop register from stack
- +00000E18: 911F POP R17 Pop register from stack
- +00000E19: 910F POP R16 Pop register from stack
- +00000E1A: 9508 RET Subroutine return
We need to squeeze out the call on line 15 and go directly 0x08A9 on line 27, the “recv” function. I won’t post the content of this one since it’s just copying what has been received into memory, but we certainly need to simulate the board’s behavior by filling out a few registers. By going into the “recv” function, we notice that it stores the received code at address 0x08E7 into data memory (SRAM). So after returning into 0x0DBD, we need to fill out the RAM by hand using our last passcode, “B1p!8lP!”, and continue the execution.
Then, the function 0x09DE is called : this one looks like the initialization of the permutations used to encrypt the received password into memory. The received password is encrypted by the function 0x09A9 and stored at address 0x08F0 into RAM.
We then see 4 differents calls to 0x0A1C, each one preceded by an address loading in r22-r23, and each one followed by a comparaison of r24 and r25 (the OR instruction) and a call to a function depending on the result. Here is the pseudo code :
- encrypted_received_password_address = 0x08F0
- encrypted_lvl1_password_address = 0x04E7
- encrypted_lvl2_password_address = 0x04F0
- encrypted_lvl3_password_address = 0x04f9
- encrypted_lvl4_password_address = 0x0502
- if (strcmp(encrypted_received_password_address,encrypted_lvl1_password_address)) {
- 0x0226()
- } else if (strcmp(encrypted_received_password_address,encrypted_lvl2_password_address)) {
- 0x043D()
- } else if (strcmp(encrypted_received_password_address,encrypted_lvl3_password_address)) {
- 0x02F7()
- } else if (strcmp(encrypted_received_password_address,encrypted_lvl4_password_address)) {
- 0x05CE()
- } else {
- return
- }
We obviously want to call the 0x05CE function, which is the function handling the last level. As a matter of fact, one could just have bypassed the 3 previous levels by copying the encrypted string at 0x0502 at address 0x08F0. Let’s jump into 0x05CE.
- +000005CE: 931F PUSH R17 Push register on stack
- +000005CF: 93CF PUSH R28 Push register on stack
- +000005D0: 93DF PUSH R29 Push register on stack
- +000005D1: B7CD IN R28,0x3D In from I/O location
- +000005D2: B7DE IN R29,0x3E In from I/O location
- +000005D3: 5AC1 SUBI R28,0xA1 Subtract immediate
- +000005D4: 40D3 SBCI R29,0x03 Subtract immediate with carry
- +000005D5: B60F IN R0,0x3F In from I/O location
- +000005D6: 94F8 CLI Global Interrupt Disable
- +000005D7: BFDE OUT 0x3E,R29 Out to I/O location
- +000005D8: BE0F OUT 0x3F,R0 Out to I/O location
- +000005D9: BFCD OUT 0x3D,R28 Out to I/O location
- +000005DA: E223 LDI R18,0x23 Load immediate
- +000005DB: E8E6 LDI R30,0x86 Load immediate
- +000005DC: E0F3 LDI R31,0x03 Load immediate
- +000005DD: 01DE MOVW R26,R28 Copy register pair
- +000005DE: 58A1 SUBI R26,0x81 Subtract immediate
- +000005DF: 4FBC SBCI R27,0xFC Subtract immediate with carry
- +000005E0: 9001 LD R0,Z+ Load indirect and postincrement
- +000005E1: 920D ST X+,R0 Store indirect and postincrement
- +000005E2: 952A DEC R18 Decrement
- +000005E3: F7E1 BRNE PC–0x03 Branch if not equal
- +000005E4: E727 LDI R18,0x77 Load immediate
- +000005E5: E4E1 LDI R30,0x41 Load immediate
- +000005E6: E0F4 LDI R31,0x04 Load immediate
- +000005E7: 01DE MOVW R26,R28 Copy register pair
- +000005E8: 5FA8 SUBI R26,0xF8 Subtract immediate
- +000005E9: 4FBC SBCI R27,0xFC Subtract immediate with carry
- +000005EA: 9001 LD R0,Z+ Load indirect and postincrement
- +000005EB: 920D ST X+,R0 Store indirect and postincrement
- +000005EC: 952A DEC R18 Decrement
- +000005ED: F7E1 BRNE PC–0x03 Branch if not equal
- +000005EE: E048 LDI R20,0x08 Load immediate
- +000005EF: E050 LDI R21,0x00 Load immediate
- +000005F0: 01BC MOVW R22,R24 Copy register pair
- +000005F1: 01CE MOVW R24,R28 Copy register pair
- +000005F2: 5F88 SUBI R24,0xF8 Subtract immediate
- +000005F3: 4F9D SBCI R25,0xFD Subtract immediate with carry
- +000005F4: 940E094E CALL 0x0000094E Call subroutine
- +000005F6: E746 LDI R20,0x76 Load immediate
- +000005F7: E050 LDI R21,0x00 Load immediate
- +000005F8: 01BE MOVW R22,R28 Copy register pair
- +000005F9: 5F68 SUBI R22,0xF8 Subtract immediate
- +000005FA: 4F7C SBCI R23,0xFC Subtract immediate with carry
- +000005FB: 01CE MOVW R24,R28 Copy register pair
- +000005FC: 5F88 SUBI R24,0xF8 Subtract immediate
- +000005FD: 4F9D SBCI R25,0xFD Subtract immediate with carry
- +000005FE: 940E09A9 CALL 0x000009A9 Call subroutine
- +00000600: 01BE MOVW R22,R28 Copy register pair
- +00000601: 5F68 SUBI R22,0xF8 Subtract immediate
- +00000602: 4F7C SBCI R23,0xFC Subtract immediate with carry
- +00000603: 01CE MOVW R24,R28 Copy register pair
- +00000604: 9601 ADIW R24,0x01 Add immediate to word
- +00000605: 940E0D1D CALL 0x00000D1D Call subroutine
- +00000607: 01CE MOVW R24,R28 Copy register pair
- +00000608: 9601 ADIW R24,0x01 Add immediate to word
- +00000609: 940E0D32 CALL 0x00000D32 Call subroutine
- +0000060B: 9700 SBIW R24,0x00 Subtract immediate from word
- +0000060C: F159 BREQ PC+0x2C Branch if equal
- +0000060D: E044 LDI R20,0x04 Load immediate
- +0000060E: E050 LDI R21,0x00 Load immediate
- +0000060F: 01BC MOVW R22,R24 Copy register pair
- +00000610: 01CE MOVW R24,R28 Copy register pair
- +00000611: 5F88 SUBI R24,0xF8 Subtract immediate
- +00000612: 4F9D SBCI R25,0xFD Subtract immediate with carry
- +00000613: 940E094E CALL 0x0000094E Call subroutine
- +00000615: E242 LDI R20,0x22 Load immediate
- +00000616: E050 LDI R21,0x00 Load immediate
- +00000617: 01BE MOVW R22,R28 Copy register pair
- +00000618: 5861 SUBI R22,0x81 Subtract immediate
- +00000619: 4F7C SBCI R23,0xFC Subtract immediate with carry
- +0000061A: 01CE MOVW R24,R28 Copy register pair
- +0000061B: 5F88 SUBI R24,0xF8 Subtract immediate
- +0000061C: 4F9D SBCI R25,0xFD Subtract immediate with carry
- +0000061D: 940E09A9 CALL 0x000009A9 Call subroutine
- +0000061F: 01CE MOVW R24,R28 Copy register pair
- +00000620: 5881 SUBI R24,0x81 Subtract immediate
- +00000621: 4F9C SBCI R25,0xFC Subtract immediate with carry
- +00000622: 940E089E CALL 0x0000089E Call subroutine
- +00000624: E585 LDI R24,0x55 Load immediate
- +00000625: E095 LDI R25,0x05 Load immediate
- +00000626: 940E089E CALL 0x0000089E Call subroutine
- +00000628: E210 LDI R17,0x20 Load immediate
- +00000629: B185 IN R24,0x05 In from I/O location
- +0000062A: 2781 EOR R24,R17 Exclusive OR
- +0000062B: B985 OUT 0x05,R24 Out to I/O location
- +0000062C: EB2F LDI R18,0xBF Load immediate
- +0000062D: E287 LDI R24,0x27 Load immediate
- +0000062E: E099 LDI R25,0x09 Load immediate
- +0000062F: 5021 SUBI R18,0x01 Subtract immediate
- +00000630: 4080 SBCI R24,0x00 Subtract immediate with carry
- +00000631: 4090 SBCI R25,0x00 Subtract immediate with carry
- +00000632: F7E1 BRNE PC–0x03 Branch if not equal
- +00000633: C000 RJMP PC+0x0001 Relative jump
- +00000634: 0000 NOP No operation
- +00000635: 940E0947 CALL 0x00000947 Call subroutine
- +00000637: CFF1 RJMP PC–0x000E Relative jump
- +00000638: 55CF SUBI R28,0x5F Subtract immediate
- +00000639: 4FDC SBCI R29,0xFC Subtract immediate with carry
- +0000063A: B60F IN R0,0x3F In from I/O location
- +0000063B: 94F8 CLI Global Interrupt Disable
- +0000063C: BFDE OUT 0x3E,R29 Out to I/O location
- +0000063D: BE0F OUT 0x3F,R0 Out to I/O location
- +0000063E: BFCD OUT 0x3D,R28 Out to I/O location
- +0000063F: 91DF POP R29 Pop register from stack
- +00000640: 91CF POP R28 Pop register from stack
- +00000641: 911F POP R17 Pop register from stack
- +00000642: 9508 RET Subroutine return
This function stores an encrypted content located at 0x0386 (our final answer to all our suffering) into 0x08BF, another one located at 0x0441 into 0x084E, and decrypts the content of 0x0441 using our encrypted level 4 password. This text is the one displayed when reaching the security level 4.
The function then calls out the 0x0D32 function, decrypts our final answer at 0x08BF using whatever has been done into 0x0D32, and prints out the result to the user via USB using the 0x089E function. 0x0D32 is a living nigthmare, responsible for the whole world’s misery, but it’s the last function we have to reverse in order to get our answer. Here is how it looks :
- +00000D32: 92EF PUSH R14 Push register on stack
- +00000D33: 92FF PUSH R15 Push register on stack
- +00000D34: 930F PUSH R16 Push register on stack
- +00000D35: 931F PUSH R17 Push register on stack
- +00000D36: 93CF PUSH R28 Push register on stack
- +00000D37: 93DF PUSH R29 Push register on stack
- +00000D38: 01EC MOVW R28,R24 Copy register pair
- +00000D39: 018C MOVW R16,R24 Copy register pair
- +00000D3A: 5F1E SUBI R17,0xFE Subtract immediate
- +00000D3B: 017C MOVW R14,R24 Copy register pair
- +00000D3C: EF8A LDI R24,0xFA Load immediate
- +00000D3D: 1AE8 SUB R14,R24 Subtract without carry
- +00000D3E: EF8D LDI R24,0xFD Load immediate
- +00000D3F: 0AF8 SBC R15,R24 Subtract with carry
- +00000D40: C066 RJMP PC+0x0067 Relative jump
- +00000D41: 2755 CLR R21 Clear Register
- +00000D42: FD47 SBRC R20,7 Skip if bit in register cleared
- +00000D43: 9550 COM R21 One‘s complement
- +00000D44: 2F65 MOV R22,R21 Copy register
- +00000D45: 2F75 MOV R23,R21 Copy register
- +00000D46: 01FA MOVW R30,R20 Copy register pair
- +00000D47: 97F0 SBIW R30,0x30 Subtract immediate from word
- +00000D48: 33E0 CPI R30,0x30 Compare with immediate
- +00000D49: 05F1 CPC R31,R1 Compare with carry
- +00000D4A: F008 BRCS PC+0x02 Branch if carry set
- +00000D4B: C05B RJMP PC+0x005C Relative jump
- +00000D4C: 5CEC SUBI R30,0xCC Subtract immediate
- +00000D4D: 4FFF SBCI R31,0xFF Subtract immediate with carry
- +00000D4E: 940C0E67 JMP 0x00000E67 Jump
- +00000D50: 01CE MOVW R24,R28 Copy register pair
- +00000D51: 940E0B42 CALL 0x00000B42 Call subroutine
- +00000D53: C053 RJMP PC+0x0054 Relative jump
- +00000D54: 01CE MOVW R24,R28 Copy register pair
- +00000D55: 940E0B62 CALL 0x00000B62 Call subroutine
- +00000D57: C04F RJMP PC+0x0050 Relative jump
- +00000D58: 01CE MOVW R24,R28 Copy register pair
- +00000D59: 940E0B82 CALL 0x00000B82 Call subroutine
- +00000D5B: C04B RJMP PC+0x004C Relative jump
- +00000D5C: 01CE MOVW R24,R28 Copy register pair
- +00000D5D: 940E0BA2 CALL 0x00000BA2 Call subroutine
- +00000D5F: C047 RJMP PC+0x0048 Relative jump
- +00000D60: 01CE MOVW R24,R28 Copy register pair
- +00000D61: 940E0BC6 CALL 0x00000BC6 Call subroutine
- +00000D63: C043 RJMP PC+0x0044 Relative jump
- +00000D64: 01CE MOVW R24,R28 Copy register pair
- +00000D65: 940E0BE6 CALL 0x00000BE6 Call subroutine
- +00000D67: C03F RJMP PC+0x0040 Relative jump
- +00000D68: 01CE MOVW R24,R28 Copy register pair
- +00000D69: 940E0C06 CALL 0x00000C06 Call subroutine
- +00000D6B: C03B RJMP PC+0x003C Relative jump
- +00000D6C: 01CE MOVW R24,R28 Copy register pair
- +00000D6D: 940E0C26 CALL 0x00000C26 Call subroutine
- +00000D6F: C037 RJMP PC+0x0038 Relative jump
- +00000D70: 01CE MOVW R24,R28 Copy register pair
- +00000D71: 940E0C33 CALL 0x00000C33 Call subroutine
- +00000D73: C033 RJMP PC+0x0034 Relative jump
- +00000D74: 01CE MOVW R24,R28 Copy register pair
- +00000D75: 940E0C60 CALL 0x00000C60 Call subroutine
- +00000D77: C02F RJMP PC+0x0030 Relative jump
- +00000D78: 01CE MOVW R24,R28 Copy register pair
- +00000D79: 940E0C84 CALL 0x00000C84 Call subroutine
- +00000D7B: C02B RJMP PC+0x002C Relative jump
- +00000D7C: 01CE MOVW R24,R28 Copy register pair
- +00000D7D: 940E0C8A CALL 0x00000C8A Call subroutine
- +00000D7F: C027 RJMP PC+0x0028 Relative jump
- +00000D80: 01CE MOVW R24,R28 Copy register pair
- +00000D81: 940E0CB1 CALL 0x00000CB1 Call subroutine
- +00000D83: C023 RJMP PC+0x0024 Relative jump
- +00000D84: 01CE MOVW R24,R28 Copy register pair
- +00000D85: 940E0C98 CALL 0x00000C98 Call subroutine
- +00000D87: C01F RJMP PC+0x0020 Relative jump
- +00000D88: 01CE MOVW R24,R28 Copy register pair
- +00000D89: 940E0CCA CALL 0x00000CCA Call subroutine
- +00000D8B: C01B RJMP PC+0x001C Relative jump
- +00000D8C: 01CE MOVW R24,R28 Copy register pair
- +00000D8D: 940E0CE3 CALL 0x00000CE3 Call subroutine
- +00000D8F: C017 RJMP PC+0x0018 Relative jump
- +00000D90: 01CE MOVW R24,R28 Copy register pair
- +00000D91: 940E0CFC CALL 0x00000CFC Call subroutine
- +00000D93: C013 RJMP PC+0x0014 Relative jump
- +00000D94: 01CE MOVW R24,R28 Copy register pair
- +00000D95: 940E0AFF CALL 0x00000AFF Call subroutine
- +00000D97: C00F RJMP PC+0x0010 Relative jump
- +00000D98: 01CE MOVW R24,R28 Copy register pair
- +00000D99: 940E0B0A CALL 0x00000B0A Call subroutine
- +00000D9B: C00B RJMP PC+0x000C Relative jump
- +00000D9C: 01CE MOVW R24,R28 Copy register pair
- +00000D9D: 940E0B1C CALL 0x00000B1C Call subroutine
- +00000D9F: C007 RJMP PC+0x0008 Relative jump
- +00000DA0: 01CE MOVW R24,R28 Copy register pair
- +00000DA1: 940E0B29 CALL 0x00000B29 Call subroutine
- +00000DA3: C003 RJMP PC+0x0004 Relative jump
- +00000DA4: 01CE MOVW R24,R28 Copy register pair
- +00000DA5: 940E0AC6 CALL 0x00000AC6 Call subroutine
- +00000DA7: 01D8 MOVW R26,R16 Copy register pair
- +00000DA8: 91ED LD R30,X+ Load indirect and postincrement
- +00000DA9: 91FC LD R31,X Load indirect
- +00000DAA: 9711 SBIW R26,0x01 Subtract immediate from word
- +00000DAB: 9141 LD R20,Z+ Load indirect and postincrement
- +00000DAC: 93ED ST X+,R30 Store indirect and postincrement
- +00000DAD: 93FC ST X,R31 Store indirect
- +00000DAE: 3342 CPI R20,0x32 Compare with immediate
- +00000DAF: F021 BREQ PC+0x05 Branch if equal
- +00000DB0: 01F7 MOVW R30,R14 Copy register pair
- +00000DB1: 8180 LDD R24,Z+0 Load indirect with displacement
- +00000DB2: FF82 SBRS R24,2 Skip if bit in register set
- +00000DB3: CF8D RJMP PC–0x0072 Relative jump
- +00000DB4: 01CE MOVW R24,R28 Copy register pair
- +00000DB5: 91DF POP R29 Pop register from stack
- +00000DB6: 91CF POP R28 Pop register from stack
- +00000DB7: 911F POP R17 Pop register from stack
- +00000DB8: 910F POP R16 Pop register from stack
- +00000DB9: 90FF POP R15 Pop register from stack
- +00000DBA: 90EF POP R14 Pop register from stack
- +00000DBB: 940C0AC6 JMP 0x00000AC6 Jump
Yes, 22 freaking subfunctions. Without going into too much details that would make some brains explode, this function basically looks over the string located at 0x0848 until it reaches the “2” at address 0x0882, and calls out functions according to the values stored into this string (and values stored at the beginning of the program below address 0x100).
(For those who like details) Turns out this subfunction seems to implement a tiny stack-based VM (stack location is around 0x0543), parsing instructions represented by the weird string I mentionned earlier (stored at 0x0848). It seems to handles ‘syscalls’ (like the 0x0CFC function, handling the I/O’s of the tiny VM), calling send/recv functions according to the instructions stored at 0x0848. Some bytes are incremented/decremented as values are poped/pushed on the stack (bytes at 0x0745, a stack pointer if you will), and the address of the PC is stored at 0x741. Long story short, the mini-stack is located around 0x0543, the VM’s internal registers are stored at 0x0741.
Here is the order of the different functions called by 0x0D32 :
- 0x0B0A (line 85) : Stores address of string “Security level 4…” into a specific address into RAM (on the mini-stack, at 0x0545).
- 0x0AFF (line 82) : Stores the letter “W” into RAM at address 0x0543 (comes from the instructions located at 0x0848, pushed on the mini-stack).
- 0x0CFC (line 79) : Compares the letter at address 0x0547 : If “W”, writes the content of the specific address to USB. If “R”, reads a password from USB. In our case, the first call to this function writes “Security level 4” to the user.
- 0x0AFF: Stores ‘0x04’ into RAM at address 0x0543.
- 0x0B0A : Stores address ‘0x08B2’ into 0x0545; this address is where the received “Last auth code” will be written.
- 0x0AFF : Stores the letter “R” into RAM at address 0x0547 (still coming from the instructions located at 0x0848).
- 0x0CFC : Compares the letter at address 0x0547; this time it’s “R”, so the function reads 0x04 (stored into the stack at 0x0543 by the 0x0AFF call) bytes from USB and stores them to 0x08B2. It also stores the length of the received password into 0x0543 (mini-stack). That’s where we intervene in memory, storing 04 (length of password) into 0x0543 and 0xDEADBEEF into 0x08B2.
- 0x0AFF : Stores 0x04 at address 0x0545.
- 0x0C33 (line 55) : Stores 0x01, 0x08 or 0x10 depending on the result of the comparaison of the bytes at 0x0543 and 0x0545 into 0x0747. At this stage, this means “is the password’s length we received is 4 ?”. In other words, the VM updates its internal register at 0x0747.
- 0x0CB1 (line 67) : Checks the result of C33, the function responsible for the comparaisons. If 0x01 is stored at 0x0747, then C33 returned true.
- 0x0B0A : Stores address ‘0x08B2’ into 0x0543 (address of the received auth code)
- 0x0B1C (line 88) : Stores the 2 first letters of the received password into 0x0543 (in our case, 0xDEAD).
- 0x0AFF : Stores 0x5337 (the string ‘S7‘) into 0x0545. Do you smell it ? Do you smell the 2 first letters of the passcode we’re looking for ?
- 0x0C33 : Again, this is the function doing the comparaison. Are the two first letters of the passcode we received equal to ‘S7’ ? Stores 0x01 into 0x0747 if it’s the case. We modify the memory accordingly during debug time in order to continue our course.
- 0x0CB1 : Checking the result of C33. If the two first letters of the received passcode are ‘S7’, you shall pass.
- 0x0B0A : Stores address ‘0x08B2’ into 0x0543.
- 0x0B1C : Stores 0x5337 into 0x0543.
- 0x0B0A : Stores address ‘0x08B2’ into 0x0545.
- 0x0AFF : Stores 0x02 at address 0x0547.
- 0x0B42 (line 31) : Stores address ‘0x08B4’ into 0x0545. This is the address of the two last letters of our passcode. The end should be near.
- 0x0B1C : Stores the two last letters of our passcode at 0x0545. This function used the address previously stored by B42, of course.
- 0x0C06 (line 49) : This is the function I should have considered useful from the beginning. It stores 0xEDD8 into 0x0543 in our case. I discovered later that it stores 2_first_bytes_of_passcode XOR 2_last_bytes_of_passcode… (0x5337 XOR 0xBEEF is 0xEDD8)
- 0x0AFF : Stores ‘0x3B03‘ into 0x0545. Those bytes come from the string located at 0x0848.
- 0x0C33 : Comparaison time. This is were I realized “shit, what did I miss ?”. I missed C06. 0x0EDD8 is obviously not equal to 0x3B03, the result we should have obtained in case of a good passcode. I went back and had a look at C06, and realized that I had to find two bytes that, when xored with the two first letters ‘0x5337’, gave me 0x3B03. 0x5337 XOR 0x3b03 is 0x6834, which corresponds to ‘h4‘ in the ASCII table. Adjusting the memory accordingly, preparing for a call to CB1… And praying.
- 0x0CB1 : Bingo ! 0x01 is stored in 0x0747, so CB1 lets us pass.
- 0x0B0A : Stores address ‘0x08B2’ into 0x0543.
Aaaaaaand we’re out D32. We wait for 94E and 9A9 to decrypt the encrypted content using our beautiful passcode ‘S7h4‘…
Testing it live…
- Access code: B1p!8lP!
- === Security Level 4 ===
- Last auth code: S7h4
- >> GG! Go to http://bit.ly/1ijlXpZ
The board blinks.
The URL redirects to a page on virtualab’s website.
And I discover that I’m not the first.
After a minute of denial, comes the minute of anger. And then the one of depression.
And then the one of acceptance.
This challenge has been especially fun to resolve, since it required a lot of various skills that I didn’t particularly possess a month ago (reading AVR assembler instructions was not part of my previous hobbies). It ripped away a part of my soul, but I would like to thank its creator for it.
Many thanks for the two weeks of hell fun I had on this challenge, and I hope you’ll keep them coming for next year 😉