Tomcat
Professional
- Messages
- 2,689
- Reaction score
- 929
- Points
- 113
Python, Java, C++, Delphi, PHP - it was these different programming languages that were used this time to create a virtual ATM-crypto machine, which participants had to test for strength as part of the $NATCH competition held at Positive Hack Days 12 in order to win. All code from start to finish was written using ChatGPT and performed exceptionally well. We revised the concept of the competition and used a reporting system. In addition to standard tasks (kiosk bypass, privilege escalation, and AppLocker bypass), this year participants were faced with new non-standard tasks, which you can read about in this article.
@nikaleksey, @drd0c, @s0x28 - winners of the ATM hacking 3.0 competition
It looked like this: you go to the ATM, enter the received flag, receive a wad of bills for your efforts and exchange it for a T-shirt.
Competition ATM at PHDays 12
More than 100 people joined the competition chat in two days (after the competition, some left, but history remembers everything).
Number of subscribers in the competition chat
The winners divided the winnings of 50,000 rubles in accordance with the final score. The table with the participants' points looked like this:
Table with participants points
You won’t believe it, but the first three results were the same, so we, as the organizers, had to make the final decision.
This year we added a mechanism for updating the virtual machine: right during the competition, participants could run the updater.py file, after which the update.zip archive with new tasks would be downloaded.
Demonstration of the process of updating a crypto ATM
Cryptomat interface
By the way, the wallets themselves were stored in the wallets.txt file in the form that you can see in the screenshot below.
Wallets in the wallets.txt file
A true information security specialist must understand everything, including how cryptocurrencies work. You can see private keys in the file; some participants in the competition paid attention to this and successfully submitted a report. In a real situation, storing funds in a cryptomat is, of course, not entirely safe.
Now let's move on to the kiosk: sometimes ATMs have a kiosk service mode available (this is done for the convenience of the personnel servicing the ATM), for example, the keyboard can be turned on and off with just one key. The first way to bypass the kiosk this year was exactly this. Every time the user pressed the u or r key , the keyboard would suddenly start working. It sounds simple, but in fact, if you don’t know, it’s difficult to notice.
Kiosk interface
It was available in a public update, which we announced in telegram chat. The main functionality of the code (presented below) is to create a window containing a kiosk bypass button and intercept keyboard events. When clicked, the button moves randomly within the window. If it is pressed more than 100 times, the window closes and keyboard interception is disabled.
Password manager in a virtual machine
I won’t attach the code, otherwise this article will stretch to 200 pages (you can download a virtual machine and revise it a little). This time I did not collect the source code into an EXE file, so that it would be easier for participants to explore and study all this. This password manager has many vulnerabilities, from storing passwords in cleartext to XSS.
Database file opened via notepad
Remote file reading
And @nikaleksey successfully exploited an XSS vulnerability in a wallet application. It looked something like this:
Exploiting an XSS vulnerability in a wallet application
The method of operation itself will remain a secret - you still have to figure it out.
And you shouldn’t forget about CVE: several exploits for all known CVEs (for example, CVE-2020-0796) were successfully developed in a virtual machine - several participants reported this almost simultaneously.
PS C:\Users\kiosk\AppData\Local\Programs\Python\Python310> .\cve-2020-0796-local.exe
-= CVE-2020-0796 LPE =-
by @danigargu and @dialluvioso_
Successfully connected socket descriptor: 192
Sending SMB negotiation request...
Finished SMB negotiation
Found kernel token at 0xffffb88770dd1060
Sending compressed buffer...
SEP_TOKEN_PRIVILEGES changed
Injecting shellcode in winlogon...
Success!
For those who were not able to attend PHDays and watch the ups and downs of the competition live, I suggest watching the video that the participants created.
Did you feel the tense atmosphere? This is how real ATMs are hacked! And this is only a small fraction of the number of reports that were sent to us!
Unfortunately, none of the participants discovered a lot of vulnerabilities. Of course, I won’t talk about them in order to maintain intrigue and interest - you can download the virtual machine yourself and try to find them. It is possible that by the time this article is published I will have added several new tasks to the updates that no one noticed in one of the virtual machine updates and that I secretly rolled out during the competition.
Of course, many thanks to regular readers too! Are there anyone here who has been reading the analysis of the competition for the third year in a row?
Good mood to you all!

@nikaleksey, @drd0c, @s0x28 - winners of the ATM hacking 3.0 competition
A little about the competition and statistics
This year, the ATM gave participants special banknotes that could be exchanged for merchandise: T-shirts and souvenir bank cards.It looked like this: you go to the ATM, enter the received flag, receive a wad of bills for your efforts and exchange it for a T-shirt.

Competition ATM at PHDays 12
More than 100 people joined the competition chat in two days (after the competition, some left, but history remembers everything).

Number of subscribers in the competition chat
The winners divided the winnings of 50,000 rubles in accordance with the final score. The table with the participants' points looked like this:

Table with participants points
You won’t believe it, but the first three results were the same, so we, as the organizers, had to make the final decision.
All participants went head to head, it would be a shame to give third place to someone who didn’t deserve it, so there was no bronze this year. @drd0c's reports were undoubtedly more interesting, which is why we recognized him as the winner of the competition.@drd0c — 25,000 rubles + backpack
@s0x28 — 12,500 rubles + backpack
@nikaleksey — 12,500 rubles + backpack
This year we added a mechanism for updating the virtual machine: right during the competition, participants could run the updater.py file, after which the update.zip archive with new tasks would be downloaded.

Demonstration of the process of updating a crypto ATM
Analysis of some tasks
It is worth noting that an ATM is a device that is not limited to just Windows problems. A real ATM, in addition to the operating system, also has a large amount of custom, highly specialized software to manage, for example, the issuance of banknotes, the component devices of the ATM, connect to processing, and conduct transactions. The range of this software is as wide as possible, and it is very important when conducting a pentest of a real ATM to study it comprehensively, to study every program that can be found there. Who knows, maybe attackers will be able to hack an ATM through it and withdraw money. This is the idea that formed the basis for the development of this year’s competition.Bypass kiosk mode #1
The first kiosk looked like this: you see the interface of a crypto machine, there is a QR code that can be scanned. It encodes a bitcoin wallet to which you need to send bitcoins so that the crypto machine will subsequently issue banknotes. The public and private key can later be used to manage your funds available on the wallet - in fact, a real bitcoin wallet was written for the competition, available in the virtual environment. Real crypto ATMs work exactly according to this principle, and under the hood they usually have Windows.
Cryptomat interface
By the way, the wallets themselves were stored in the wallets.txt file in the form that you can see in the screenshot below.

Wallets in the wallets.txt file
A true information security specialist must understand everything, including how cryptocurrencies work. You can see private keys in the file; some participants in the competition paid attention to this and successfully submitted a report. In a real situation, storing funds in a cryptomat is, of course, not entirely safe.
Now let's move on to the kiosk: sometimes ATMs have a kiosk service mode available (this is done for the convenience of the personnel servicing the ATM), for example, the keyboard can be turned on and off with just one key. The first way to bypass the kiosk this year was exactly this. Every time the user pressed the u or r key , the keyboard would suddenly start working. It sounds simple, but in fact, if you don’t know, it’s difficult to notice.
Code:
import sys
import keyboard
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget
from PyQt5.QtWebEngineWidgets import QWebEngineView
class KioskApp(QMainWindow):
def __init__(self):
super().__init__()
# Window setup
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.showFullScreen()
# Creating and configuring a web browser
browser = QWebEngineView()
browser.settings().setDefaultTextEncoding("utf-8")
browser.load(QUrl("http://localhost:8000/create_wallet"))
self.setCentralWidget(browser)
# Create a gray layer
self.gray_layer = QWidget(self)
self.gray_layer.setStyleSheet("background-color: rgba(128, 128, 128, 20);") # 1% прозрачности
self.gray_layer.setGeometry(0, 0, self.width(), self.height())
# Key state switch (default lock)
self.keys_enabled = True
self.toggle_keys()
def keyPressEvent(self, event):
if event.key() == Qt.Key_U or (event.text() == 'г' and event.modifiers() == Qt.NoModifier):
self.toggle_keys()
elif self.keys_enabled:
super().keyPressEvent(event)
def toggle_keys(self):
self.keys_enabled = not self.keys_enabled
if self.keys_enabled:
keyboard.unhook_all()
self.gray_layer.hide()
else:
for key in keyboard.all_modifiers:
keyboard.block_key(key)
for i in range(1, 255):
if keyboard.key_to_scan_codes(i) != keyboard.key_to_scan_codes("u") and keyboard.key_to_scan_codes(i) != 33:
keyboard.block_key(i)
self.gray_layer.show()
def closeEvent(self, event):
# Unlock keys when closing the application
keyboard.unhook_all()
if __name__ == '__main__':
app = QApplication(sys.argv)
kiosk = KioskApp()
kiosk.show()
sys.exit(app.exec_())
Bypass kiosk mode #2
The second kiosk looked like this:
Kiosk interface
It was available in a public update, which we announced in telegram chat. The main functionality of the code (presented below) is to create a window containing a kiosk bypass button and intercept keyboard events. When clicked, the button moves randomly within the window. If it is pressed more than 100 times, the window closes and keyboard interception is disabled.
Bonus for habro readers : I deliberately give you the kiosk application code in complete and unchanged form, so that you can study exactly how a kiosk application can be written in different languages and how it all works. One of my ideas for the future is to make a kiosk that no hacker can hack.
Code:
#include <Windows.h>
#include <cstdlib>
HHOOK keyboardHook;
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
void DisableKeyboard();
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void MoveButtonRandomly(HWND hwnd);
HWND buttonHandle; // Global variable to store the button handle
int clickCount = 0; // Global variable for counting clicks
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// Register the window class
WNDCLASS wc = {0};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
wc.lpszClassName = TEXT("FullScreenWindow");
RegisterClass(&wc);
// Create a window
HWND hwnd = CreateWindowEx(
0,
TEXT("FullScreenWindow"),
TEXT(""),
WS_POPUP,
0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN),
NULL, NULL, hInstance, NULL);
// Create a button "Exit kiosk mode"
buttonHandle = CreateWindow(
TEXT("BUTTON"),
TEXT("kiosk bypass"),
WS_VISIBLE | WS_CHILD,
10, 10, 200, 50,
hwnd, NULL, hInstance, NULL);
// Place the window on top of all other windows
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
// Display the window
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd); // Обновление содержимого окна и его отображение
// Install the keyboard hook
keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0);
if (keyboardHook == NULL) {
// Handle errors when installing a keyboard hook
}
// Start the message processing loop
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Exit the program
DisableKeyboard(); // Disable keyboard before exit
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_COMMAND:
if (reinterpret_cast<HWND>(lParam) == buttonHandle) // Проверка, что сообщение пришло от кнопки
{
MoveButtonRandomly(hwnd);
clickCount++;
if (clickCount >= 100)
{
DestroyWindow(hwnd); // Закрытие окна
DisableKeyboard(); // Отключение клавиатуры
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
void MoveButtonRandomly(HWND hwnd)
{
RECT rect;
GetClientRect(hwnd, &rect);
// Generate random coordinates to move the button
int newX = rand() % (rect.right - rect.left - 200) + rect.left;
int newY = rand() % (rect.bottom - rect.top - 50) + rect.top;
// Move the button to new coordinates
SetWindowPos(buttonHandle, NULL, newX, newY, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0)
{
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
{
// Prevent keyboard events from being processed
return 1;
}
}
return CallNextHookEx(keyboardHook, nCode, wParam, lParam);
}
void DisableKeyboard()
{
UnhookWindowsHookEx(keyboardHook);
}
Privilege Elevation - Password Manager
It is a very real situation when a password manager can be found on a cryptomat. For an ATM, of course, it’s debatable, but in our business anything can happen. On the virtual machine, you could find a password_manager written in Python using the Flask framework. It itself was just lying in a folder (inactive), it still needed to be found and then launched. Privileges could be increased by logging into the administrator account (credentials were saved in the Chrome browser). In fact, this is a ready-made password manager, which is specifically designed with vulnerabilities. With a little tweaking, you can safely store your passwords online and be confident that they are safe.
Password manager in a virtual machine
I won’t attach the code, otherwise this article will stretch to 200 pages (you can download a virtual machine and revise it a little). This time I did not collect the source code into an EXE file, so that it would be easier for participants to explore and study all this. This password manager has many vulnerabilities, from storing passwords in cleartext to XSS.

Database file opened via notepad
Interesting reports from participants
The virtual machine contained more than 15 vulnerabilities. I’ll tell you about some cool reports that the participants sent us. For example, @s0x28 found a remote file reader. An attacker who was on the same network as a crypto ATM could even obtain the contents of any file without bypassing the kiosk—in our case, the contents of wallets.txt.
Remote file reading
And @nikaleksey successfully exploited an XSS vulnerability in a wallet application. It looked something like this:

Exploiting an XSS vulnerability in a wallet application
The method of operation itself will remain a secret - you still have to figure it out.
And you shouldn’t forget about CVE: several exploits for all known CVEs (for example, CVE-2020-0796) were successfully developed in a virtual machine - several participants reported this almost simultaneously.
PS C:\Users\kiosk\AppData\Local\Programs\Python\Python310> .\cve-2020-0796-local.exe
-= CVE-2020-0796 LPE =-
by @danigargu and @dialluvioso_
Successfully connected socket descriptor: 192
Sending SMB negotiation request...
Finished SMB negotiation
Found kernel token at 0xffffb88770dd1060
Sending compressed buffer...
SEP_TOKEN_PRIVILEGES changed
Injecting shellcode in winlogon...
Success!

For those who were not able to attend PHDays and watch the ups and downs of the competition live, I suggest watching the video that the participants created.
Did you feel the tense atmosphere? This is how real ATMs are hacked! And this is only a small fraction of the number of reports that were sent to us!
Unfortunately, none of the participants discovered a lot of vulnerabilities. Of course, I won’t talk about them in order to maintain intrigue and interest - you can download the virtual machine yourself and try to find them. It is possible that by the time this article is published I will have added several new tasks to the updates that no one noticed in one of the virtual machine updates and that I secretly rolled out during the competition.
Conclusions
I would like to say a big thank you to the guys who participate in this competition year after year and help make it better. I also thank the new participants. This year the format was truly experimental. It has received a lot of feedback (mostly positive), but we have some work to do. Next year we look forward to seeing you again at PHDays and at our ATM competition, which will be even better - v 4.0 is coming!Of course, many thanks to regular readers too! Are there anyone here who has been reading the analysis of the competition for the third year in a row?
Good mood to you all!