Welcome back security folks to the multi-part series of blogs about “Jackpoting Online Slots” in which we continue to try our luck a little bit more. Now it’s time to get down to the nitty-gritty and we’ll dive into requests and responses and try to modify them to get us to our goal. Tie your shoes, clean your glasses and warm up your burp suit. We’re off.
warning note
I would like to point out that I do not support gambling. It can very quickly become a very dangerous thing where you can lose everything. Casinos are neither your friends nor charities, in the end they want your money and they get it, ALWAYS. I will not mention any casinos by name because I do not want to endorse them. If you already have symptoms of addiction, so the following blog may trigger you, simply do not read it. There are many places that can help you, such as “responsiblegambling“. Take care of yourselves!
Execution
Now that this has been clarified, let’s take a quick look back at the results from the last blog “Jackpoting Online Slots – part two“. We have tried to use the two methods of “Winning Indicator” and “Recurring Events Analysis” to predict whether a game is worth playing at the moment or not. Unfortunately, we have not been able to find enough evidence to make a reliable prediction. The only thing that has become crystal clear is that the money always goes downhill when we play the slots. We want to counteract this with the following techniques and see how far we can push the slots to make us happy and hopefully win some fun money. In this third part, we will look at the following techniques, which are shown in bold.
- Winning indicator
- Recurring Events Analysis
- Request Payload Attack
- Response Interception Attack
- Backend Forgery Attack
Request Payload Attack
First, we try to modify the data we send to the server to make it do something it shouldn’t do. We will use the Community Edition of Burp Suite for this. This is quite a powerful tool and we will not cover an introduction to it here. If you are not familiar with the tool or feel unsure, I can recommend the “Burp Suite: The Basics” classroom from THM, it’s a great thing. The URL is again the same as in the previous blogs.
https://alvcw.playngonetwork.com/casino/ContainerLauncher?channel=desktop&demo=2&gid=bookofdead&lang=en_GB&pid=393&practice=1
So let’s fire up Burp Suit, we won’t use any scops for the first tests and we’ll switch off the Intruder. We want to see what kind of data is going over the internet cable. I use the normal Chrome browser (not Burp’s) and switch Burp in between using the FoxyProxy add-on. But as always, use it in the way that works best for you, and work with the tools that you know will make things easier for you. There is no right or wrong way. Let’s do a couple of spins to get started and then see what’s going on in burp.
You will immediately notice that we are talking to two different hosts, “alvcw.blabla” and “alvflyp.blabla”. The one we are currently interested in is “alvflyp“, because this URL is the game engine, the “alvcw” is only responsible for transmitting the scripts and the configs. So let’s define this host as a scope and switch on the Intruder. Oh, and I define in the Proxy Settings that only the URLs in the scope are displayed by the Interceptor.
When we press spin for the first time, we immediately see the payload being sent to the server. This should be familiar from the last two blogs and we know how it works. Let us now play around with the values. Sure, we can change the coins, or the value of the coins, or the number of winning lines. That’s fun, but it doesn’t get us anywhere, except to see that the server doesn’t use a checksum or something, so we’ll just do that. Next, we send combinations in the payload that cannot actually occur in that slot. For example, 15 winning lines or a negative coin value or a number of 4000 coins.
# 15 winning lines
d=4
kToPv5s0pdg!10980
1 1 15 20 1
# negative coin value
d=4
4XNf7iF3Fgr!10987
1 1 10 -20 1
# 4000 coins
d=4
phXzqcHPzEb!11013
1 4000 10 20 1
Sadly, all of our beautiful payloads cause the game to crash instantly. Of course, you, the resourceful reader, have already figured this out by reading the payloads, as a new ID is displayed each time, which means I have to restart the game every time 😉 I can already see that this is going to be a rather dull affair! Start – payload – crash – reload, over and over again, but we won’t let that stop us.
But wait, why don’t we just rewrite our script that we used before, we already have everything we need there. Now we just want to inject different characters into the paload.
cls
function session($url,$header) {
# get id
$body = "d=1`r`n0`r`n103 3922 `"en_GB`" 310 `"[User Agent]`" `"Book of Dead`" `"desktop`""
$id = (Invoke-RestMethod $url -Method 'POST' -Headers $headers -Body $body).split('"')[1]
# get session
$body = "d=2`r`n$id`r`n101 `"495b091d-73e0-4cbf-8457-95a283910a3b`" `"USD`" `"`" `"`" `"`" `"`""
$session = (Invoke-RestMethod $url -Method 'POST' -Headers $headers -Body $body).split('"')[1]
# register session
$body = "d=3`r`n$session`r`n104 310 0"
$reg = Invoke-RestMethod $url -Method 'POST' -Headers $headers -Body $body
return $session
}
# define variables
$url = "https://alvflyp.playngonetwork.com/"
$header = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$header.Add("Content-Type", "text/plain")
$vaules = @("$","@","{","}","/","//","///","\","\\","\\\","-",";",":","'","","(",")","&","%","*","?",",",".")
$rid = 7
$payload = "1 2 5 30 1"
# try and error
foreach ($symbol in $vaules) {
Write-Host("Inject $symbol to payload") -ForegroundColor Yellow
# inject coin count
$session = session $url $header
$attack = $payload -replace ("2","$symbol")
$body = "d=$rid`r`n$session`r`n$attack"
$data = Invoke-RestMethod $url -Method 'POST' -Headers $headers -Body $body
if ($data -like "*Game module failed*") { Write-Host("coin count failed") -ForegroundColor red }
# inject wining line
$session = session $url $header
$attack = $payload -replace ("5","$symbol")
$body = "d=$rid`r`n$session`r`n$attack"
$data = Invoke-RestMethod $url -Method 'POST' -Headers $headers -Body $body
if ($data -like "*Game module failed*") { Write-Host("winning line failed") -ForegroundColor red }
# inject coin value
$session = session $url $header
$attack = $payload -replace ("30","$symbol")
$body = "d=$rid`r`n$session`r`n$attack"
$data = Invoke-RestMethod $url -Method 'POST' -Headers $headers -Body $body
if ($data -like "*Game module failed*") { Write-Host("coin value failed") -ForegroundColor red }
}
It was well-intentioned, but no value was vulnerable to any of the injected characters. All injections resulted in an error in the game engine and the response was always “Game module failed”, nice try anyway. Well, where else can we customize the request payload to provoke something that shouldn’t be there? Unfortunately, at the current state of knowledge, no other request is attackable. Manipulating the session makes no sense, and a spin request is always structured exactly the same way. So let’s move on and see what we can do with the “Response Interception Attack“.
Response Interception Attack
Now we are not trying to change the data we send to the server (request), but the data the server sends back to us (response). This can help with how any local scripts and engines process the data. This may help us to achieve our goal. Let’s start with the first response, which we already know from the last blog, and which determines the credit balance we currently have. You will remember that this is reported in the same response where the RTP value is displayed. Here is a brief refresher for our knowledge. As a quick refresher, we are going to focus on the part that is marked in green.
d=104 1
54 11 1 2 3 4 5 10 20 50 100 150 200 1
57 "<custom><RTP Value=\"96\" /></custom>"
60 96 0 0
52 100000000 0 0
83 0
91 736 "lcBzVRXsXfO6CHHtd8BRzYHRjtVpHlDDVaL5Q4g5zG6WzNpy7f1Uaxkjbfgax1jZwkGydFea2pc5R2Hp3rhik53rCDZltP2kZVy--utQqz8oTgRotM0JQKqiHfLZ1qTJZVTawvYBQW5yBDMmMw7Hy_K6aHUGWW_06b8paKp-y1cqGcMOtKqhqCx__JnxyRaN2skSVgtDdIs_Xrp2uzGx-A.."
109
This is the response to the following request payload, which is called each time the game starts.
URL https://alvflyp.playngonetwork.com/
Method POST
# payload
d=3
nmVXGttixjh!735
104 310 0 %22%3croot%3e%3csettings%3e%3cDenominations%3e%3cdenom+Value%3d%220.01%22+%2f%3e%3cdenom+Value%3d%220.02%22+%2f%3e%3cdenom+Value%3d%220.03%22+%2f%3e%3cdenom+Value%3d%220.04%22+%2f%3e%3cdenom+Value%3d%220.05%22+%2f%3e%3cdenom+Value%3d%220.1%22+%2f%3e%3cdenom+Value%3d%220.2%22+%2f%3e%3cdenom+Value%3d%220.5%22+%2f%3e%3cdenom+Value%3d%221%22+%2f%3e%3cdenom+Value%3d%222%22+%2f%3e%3cdenom+Value%3d%221.5%22+%2f%3e%3c%2fDenominations%3e%3c%2fsettings%3e%3c%2froot%3e%22
So let’s use Burp to modify the response of the “d=3” request so that our credit is not $1’000’000 (100’000’000/100=$1’000’000), which is the default balance for a demo game, but only $50 ($50*100=5’000). To do this, we inject the following into the response
d=104 1
54 11 1 2 3 4 5 10 20 50 100 150 200 1
57 "<custom><RTP Value=\"96\" /></custom>"
60 96 0 0
52 5000 0 0
83 0
91 736
Fireworks in the sky, the slot immediately shows the amount injected balance. This means that we can trick the slot machine into believing how much money we have. Could this be our first success story? Unfortunately no, as soon as the first spin is finished, the balance returns to normal. This is because the response value for each spin contains the current balance. But this immediately raises another question: how the hell does the game engine get information from the casino about how much money we have, and how is this synchronised? We can’t answer that yet, but it’s on our to-do list.
Let’s go further, we already know that there is a response per spin and that this looks different depending on the type (win/loss). Furthermore the symbols that are displayed are also transmitted via this. Let’s take another look at some of the winning responses. One case not considered yet is that there can be one or more winning lines per spin. So somewhere this information must also be shown in the response.
winning line 9 (id 8)
d=1 1 10 20 4 1 3 0 3 9 7 3 5 4 2 6 4 7 0 0 1 8 3 3 1 5 3 8 5 4 0 1 1 0 0 5
3 1 5 100 0 1
winning line6 (id 5)
d=1 1 10 20 4 3 6 3 2 1 3 0 4 4 9 5 4 6 1 0 1 5 3 3 0 5 2 0 1 5 4 2 0 4 3 2
3 1 5 100 0 1
winning line9 (id 8)
d=1 1 10 20 8 4 2 1 0 2 2 9 1 2 8 1 9 5 4 0 1 8 2 5 2 100 5 0 3 5 1 6 0 2 3 2
3 1 100 2000 0 1
winning line 3 (id 2) & winning line 9 (id 8)
d=1 1 10 20 2 4 6 3 0 6 8 0 1 7 1 3 4 8 1 0 2 2 6 2 0 5 8 6 2 0 5 6 3 4 1 2 4 2 6 6 2
3 1 10 200 0 1
I think we’ve cracked the code, but let’s take a step back and look at the results. In the first line we know the yellow block, this reflects our bet, we also know the purple block, these are the symbols that the slot machine displays. In the second line we know the blue block, the first value is the number of coins we have won and the second value is the actual amount we have won. The pink numbers are new, they indicate how many wins were made on this spin (1 or 2). Then there are the cyan numbers that must correspond to the winning lines, followed by a block of 3 numbers that I have not yet been able to identify and the blue numbers that correspond to the coins of these wins, in the case of multiple wins this combination is repeated. At the end, there is another block of 10 numbers which I could not identify yet.
With this information we should be able to manipulate an response so that we get a win on every spin, let’s give it a try 🙂 I’m going to use the third response from the above again. And wuuup, the game shows us spin after spin that we have won, and always the same prize.
However, you may have noticed (with some zooming) that the winnings have no effect on our balance, which is reset after each spin. So we are not successful here either. And again the question arises: how the hell does the casino and the slot machine compare our balance? Anyway, let’s have some fun and use the following payload.
d=1 1 10 20 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 0 1 8 2 5 2 100000 5 0 3 5 1 6 0 2 3 2
3 1 100000 2000000 0 1
Finally, let’s find out, if only by trial and error, what the block of 10 numbers at the end means. Maybe a checksum? Maybe a win ID, i.e. this ID always has this win? Maybe the door lock combination for Area51? So what we already know, this one doesn’t count up steadily, so it’s not a round ID, we don’t use our own payload, we just replace the last 10 digits in the response of a random spin with “1 3 3 7 1 3 3 7 1 3” which looks like this.
d=1 2 5 50 6 2 4 3 1 4 8 2 4 6 7 1 5 0 4 0 1 2 4 3 0 10 1 3 3 7 1 3 3 7 1 3
3 1 10 500 0 1
Apart from the fact that the backend engine is now unresponsive and no new games can be loaded, they either blocked me in some way or we crashed the backend with this payload. 😮 I hope the former. Anyway, after about 30 minutes the games started again, so let’s hope they had another problem, I don’t want to try it again right now 🙂
We have always tried this in demo mode, what is so different about trying such payloads directly through the casino? Is it possible for a local script to report the winnings to the casino via ajax or something else? Because you have to know that if you win, the casino will know it directly (even if it is only $1), because the account balance is always up to date with the gaming behavior on the casino page.
While analyzing the traffic generated by the casino page, I noticed that WSS (Web Secure Sockets Messages) were constantly running in the background. The direction is either to the client (client update) or to the server (server update), interestingly there is a subscription here that goes to the server and has the following payload.
{
"id": "3",
"eventName": "subscribe",
"eventData": {
"id": "3",
"variables": {},
"extensions": {},
"operationName": "MyBalanceChangedSubscription",
"query": "subscription MyBalanceChangedSubscription {\n bankingMyBalanceChanged(input: {}) {\n changedBalance {\n id\n currency\n totalBalance\n totalBonusBalance\n realBalance\n __typename\n }\n clientSubscriptionId\n __typename\n }\n}\n"
},
"token": "some JWT"
}
Perhaps we can look at this in more detail in the next blog and try to exploit it. Unfortunately, these two variants did not have the desired effect. It is possible to fake the initial balance on the client side, but it will always be overwritten by the server. We also managed to get funny combinations with all sorts of effects like 15 wild symbols or 1,000,000 wins, but apart from being funny, they don’t do anything. Let us leave this for now, maybe we will come up with something in the next few days.
Until then, stay tuned and see you soon!
** midjourney string “A casino player sits nervously at the slot machine hands clasped behind his head. , cinematic lighting –ar 1:2 –q 2“