Posts Tagged ‘avr’

NdH2k14: Collector badge challenge write-up

Monday, July 28th, 2014

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.

NdH2k14 Collector badge

NdH2k14 Collector badge

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.

  1. # python 
  2. *** Ndh2k14 serial console ***
  3.   waiting for device 
  4.   device detected, spawning console
  5. Welcome to Ndh2k14 Secure Chip.
  7. New here ? type access code ‘HelpPlz!’.
  9. Access code: HelpPlz!
  10. === Security level 1 ===
  11.  Decrypt this and use it as an access code to enter level 2: 
  13.     DyHmXoHv
  15. 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.

  1. code = “DyHmXoHv”
  2. def bruteforce():
  3.     charset = “0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”
  4.     for i in range(len(charset)):
  5.         nc = 
  6.         for c in code:
  7.             j = charset.index(c)
  8.             nc += charset[(+ i) % len(charset)]
  9.         yield(nc)
  10. for attempt in bruteforce():
  11.     self.badge.send(attempt)

After a few tries, the code unlocking the next level comes up :

  1. Access code: AvEjUlEs
  2. === 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 :

  1. === Security level 2 ===
  2.   Hope you have your RSA token 
  4. Enter OTP (number, KEY and IV are 4 bytes long):
  6. IV: 5748524f
  7. RC4(RANDOM, IV+KEY): 9937c0cf5244188d
  10. ANSWER: 
  11. Timeout reached.
  13. 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 :

  1.     (“e7ea483d5d68ab54”,“TlYk”),
  2.     (“55c1ab1aa03129c5”,“u8J7”),
  3.     (“cf473fbe817ca98b”,“0zt6”),
  4.     (“904a4d1afdb1bff7”,“aDHv”),
  5.     (“d9cebc9e6a0da621”,“qkZu”),
  6.     (“06937a2ab43edef8”,“cNcc”),
  7.     (“3f05b477b874e824”,“Nv3H”),
  8.     (“8c1aeabcde6adbaf”,“7LGM”),
  9.     (“5617b6bcaadbad26”,“Qfvk”),
  10.     (‘d662e9854ec7b192’,“fUYM”),
  11.     (‘5d4f5d59a3e6d4bb’,“k8D6”),
  12.     (‘1faeaf3384eb70fb’,‘w5mt’),
  13.     (‘2b9abbb3af1929b5’,‘zLaF’),
  14.     (“5963ae9f728cb753”,“5BRO”),
  15.     (“b89a660d907bacc6”,“n2Ai”),
  16.     (“9faabe2ceebb24bc”,“0M1K”),
  17.     (“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 :

  1. charset = “0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”
  2. def RC4(data, key):
  3.     x = 0
  4.     s = range(256)
  5.     for i in range(256):
  6.         x = (+ s[i] + ord(key[% len(key)])) % 256
  7.         s[i], s[x] = s[x], s[i]
  8.     x = y = 0
  9.     out = “”
  10.     for c in data:
  11.         x = (+ 1) % 256
  12.         y = (+ s[x]) % 256
  13.         s[x], s[y] = s[y], s[x]
  14.         out += chr(ord(c) ^ s[(s[x] + s[y]) % 256])
  15.     return out
  17. def brute():
  18.     return (.join(candidate)
  19.         for candidate in itertools.chain.from_iterable(itertools.product(charset, repeat=i)
  20.         for i in range(4,5)))
  22. encrypted = (“e7ea483d5d68ab54”,“TlYk”)
  24. for key in brute():
  25.     (encoded,iv) = encrypted
  26.     encoded = encoded.decode(‘hex’)
  27.     decoded = RC4(encoded, “%s%s” % (iv, key))
  28.     if all(in charset for e in decoded):
  29.         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 :

  1. Access code: Pu1lD0wn
  2. === Security level 3 ===
  3.   Badge phone home, obviously.
  5. 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.

  1. for bRequest in range(256):
  2.     try:
  3.         ret = dev.ctrl_transfer(0xC0, bRequest, 0, 0, 1)
  4.         print “bRequest “,bRequest
  5.         print ret
  6.         sleep(0.02)
  7.     except:
  8.         pass

0xC0 is the request type IN. Here is the trimmed output :

  1. bRequest  0
  2. array(‘B’)
  3. […]
  4. bRequest  97
  5. array(‘B’, [111])
  6. bRequest  98
  7. array(‘B’, [80])
  8. bRequest  99
  9. array(‘B’, [78])
  10. bRequest  100
  11. array(‘B’)
  12. […]
  13. bRequest  255
  14. array(‘B’)

Seems like the board is talking through 97-99. I then tried printing what was sent :

  1. for bRequest in range(97,100):
  2.     try:
  3.         ret = dev.ctrl_transfer(0xC1, bRequest, 0, 0, 4000)
  4.         print “bRequest “,bRequest
  5.         print .join(chr(x) for x in ret)
  6.         sleep(0.02)
  7.     except:
  8.         pass

Here is the output :

  1. bRequest  97
  2. ZK
  3.  |��a�:�i�‘%}���1]>N���S@�k������x[��B}5��6jy��E�
  4. /�Gb���{���bH�}�]��b�/>>���:�����    �����M����I5�>
  5. bRequest  98
  6. Pu1lD0wn
  7. k�����>wl�Z��A5Q�.�<�_ardware much electroni��8_�榌����)�������K��v�f*9i�>qe���[�/�”��
  8.      J
  9. bRequest  99
  10. 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:

  1. for i in range(200):
  2.         d = device.ctrl_transfer(0xC1, 97, 0, 0, 4000)
  3.         ret = .join(chr(x) for x in d)
  4.         print ret

From what I was seeing, I was confirming that it was indeed the RAM :

  1. # python | strings
  2. 1]>N
  3. <“$[
  4. wIfT}
  5. Pu1lD0wn
  7. ode: s c hardware much electroni
  8. bIJ2+
  9. 1s`I
  10. MJ$<
  11.     x7X
  12. f:ErH
  13. 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%02x%02x%02x%02x%02x%02x%02x%02x
  14. IV: 
  15. RC4(RANDOM, IV+KEY): 
  16. ANSWER: 
  17. Timeout reached.
  18. =so1
  19. %02X%02X%02X%02X%02X%02X%02X%02X
  20. Access code: 
  21. d@l4
  22. Welcome to Ndh2k14 Secure Chip.
  23. […]

What if I sent the last code I just obtained ? Would the next access code be decrypted in RAM ? Guess what 🙂

  1. device.ctrl_transfer(bmRequestTypeOut, 0x61, 0, 0, “Pu1lD0wn”)
  2.     for i in range(200):
  3.         d = device.ctrl_transfer(0xC1, 97, 0, 0, 4000)
  4.         ret = .join(chr(x) for x in d)
  5.         print ret

  1. # python | strings
  2. 1lD0wn
  3. 1]>N
  4. <“$[
  5. === Se
  6. curity level 3 ===
  7.  – Badge phone home, obviously.
  8. Next access code is: B1p!8lP!
  9. Pu1lD0wn
  10. Z(Pu1lD0wn
  12. === Secu h
  13. ardware much electroni
  14. bIJ2+
  15. 1s`I
  16. MJ$<
  17.     x7X
  18. f:ErH
  19. 3456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%02x%02x%02x%02x%02x%02x%02x%02x
  20. IV: 
  21. 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 :

  1. Access code: B1p!8lP!
  2. === Security Level 4 ===
  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 :

  1. 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.

IDA samples

IDA samples

Here’s an overall diagram of the whole flash program:

ndh2k14 flash memory

ndh2k14 flash memory

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).

  1. +0000081F:   E184        LDI       R24,0x14       Load immediate
  2. +00000820:   B199        IN        R25,0x09       In from I/O location
  3. +00000821:   7194        ANDI      R25,0x14       Logical AND with immediate
  4. +00000822:   F431        BRNE      PC+0x07        Branch if not equal
  5. +00000823:   5081        SUBI      R24,0x01       Subtract immediate
  6. +00000824:   F7D9        BRNE      PC0x04        Branch if not equal
  7. +00000825:   92100580    STS       0x0580,R1      Store direct to data space
  8. +00000827:   9210057A    STS       0x057A,R1      Store direct to data space
  9. +00000829:   91DF        POP       R29            Pop register from stack
  10. +0000082A:   91CF        POP       R28            Pop register from stack
  11. +0000082B:   9508        RET                      Subroutine return

After 20 loops, we RET into the function I called “DidIReceivedSomething”, a.k.a 0x082C :

  1. +0000082C:   930F        PUSH      R16            Push register on stack
  2. +0000082D:   931F        PUSH      R17            Push register on stack
  3. +0000082E:   93CF        PUSH      R28            Push register on stack
  4. +0000082F:   93DF        PUSH      R29            Push register on stack
  5. +00000830:   D000        RCALL     PC+0x0001      Relative call subroutine
  6. +00000831:   D000        RCALL     PC+0x0001      Relative call subroutine
  7. +00000832:   B7CD        IN        R28,0x3D       In from I/O location
  8. +00000833:   B7DE        IN        R29,0x3E       In from I/O location
  9. +00000834:   C00A        RJMP      PC+0x000B      Relative jump
  10. +00000835:   836B        STD       Y+3,R22        Store indirect with displacement
  11. +00000836:   837C        STD       Y+4,R23        Store indirect with displacement
  12. +00000837:   8389        STD       Y+1,R24        Store indirect with displacement
  13. +00000838:   839A        STD       Y+2,R25        Store indirect with displacement
  14. +00000839:   940E070B    CALL      0x0000070B     Call subroutine
  15. +0000083B:   819A        LDD       R25,Y+2        Load indirect with displacement
  16. +0000083C:   8189        LDD       R24,Y+1        Load indirect with displacement
  17. +0000083D:   817C        LDD       R23,Y+4        Load indirect with displacement
  18. +0000083E:   816B        LDD       R22,Y+3        Load indirect with displacement
  19. +0000083F:   9120055A    LDS       R18,0x055A     Load direct from data space
  20. +00000841:   9130055B    LDS       R19,0x055B     Load direct from data space
  21. +00000843:   2B23        OR        R18,R19        Logical OR
  22. +00000844:   F781        BRNE      PC0x0F        Branch if not equal
  23. +00000845:   018B        MOVW      R16,R22        Copy register pair
  24. +00000846:   3069        CPI       R22,0x09       Compare with immediate
  25. +00000847:   0571        CPC       R23,R1         Compare with carry
  26. +00000848:   F010        BRCS      PC+0x03        Branch if carry set
  27. +00000849:   E008        LDI       R16,0x08       Load immediate
  28. +0000084A:   E010        LDI       R17,0x00       Load immediate
  29. +0000084B:   E021        LDI       R18,0x01       Load immediate
  30. +0000084C:   E030        LDI       R19,0x00       Load immediate
  31. +0000084D:   9330055B    STS       0x055B,R19     Store direct to data space
  32. +0000084F:   9320055A    STS       0x055A,R18     Store direct to data space
  33. +00000851:   E222        LDI       R18,0x22       Load immediate
  34. +00000852:   E031        LDI       R19,0x01       Load immediate
  35. +00000853:   01A8        MOVW      R20,R16        Copy register pair
  36. +00000854:   01BC        MOVW      R22,R24        Copy register pair
  37. +00000855:   01C9        MOVW      R24,R18        Copy register pair
  38. +00000856:   940E0E6D    CALL      0x00000E6D     Call subroutine
  39. +00000858:   9300055C    STS       0x055C,R16     Store direct to data space
  40. +0000085A:   C002        RJMP      PC+0x0003      Relative jump
  41. +0000085B:   940E070B    CALL      0x0000070B     Call subroutine
  42. +0000085D:   9180055A    LDS       R24,0x055A     Load direct from data space
  43. +0000085F:   9190055B    LDS       R25,0x055B     Load direct from data space
  44. +00000861:   9702        SBIW      R24,0x02       Subtract immediate from word
  45. +00000862:   F7C1        BRNE      PC0x07        Branch if not equal
  46. +00000863:   C002        RJMP      PC+0x0003      Relative jump
  47. +00000864:   940E070B    CALL      0x0000070B     Call subroutine
  48. +00000866:   9180056E    LDS       R24,0x056E     Load direct from data space
  49. +00000868:   FF84        SBRS      R24,4          Skip if bit in register set
  50. +00000869:   CFFA        RJMP      PC0x0005      Relative jump
  51. +0000086A:   9210055B    STS       0x055B,R1      Store direct to data space
  52. +0000086C:   9210055A    STS       0x055A,R1      Store direct to data space
  53. +0000086E:   E081        LDI       R24,0x01       Load immediate
  54. +0000086F:   E090        LDI       R25,0x00       Load immediate
  55. +00000870:   900F        POP       R0             Pop register from stack
  56. +00000871:   900F        POP       R0             Pop register from stack
  57. +00000872:   900F        POP       R0             Pop register from stack
  58. +00000873:   900F        POP       R0             Pop register from stack
  59. +00000874:   91DF        POP       R29            Pop register from stack
  60. +00000875:   91CF        POP       R28            Pop register from stack
  61. +00000876:   911F        POP       R17            Pop register from stack
  62. +00000877:   910F        POP       R16            Pop register from stack
  63. +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.

  1. +00000879:   92EF        PUSH      R14            Push register on stack
  2. +0000087A:   92FF        PUSH      R15            Push register on stack
  3. +0000087B:   930F        PUSH      R16            Push register on stack
  4. +0000087C:   931F        PUSH      R17            Push register on stack
  5. +0000087D:   93CF        PUSH      R28            Push register on stack
  6. +0000087E:   93DF        PUSH      R29            Push register on stack
  7. +0000087F:   018C        MOVW      R16,R24        Copy register pair
  8. +00000880:   01EB        MOVW      R28,R22        Copy register pair
  9. +00000881:   C011        RJMP      PC+0x0012      Relative jump
  10. +00000882:   30C8        CPI       R28,0x08       Compare with immediate
  11. +00000883:   05D1        CPC       R29,R1         Compare with carry
  12. +00000884:   F028        BRCS      PC+0x06        Branch if carry set
  13. +00000885:   9728        SBIW      R28,0x08       Subtract immediate from word
  14. +00000886:   E088        LDI       R24,0x08       Load immediate
  15. +00000887:   2EE8        MOV       R14,R24        Copy register
  16. +00000888:   2CF1        MOV       R15,R1         Copy register
  17. +00000889:   C003        RJMP      PC+0x0004      Relative jump
  18. +0000088A:   017E        MOVW      R14,R28        Copy register pair
  19. +0000088B:   E0C0        LDI       R28,0x00       Load immediate
  20. +0000088C:   E0D0        LDI       R29,0x00       Load immediate
  21. +0000088D:   01B7        MOVW      R22,R14        Copy register pair
  22. +0000088E:   01C8        MOVW      R24,R16        Copy register pair
  23. +0000088F:   940E082C    CALL      0x0000082C     Call subroutine
  24. +00000891:   0D0E        ADD       R16,R14        Add without carry
  25. +00000892:   1D1F        ADC       R17,R15        Add with carry
  26. +00000893:   9720        SBIW      R28,0x00       Subtract immediate from word
  27. +00000894:   F769        BRNE      PC0x12        Branch if not equal
  28. +00000895:   E081        LDI       R24,0x01       Load immediate
  29. +00000896:   E090        LDI       R25,0x00       Load immediate
  30. +00000897:   91DF        POP       R29            Pop register from stack
  31. +00000898:   91CF        POP       R28            Pop register from stack
  32. +00000899:   911F        POP       R17            Pop register from stack
  33. +0000089A:   910F        POP       R16            Pop register from stack
  34. +0000089B:   90FF        POP       R15            Pop register from stack
  35. +0000089C:   90EF        POP       R14            Pop register from stack
  36. +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
  1. +00000E1B:   E28F        LDI       R24,0x2F       Load immediate
  2. +00000E1C:   E795        LDI       R25,0x75       Load immediate
  3. +00000E1D:   9701        SBIW      R24,0x01       Subtract immediate from word
  4. +00000E1E:   F7F1        BRNE      PC0x01        Branch if not equal
  5. +00000E1F:   C000        RJMP      PC+0x0001      Relative jump
  6. +00000E20:   0000        NOP                      No operation
  7. +00000E21:   9A25        SBI       0x04,5         Set bit in I/register
  8. +00000E22:   E088        LDI       R24,0x08       Load immediate
  9. +00000E23:   B983        OUT       0x03,R24       Out to I/O location
  10. +00000E24:   940E0937    CALL      0x00000937     Call subroutine
  11. +00000E26:   E08B        LDI       R24,0x0B       Load immediate
  12. +00000E27:   E095        LDI       R25,0x05       Load immediate
  13. +00000E28:   940E089E    CALL      0x0000089E     Call subroutine
  14. +00000E2A:   E28D        LDI       R24,0x2D       Load immediate
  15. +00000E2B:   E095        LDI       R25,0x05       Load immediate
  16. +00000E2C:   940E089E    CALL      0x0000089E     Call subroutine
  17. +00000E2E:   940E0DBD    CALL      0x00000DBD     Call subroutine
  18. +00000E30:   940E0947    CALL      0x00000947     Call subroutine
  19. +00000E32:   CFFB        RJMP      PC0x0004      Relative jump
  20. +00000E33:   1B99        SUB       R25,R25        Subtract without carry
  21. +00000E34:   E079        LDI       R23,0x09       Load immediate
  22. +00000E35:   C004        RJMP      PC+0x0005      Relative jump
  23. +00000E36:   1F99        ROL       R25            Rotate Left Through Carry
  24. +00000E37:   1796        CP        R25,R22        Compare
  25. +00000E38:   F008        BRCS      PC+0x02        Branch if carry set
  26. +00000E39:   1B96        SUB       R25,R22        Subtract without carry
  27. +00000E3A:   1F88        ROL       R24            Rotate Left Through Carry
  28. +00000E3B:   957A        DEC       R23            Decrement
  29. +00000E3C:   F7C9        BRNE      PC0x06        Branch if not equal
  30. +00000E3D:   9580        COM       R24            Ones complement
  31. +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.

  1. +00000DBD:   930F        PUSH      R16            Push register on stack
  2. +00000DBE:   931F        PUSH      R17            Push register on stack
  3. +00000DBF:   93CF        PUSH      R28            Push register on stack
  4. +00000DC0:   93DF        PUSH      R29            Push register on stack
  5. +00000DC1:   B7CD        IN        R28,0x3D       In from I/O location
  6. +00000DC2:   B7DE        IN        R29,0x3E       In from I/O location
  7. +00000DC3:   9761        SBIW      R28,0x11       Subtract immediate from word
  8. +00000DC4:   B60F        IN        R0,0x3F        In from I/O location
  9. +00000DC5:   94F8        CLI                      Global Interrupt Disable
  10. +00000DC6:   BFDE        OUT       0x3E,R29       Out to I/O location
  11. +00000DC7:   BE0F        OUT       0x3F,R0        Out to I/O location
  12. +00000DC8:   BFCD        OUT       0x3D,R28       Out to I/O location
  13. +00000DC9:   ED89        LDI       R24,0xD9       Load immediate
  14. +00000DCA:   E094        LDI       R25,0x04       Load immediate
  15. +00000DCB:   940E089E    CALL      0x0000089E     Call subroutine
  16. +00000DCD:   018E        MOVW      R16,R28        Copy register pair
  17. +00000DCE:   5F0F        SUBI      R16,0xFF       Subtract immediate
  18. +00000DCF:   4F1F        SBCI      R17,0xFF       Subtract immediate with carry
  19. +00000DD0:   E089        LDI       R24,0x09       Load immediate
  20. +00000DD1:   01F8        MOVW      R30,R16        Copy register pair
  21. +00000DD2:   9211        ST        Z+,R1          Store indirect and postincrement
  22. +00000DD3:   958A        DEC       R24            Decrement
  23. +00000DD4:   F7E9        BRNE      PC0x02        Branch if not equal
  24. +00000DD5:   E069        LDI       R22,0x09       Load immediate
  25. +00000DD6:   E070        LDI       R23,0x00       Load immediate
  26. +00000DD7:   01C8        MOVW      R24,R16        Copy register pair
  27. +00000DD8:   940E08A9    CALL      0x000008A9     Call subroutine
  28. +00000DDA:   E048        LDI       R20,0x08       Load immediate
  29. +00000DDB:   E050        LDI       R21,0x00       Load immediate
  30. +00000DDC:   01B8        MOVW      R22,R16        Copy register pair
  31. +00000DDD:   01CE        MOVW      R24,R28        Copy register pair
  32. +00000DDE:   960A        ADIW      R24,0x0A       Add immediate to word
  33. +00000DDF:   940E09DE    CALL      0x000009DE     Call subroutine
  34. +00000DE1:   EE67        LDI       R22,0xE7       Load immediate
  35. +00000DE2:   E074        LDI       R23,0x04       Load immediate
  36. +00000DE3:   01CE        MOVW      R24,R28        Copy register pair
  37. +00000DE4:   960A        ADIW      R24,0x0A       Add immediate to word
  38. +00000DE5:   940E0A1C    CALL      0x00000A1C     Call subroutine
  39. +00000DE7:   2B89        OR        R24,R25        Logical OR
  40. +00000DE8:   F021        BREQ      PC+0x05        Branch if equal
  41. +00000DE9:   01C8        MOVW      R24,R16        Copy register pair
  42. +00000DEA:   940E0226    CALL      0x00000226     Call subroutine
  43. +00000DEC:   C023        RJMP      PC+0x0024      Relative jump
  44. +00000DED:   EF60        LDI       R22,0xF0       Load immediate
  45. +00000DEE:   E074        LDI       R23,0x04       Load immediate
  46. +00000DEF:   01CE        MOVW      R24,R28        Copy register pair
  47. +00000DF0:   960A        ADIW      R24,0x0A       Add immediate to word
  48. +00000DF1:   940E0A1C    CALL      0x00000A1C     Call subroutine
  49. +00000DF3:   2B89        OR        R24,R25        Logical OR
  50. +00000DF4:   F021        BREQ      PC+0x05        Branch if equal
  51. +00000DF5:   01C8        MOVW      R24,R16        Copy register pair
  52. +00000DF6:   940E043D    CALL      0x0000043D     Call subroutine
  53. +00000DF8:   C017        RJMP      PC+0x0018      Relative jump
  54. +00000DF9:   EF69        LDI       R22,0xF9       Load immediate
  55. +00000DFA:   E074        LDI       R23,0x04       Load immediate
  56. +00000DFB:   01CE        MOVW      R24,R28        Copy register pair
  57. +00000DFC:   960A        ADIW      R24,0x0A       Add immediate to word
  58. +00000DFD:   940E0A1C    CALL      0x00000A1C     Call subroutine
  59. +00000DFF:   2B89        OR        R24,R25        Logical OR
  60. +00000E00:   F021        BREQ      PC+0x05        Branch if equal
  61. +00000E01:   01C8        MOVW      R24,R16        Copy register pair
  62. +00000E02:   940E02F7    CALL      0x000002F7     Call subroutine
  63. +00000E04:   C00B        RJMP      PC+0x000C      Relative jump
  64. +00000E05:   E062        LDI       R22,0x02       Load immediate
  65. +00000E06:   E075        LDI       R23,0x05       Load immediate
  66. +00000E07:   01CE        MOVW      R24,R28        Copy register pair
  67. +00000E08:   960A        ADIW      R24,0x0A       Add immediate to word
  68. +00000E09:   940E0A1C    CALL      0x00000A1C     Call subroutine
  69. +00000E0B:   2B89        OR        R24,R25        Logical OR
  70. +00000E0C:   F019        BREQ      PC+0x04        Branch if equal
  71. +00000E0D:   01C8        MOVW      R24,R16        Copy register pair
  72. +00000E0E:   940E05CE    CALL      0x000005CE     Call subroutine
  73. +00000E10:   9661        ADIW      R28,0x11       Add immediate to word
  74. +00000E11:   B60F        IN        R0,0x3F        In from I/O location
  75. +00000E12:   94F8        CLI                      Global Interrupt Disable
  76. +00000E13:   BFDE        OUT       0x3E,R29       Out to I/O location
  77. +00000E14:   BE0F        OUT       0x3F,R0        Out to I/O location
  78. +00000E15:   BFCD        OUT       0x3D,R28       Out to I/O location
  79. +00000E16:   91DF        POP       R29            Pop register from stack
  80. +00000E17:   91CF        POP       R28            Pop register from stack
  81. +00000E18:   911F        POP       R17            Pop register from stack
  82. +00000E19:   910F        POP       R16            Pop register from stack
  83. +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.

0x0DBD memory

0x0DBD memory

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 :

  1. encrypted_received_password_address = 0x08F0
  2. encrypted_lvl1_password_address = 0x04E7
  3. encrypted_lvl2_password_address = 0x04F0
  4. encrypted_lvl3_password_address = 0x04f9
  5. encrypted_lvl4_password_address = 0x0502
  7. if (strcmp(encrypted_received_password_address,encrypted_lvl1_password_address)) {
  8.     0x0226()
  9. } else if (strcmp(encrypted_received_password_address,encrypted_lvl2_password_address)) {
  10.     0x043D()
  11. } else if (strcmp(encrypted_received_password_address,encrypted_lvl3_password_address)) {
  12.     0x02F7()
  13. } else if (strcmp(encrypted_received_password_address,encrypted_lvl4_password_address)) {
  14.     0x05CE()
  15. } else {
  16.     return
  17. }

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.

  1. +000005CE:   931F        PUSH      R17            Push register on stack
  2. +000005CF:   93CF        PUSH      R28            Push register on stack
  3. +000005D0:   93DF        PUSH      R29            Push register on stack
  4. +000005D1:   B7CD        IN        R28,0x3D       In from I/O location
  5. +000005D2:   B7DE        IN        R29,0x3E       In from I/O location
  6. +000005D3:   5AC1        SUBI      R28,0xA1       Subtract immediate
  7. +000005D4:   40D3        SBCI      R29,0x03       Subtract immediate with carry
  8. +000005D5:   B60F        IN        R0,0x3F        In from I/O location
  9. +000005D6:   94F8        CLI                      Global Interrupt Disable
  10. +000005D7:   BFDE        OUT       0x3E,R29       Out to I/O location
  11. +000005D8:   BE0F        OUT       0x3F,R0        Out to I/O location
  12. +000005D9:   BFCD        OUT       0x3D,R28       Out to I/O location
  13. +000005DA:   E223        LDI       R18,0x23       Load immediate
  14. +000005DB:   E8E6        LDI       R30,0x86       Load immediate
  15. +000005DC:   E0F3        LDI       R31,0x03       Load immediate
  16. +000005DD:   01DE        MOVW      R26,R28        Copy register pair
  17. +000005DE:   58A1        SUBI      R26,0x81       Subtract immediate
  18. +000005DF:   4FBC        SBCI      R27,0xFC       Subtract immediate with carry
  19. +000005E0:   9001        LD        R0,Z+          Load indirect and postincrement
  20. +000005E1:   920D        ST        X+,R0          Store indirect and postincrement
  21. +000005E2:   952A        DEC       R18            Decrement
  22. +000005E3:   F7E1        BRNE      PC0x03        Branch if not equal
  23. +000005E4:   E727        LDI       R18,0x77       Load immediate
  24. +000005E5:   E4E1        LDI       R30,0x41       Load immediate
  25. +000005E6:   E0F4        LDI       R31,0x04       Load immediate
  26. +000005E7:   01DE        MOVW      R26,R28        Copy register pair
  27. +000005E8:   5FA8        SUBI      R26,0xF8       Subtract immediate
  28. +000005E9:   4FBC        SBCI      R27,0xFC       Subtract immediate with carry
  29. +000005EA:   9001        LD        R0,Z+          Load indirect and postincrement
  30. +000005EB:   920D        ST        X+,R0          Store indirect and postincrement
  31. +000005EC:   952A        DEC       R18            Decrement
  32. +000005ED:   F7E1        BRNE      PC0x03        Branch if not equal
  33. +000005EE:   E048        LDI       R20,0x08       Load immediate
  34. +000005EF:   E050        LDI       R21,0x00       Load immediate
  35. +000005F0:   01BC        MOVW      R22,R24        Copy register pair
  36. +000005F1:   01CE        MOVW      R24,R28        Copy register pair
  37. +000005F2:   5F88        SUBI      R24,0xF8       Subtract immediate
  38. +000005F3:   4F9D        SBCI      R25,0xFD       Subtract immediate with carry
  39. +000005F4:   940E094E    CALL      0x0000094E     Call subroutine
  40. +000005F6:   E746        LDI       R20,0x76       Load immediate
  41. +000005F7:   E050        LDI       R21,0x00       Load immediate
  42. +000005F8:   01BE        MOVW      R22,R28        Copy register pair
  43. +000005F9:   5F68        SUBI      R22,0xF8       Subtract immediate
  44. +000005FA:   4F7C        SBCI      R23,0xFC       Subtract immediate with carry
  45. +000005FB:   01CE        MOVW      R24,R28        Copy register pair
  46. +000005FC:   5F88        SUBI      R24,0xF8       Subtract immediate
  47. +000005FD:   4F9D        SBCI      R25,0xFD       Subtract immediate with carry
  48. +000005FE:   940E09A9    CALL      0x000009A9     Call subroutine
  49. +00000600:   01BE        MOVW      R22,R28        Copy register pair
  50. +00000601:   5F68        SUBI      R22,0xF8       Subtract immediate
  51. +00000602:   4F7C        SBCI      R23,0xFC       Subtract immediate with carry
  52. +00000603:   01CE        MOVW      R24,R28        Copy register pair
  53. +00000604:   9601        ADIW      R24,0x01       Add immediate to word
  54. +00000605:   940E0D1D    CALL      0x00000D1D     Call subroutine
  55. +00000607:   01CE        MOVW      R24,R28        Copy register pair
  56. +00000608:   9601        ADIW      R24,0x01       Add immediate to word
  57. +00000609:   940E0D32    CALL      0x00000D32     Call subroutine
  58. +0000060B:   9700        SBIW      R24,0x00       Subtract immediate from word
  59. +0000060C:   F159        BREQ      PC+0x2C        Branch if equal
  60. +0000060D:   E044        LDI       R20,0x04       Load immediate
  61. +0000060E:   E050        LDI       R21,0x00       Load immediate
  62. +0000060F:   01BC        MOVW      R22,R24        Copy register pair
  63. +00000610:   01CE        MOVW      R24,R28        Copy register pair
  64. +00000611:   5F88        SUBI      R24,0xF8       Subtract immediate
  65. +00000612:   4F9D        SBCI      R25,0xFD       Subtract immediate with carry
  66. +00000613:   940E094E    CALL      0x0000094E     Call subroutine
  67. +00000615:   E242        LDI       R20,0x22       Load immediate
  68. +00000616:   E050        LDI       R21,0x00       Load immediate
  69. +00000617:   01BE        MOVW      R22,R28        Copy register pair
  70. +00000618:   5861        SUBI      R22,0x81       Subtract immediate
  71. +00000619:   4F7C        SBCI      R23,0xFC       Subtract immediate with carry
  72. +0000061A:   01CE        MOVW      R24,R28        Copy register pair
  73. +0000061B:   5F88        SUBI      R24,0xF8       Subtract immediate
  74. +0000061C:   4F9D        SBCI      R25,0xFD       Subtract immediate with carry
  75. +0000061D:   940E09A9    CALL      0x000009A9     Call subroutine
  76. +0000061F:   01CE        MOVW      R24,R28        Copy register pair
  77. +00000620:   5881        SUBI      R24,0x81       Subtract immediate
  78. +00000621:   4F9C        SBCI      R25,0xFC       Subtract immediate with carry
  79. +00000622:   940E089E    CALL      0x0000089E     Call subroutine
  80. +00000624:   E585        LDI       R24,0x55       Load immediate
  81. +00000625:   E095        LDI       R25,0x05       Load immediate
  82. +00000626:   940E089E    CALL      0x0000089E     Call subroutine
  83. +00000628:   E210        LDI       R17,0x20       Load immediate
  84. +00000629:   B185        IN        R24,0x05       In from I/O location
  85. +0000062A:   2781        EOR       R24,R17        Exclusive OR
  86. +0000062B:   B985        OUT       0x05,R24       Out to I/O location
  87. +0000062C:   EB2F        LDI       R18,0xBF       Load immediate
  88. +0000062D:   E287        LDI       R24,0x27       Load immediate
  89. +0000062E:   E099        LDI       R25,0x09       Load immediate
  90. +0000062F:   5021        SUBI      R18,0x01       Subtract immediate
  91. +00000630:   4080        SBCI      R24,0x00       Subtract immediate with carry
  92. +00000631:   4090        SBCI      R25,0x00       Subtract immediate with carry
  93. +00000632:   F7E1        BRNE      PC0x03        Branch if not equal
  94. +00000633:   C000        RJMP      PC+0x0001      Relative jump
  95. +00000634:   0000        NOP                      No operation
  96. +00000635:   940E0947    CALL      0x00000947     Call subroutine
  97. +00000637:   CFF1        RJMP      PC0x000E      Relative jump
  98. +00000638:   55CF        SUBI      R28,0x5F       Subtract immediate
  99. +00000639:   4FDC        SBCI      R29,0xFC       Subtract immediate with carry
  100. +0000063A:   B60F        IN        R0,0x3F        In from I/O location
  101. +0000063B:   94F8        CLI                      Global Interrupt Disable
  102. +0000063C:   BFDE        OUT       0x3E,R29       Out to I/O location
  103. +0000063D:   BE0F        OUT       0x3F,R0        Out to I/O location
  104. +0000063E:   BFCD        OUT       0x3D,R28       Out to I/O location
  105. +0000063F:   91DF        POP       R29            Pop register from stack
  106. +00000640:   91CF        POP       R28            Pop register from stack
  107. +00000641:   911F        POP       R17            Pop register from stack
  108. +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.

lvl4 decrypted

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 :

  1. +00000D32:   92EF        PUSH      R14            Push register on stack
  2. +00000D33:   92FF        PUSH      R15            Push register on stack
  3. +00000D34:   930F        PUSH      R16            Push register on stack
  4. +00000D35:   931F        PUSH      R17            Push register on stack
  5. +00000D36:   93CF        PUSH      R28            Push register on stack
  6. +00000D37:   93DF        PUSH      R29            Push register on stack
  7. +00000D38:   01EC        MOVW      R28,R24        Copy register pair
  8. +00000D39:   018C        MOVW      R16,R24        Copy register pair
  9. +00000D3A:   5F1E        SUBI      R17,0xFE       Subtract immediate
  10. +00000D3B:   017C        MOVW      R14,R24        Copy register pair
  11. +00000D3C:   EF8A        LDI       R24,0xFA       Load immediate
  12. +00000D3D:   1AE8        SUB       R14,R24        Subtract without carry
  13. +00000D3E:   EF8D        LDI       R24,0xFD       Load immediate
  14. +00000D3F:   0AF8        SBC       R15,R24        Subtract with carry
  15. +00000D40:   C066        RJMP      PC+0x0067      Relative jump
  16. +00000D41:   2755        CLR       R21            Clear Register
  17. +00000D42:   FD47        SBRC      R20,7          Skip if bit in register cleared
  18. +00000D43:   9550        COM       R21            Ones complement
  19. +00000D44:   2F65        MOV       R22,R21        Copy register
  20. +00000D45:   2F75        MOV       R23,R21        Copy register
  21. +00000D46:   01FA        MOVW      R30,R20        Copy register pair
  22. +00000D47:   97F0        SBIW      R30,0x30       Subtract immediate from word
  23. +00000D48:   33E0        CPI       R30,0x30       Compare with immediate
  24. +00000D49:   05F1        CPC       R31,R1         Compare with carry
  25. +00000D4A:   F008        BRCS      PC+0x02        Branch if carry set
  26. +00000D4B:   C05B        RJMP      PC+0x005C      Relative jump
  27. +00000D4C:   5CEC        SUBI      R30,0xCC       Subtract immediate
  28. +00000D4D:   4FFF        SBCI      R31,0xFF       Subtract immediate with carry
  29. +00000D4E:   940C0E67    JMP       0x00000E67     Jump
  30. +00000D50:   01CE        MOVW      R24,R28        Copy register pair
  31. +00000D51:   940E0B42    CALL      0x00000B42     Call subroutine
  32. +00000D53:   C053        RJMP      PC+0x0054      Relative jump
  33. +00000D54:   01CE        MOVW      R24,R28        Copy register pair
  34. +00000D55:   940E0B62    CALL      0x00000B62     Call subroutine
  35. +00000D57:   C04F        RJMP      PC+0x0050      Relative jump
  36. +00000D58:   01CE        MOVW      R24,R28        Copy register pair
  37. +00000D59:   940E0B82    CALL      0x00000B82     Call subroutine
  38. +00000D5B:   C04B        RJMP      PC+0x004C      Relative jump
  39. +00000D5C:   01CE        MOVW      R24,R28        Copy register pair
  40. +00000D5D:   940E0BA2    CALL      0x00000BA2     Call subroutine
  41. +00000D5F:   C047        RJMP      PC+0x0048      Relative jump
  42. +00000D60:   01CE        MOVW      R24,R28        Copy register pair
  43. +00000D61:   940E0BC6    CALL      0x00000BC6     Call subroutine
  44. +00000D63:   C043        RJMP      PC+0x0044      Relative jump
  45. +00000D64:   01CE        MOVW      R24,R28        Copy register pair
  46. +00000D65:   940E0BE6    CALL      0x00000BE6     Call subroutine
  47. +00000D67:   C03F        RJMP      PC+0x0040      Relative jump
  48. +00000D68:   01CE        MOVW      R24,R28        Copy register pair
  49. +00000D69:   940E0C06    CALL      0x00000C06     Call subroutine
  50. +00000D6B:   C03B        RJMP      PC+0x003C      Relative jump
  51. +00000D6C:   01CE        MOVW      R24,R28        Copy register pair
  52. +00000D6D:   940E0C26    CALL      0x00000C26     Call subroutine
  53. +00000D6F:   C037        RJMP      PC+0x0038      Relative jump
  54. +00000D70:   01CE        MOVW      R24,R28        Copy register pair
  55. +00000D71:   940E0C33    CALL      0x00000C33     Call subroutine
  56. +00000D73:   C033        RJMP      PC+0x0034      Relative jump
  57. +00000D74:   01CE        MOVW      R24,R28        Copy register pair
  58. +00000D75:   940E0C60    CALL      0x00000C60     Call subroutine
  59. +00000D77:   C02F        RJMP      PC+0x0030      Relative jump
  60. +00000D78:   01CE        MOVW      R24,R28        Copy register pair
  61. +00000D79:   940E0C84    CALL      0x00000C84     Call subroutine
  62. +00000D7B:   C02B        RJMP      PC+0x002C      Relative jump
  63. +00000D7C:   01CE        MOVW      R24,R28        Copy register pair
  64. +00000D7D:   940E0C8A    CALL      0x00000C8A     Call subroutine
  65. +00000D7F:   C027        RJMP      PC+0x0028      Relative jump
  66. +00000D80:   01CE        MOVW      R24,R28        Copy register pair
  67. +00000D81:   940E0CB1    CALL      0x00000CB1     Call subroutine
  68. +00000D83:   C023        RJMP      PC+0x0024      Relative jump
  69. +00000D84:   01CE        MOVW      R24,R28        Copy register pair
  70. +00000D85:   940E0C98    CALL      0x00000C98     Call subroutine
  71. +00000D87:   C01F        RJMP      PC+0x0020      Relative jump
  72. +00000D88:   01CE        MOVW      R24,R28        Copy register pair
  73. +00000D89:   940E0CCA    CALL      0x00000CCA     Call subroutine
  74. +00000D8B:   C01B        RJMP      PC+0x001C      Relative jump
  75. +00000D8C:   01CE        MOVW      R24,R28        Copy register pair
  76. +00000D8D:   940E0CE3    CALL      0x00000CE3     Call subroutine
  77. +00000D8F:   C017        RJMP      PC+0x0018      Relative jump
  78. +00000D90:   01CE        MOVW      R24,R28        Copy register pair
  79. +00000D91:   940E0CFC    CALL      0x00000CFC     Call subroutine
  80. +00000D93:   C013        RJMP      PC+0x0014      Relative jump
  81. +00000D94:   01CE        MOVW      R24,R28        Copy register pair
  82. +00000D95:   940E0AFF    CALL      0x00000AFF     Call subroutine
  83. +00000D97:   C00F        RJMP      PC+0x0010      Relative jump
  84. +00000D98:   01CE        MOVW      R24,R28        Copy register pair
  85. +00000D99:   940E0B0A    CALL      0x00000B0A     Call subroutine
  86. +00000D9B:   C00B        RJMP      PC+0x000C      Relative jump
  87. +00000D9C:   01CE        MOVW      R24,R28        Copy register pair
  88. +00000D9D:   940E0B1C    CALL      0x00000B1C     Call subroutine
  89. +00000D9F:   C007        RJMP      PC+0x0008      Relative jump
  90. +00000DA0:   01CE        MOVW      R24,R28        Copy register pair
  91. +00000DA1:   940E0B29    CALL      0x00000B29     Call subroutine
  92. +00000DA3:   C003        RJMP      PC+0x0004      Relative jump
  93. +00000DA4:   01CE        MOVW      R24,R28        Copy register pair
  94. +00000DA5:   940E0AC6    CALL      0x00000AC6     Call subroutine
  95. +00000DA7:   01D8        MOVW      R26,R16        Copy register pair
  96. +00000DA8:   91ED        LD        R30,X+         Load indirect and postincrement
  97. +00000DA9:   91FC        LD        R31,X          Load indirect
  98. +00000DAA:   9711        SBIW      R26,0x01       Subtract immediate from word
  99. +00000DAB:   9141        LD        R20,Z+         Load indirect and postincrement
  100. +00000DAC:   93ED        ST        X+,R30         Store indirect and postincrement
  101. +00000DAD:   93FC        ST        X,R31          Store indirect
  102. +00000DAE:   3342        CPI       R20,0x32       Compare with immediate
  103. +00000DAF:   F021        BREQ      PC+0x05        Branch if equal
  104. +00000DB0:   01F7        MOVW      R30,R14        Copy register pair
  105. +00000DB1:   8180        LDD       R24,Z+0        Load indirect with displacement
  106. +00000DB2:   FF82        SBRS      R24,2          Skip if bit in register set
  107. +00000DB3:   CF8D        RJMP      PC0x0072      Relative jump
  108. +00000DB4:   01CE        MOVW      R24,R28        Copy register pair
  109. +00000DB5:   91DF        POP       R29            Pop register from stack
  110. +00000DB6:   91CF        POP       R28            Pop register from stack
  111. +00000DB7:   911F        POP       R17            Pop register from stack
  112. +00000DB8:   910F        POP       R16            Pop register from stack
  113. +00000DB9:   90FF        POP       R15            Pop register from stack
  114. +00000DBA:   90EF        POP       R14            Pop register from stack
  115. +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‘…

lvl4 complete

Lvl4 after completion

Testing it live…

  1. Access code: B1p!8lP!
  2. === Security Level 4 ===
  4. Last auth code: S7h4
  5. >> GG! Go to

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 😉