|
Прочая документация Помощь, книги, инструкции, описания |
|
Опции темы | Поиск в этой теме | Опции просмотра |
|
07.03.2010, 16:14 | #1 |
Администратор
Регистрация: 07.03.2010
Адрес: Питер.
Сообщений: 69
Сказал(а) спасибо: 66
Поблагодарили 102 раз(а) в 15 сообщениях
Записей в дневнике: 12
|
Разбор пакетов WoW
Разбор структуры пакетов из wow.exe это просто!
Нужно: 1. Интуиция. 2. Знание "СИ". 3. IDA+HexRays. 4. Терпение. Качаем IDA+HexRays. нацеливаем IDA на wow.exe декомпилируем, потом сохраняем всё в .c файл с помощью HexRyas ищем в файле строчку типа Код:
(461, (int) Код:
sub_5F9870(459, (int)sub_401220, 0); sub_5F9870(461, (int)sub_401330, 0); Код:
sub_5F9870() это что то типа OpcodeHandler() Код:
sub_5F9870->OpcodeHandler Код:
OpcodeHandler(459, (int)sub_401220, 0); OpcodeHandler(461, (int)sub_401330, 0); ищём по коду эту функцию sub_401220(). ...... находим что то типа Код:
//----- (00401220) -------------------------------------------------------- signed int __usercall sub_401220<eax>(__int64 a1<edi:ebp>, int a2, int a3, int a4, int a5) { .................... v5 = (int)&v9; v10 = 1; v11 = 0; sub_422B90(a5, (int)&v9, 0x1000u); if ( !sub_6A6BF0(&v9, "DEBUGACTION: ", 0xDu) ) v5 = (int)&v12; if ( *(_BYTE *)v5 == 126 ) переименовываем её в ReadString() находим в начале файла. int __thiscall ReadString(int this, int a2, unsigned int a3); и переименовываем все рядом стоящими функции типа так... Код:
int __thiscall ReadByte(int this, int a2); int __thiscall ReadInt8(int this, int a2); int __thiscall ReadInt16(int this, int a2); int __thiscall ReadInt32(int this, int a2); int __thiscall ReadInt64(int this, int a2); int __thiscall ReadFloat(int this, int a2); >int __thiscall ReadString(int this, int a2, unsigned int a3); int __thiscall ReadString2(int this, int a2, int a3); int __thiscall callReadString2(int this, int a2, int a3); int __thiscall ReadPascalString(int this, int a2, int a3); теперь мы знаем какие функции какой тип данных считывают из пакета ИТАК. теперь мы хотим разобрать структуру какого либо SMSG пакета. Обратите внимание, что CMSG пакеты разбираются по другому и пока что я это описывать не буду. И ещё, некоторые пакеты SMSG которые есть в Opcodes.h(Мангос) их нету в wow.exe это связанно с тем что эти пакеты только для GM клиента и в обычном клиенте их просто нет Так вот... к примеру мы хотим узнать что за структура у пакета SMSG_LOOT_LIST = 0x3F9, переводим 0x3F9 в десятичное число и ищём по нашему коду. OpcodeHandler(1017, (int) нашли к примеру OpcodeHandler(1017, (int)sub_65E100, 0); переходим к sub_65E100 Код:
//----- (0065E100) -------------------------------------------------------- signed int __cdecl sub_65E100(int a1, int a2, int a3, int a4) { int v4; // eax@1 int v5; // esi@1 __int64 v7; // [sp+4h] [bp-8h]@1 v5 = a4; ReadInt64(a4, (int)&v7); v4 = sub_46DB20(v7, 8); if ( v4 ) sub_65D6D0(v4, v5); return 1; } всё, структуру опкода мы знаем все адреса приведённые здесь sub_65E100, sub_401220 и т.д. меняются с каждой версией клиента, так что нужно полагаться на интуицию и ещё... клиент бывает считывает Пакованный гуид из пакета... функция считывания выглядит примерно так Код:
//----- (006A5870) -------------------------------------------------------- int __cdecl sub_6A5870(int a1, int a2) { int v2; // ebx@1 unsigned int v3; // edi@1 int v4; // esi@1 int v6; // ecx@1 __int64 v7; // qax@3 unsigned __int8 v8; // [sp+Fh] [bp-1h]@3 v6 = a1; v4 = a2; v2 = 0; *(_DWORD *)a2 = 0; *(_DWORD *)(a2 + 4) = 0; ReadInt8(v6, (int)((char *)&a2 + 3)); v3 = 0; do { if ( (unsigned __int8)(1 << v2) & BYTE3(a2) ) { ReadInt8(a1, (int)&v8); v7 = v8 << (char)v3; *(_DWORD *)v4 |= v7; *(_DWORD *)(v4 + 4) |= *((_DWORD *)&v7 + 1); } v3 += 8; ++v2; } while ( v3 < 0x40 ); return a1; } ReadInt8(v6, (int)((char *)&a2 + 3)); но!!!!!! строка не уникальна и в других версиях клиента может меняться... Автор: Дерека.
|
22 пользователя(ей) сказали cпасибо: |
28.04.2011, 12:46 | #2 |
Гость
Сообщений: n/a
|
Хорошая статья. Скажите, а где можно более детально узнать о разборе пакетов?
Я так понимаю что опкоды (например такой 0x3F9) берется из снифов! Я правильно понял? В статье говорится о разборе SMSG пакетов, а как разбирать СMSG? ПыСы Может подобные вопросы и покажутся нубскими, но я думаю не только у меня есть большое желание научиться этому.... |
28.04.2011, 13:21 | #3 |
Новичок
Регистрация: 04.03.2011
Сообщений: 29
Сказал(а) спасибо: 12
Поблагодарили 9 раз(а) в 7 сообщениях
|
Я делал мини гайдик на английском по поиску опкодов и структур для 4.0. Но в принципе и для 3.3.5 некоторая инфа тоже может быть полезна.
|
28.04.2011, 15:58 | #4 |
Умный
Регистрация: 17.06.2010
Сообщений: 397
Сказал(а) спасибо: 58
Поблагодарили 55 раз(а) в 38 сообщениях
|
Я прочитал его, но возник вопросик: мы ищем в дампе иды результат сложения оффсета и 8080? или ищем само выражение т.е 1111 + 8080(к примеру)?
|
28.04.2011, 22:34 | #5 | |
Новичок
Регистрация: 04.03.2011
Сообщений: 29
Сказал(а) спасибо: 12
Поблагодарили 9 раз(а) в 7 сообщениях
|
Цитата:
|
|
29.04.2011, 03:43 | #6 |
MaNGOS Dev
Регистрация: 11.03.2010
Сообщений: 468
Сказал(а) спасибо: 0
Поблагодарили 514 раз(а) в 163 сообщениях
|
Я осуществляю поиск в IDB с помощью скрипта на питоне (findinstructions.py). Можно найти в блоге разработчиков IDA.
Последний раз редактировалось TOM_RUS; 30.04.2011 в 10:17. |
28.05.2012, 20:56 | #7 | |
Новичок
Регистрация: 28.05.2012
Сообщений: 10
Сказал(а) спасибо: 1
Поблагодарили 0 раз(а) в 0 сообщениях
|
Цитата:
sub_5F9870(459, (int)sub_401220, 0); подобных строк нет, точнее в "субах" нет вложенных субов. Собственно говоря вопрос, что делать? (база idb взята с форума) |
|
29.05.2012, 20:59 | #8 | |
MaNGOS Dev
Регистрация: 08.03.2010
Адрес: Ханты-Мансийск
Сообщений: 28
Сказал(а) спасибо: 27
Поблагодарили 13 раз(а) в 8 сообщениях
|
Цитата:
|
|
03.06.2012, 18:27 | #10 |
Новичок
Регистрация: 28.05.2012
Сообщений: 10
Сказал(а) спасибо: 1
Поблагодарили 0 раз(а) в 0 сообщениях
|
Ну понимаеш, LordJZ допустим не всегда будет снабжать нас всем необходимым, поэтому хотелось бы быть более независимым и сделать что то самому
|
03.06.2012, 22:02 | #12 |
Новичок
Регистрация: 28.05.2012
Сообщений: 10
Сказал(а) спасибо: 1
Поблагодарили 0 раз(а) в 0 сообщениях
|
вот собственно мы и незаметно перешли к той теме о которой я и хотел спросить)) Так кто-нибудь уже декомпилил панд? Как оно?
|
25.06.2012, 11:54 | #13 | |
MaNGOS Dev
Регистрация: 16.01.2011
Сообщений: 262
Сказал(а) спасибо: 57
Поблагодарили 73 раз(а) в 59 сообщениях
|
Вроде бы разобрался на основе 14333 с Jam и Auth опкодами, с ними все прозрачно.
Использовал Opcode Calculator от Chameleon. Но не понятно, что делать с regular opcodes, согласно его гайду Цитата:
Текстовый поиск по + 4844 или + 0x12EC ничего не дает, кроме Код:
void __usercall ClientDestroyGame(int a1<ebx>, double a2<st6>, double a3<st5>, double a4<st4>, double a5<st3>, double a6<st2>, double a7<st1>, double a8<st0>, int a9, int a10, int a11) { int v11; // eax@2 int v12; // eax@2 int v13; // eax@2 int v14; // eax@2 int v15; // eax@2 int v16; // eax@2 int v17; // eax@2 int v18; // eax@2 void *v19; // edi@3 int v20; // ecx@4 void *v21; // edi@9 if ( dword_CFA6A4 ) { EventUnregister(5, (int)ClientIdle); v11 = g_clientConnection; *(_DWORD *)(g_clientConnection + 4844) = 0; *(_DWORD *)(v11 + 13036) = 0; v12 = g_clientConnection; *(_DWORD *)(g_clientConnection + 2272) = 0; *(_DWORD *)(v12 + 10464) = 0; v13 = g_clientConnection; *(_DWORD *)(g_clientConnection + 1840) = 0; *(_DWORD *)(v13 + 10032) = 0; v14 = g_clientConnection; *(_DWORD *)(g_clientConnection + 3836) = 0; *(_DWORD *)(v14 + 12028) = 0; v15 = g_clientConnection; *(_DWORD *)(g_clientConnection + 1800) = 0; *(_DWORD *)(v15 + 9992) = 0; v16 = g_clientConnection; *(_DWORD *)(g_clientConnection + 5324) = 0; *(_DWORD *)(v16 + 13516) = 0; v17 = g_clientConnection; *(_DWORD *)(g_clientConnection + 7116) = 0; *(_DWORD *)(v17 + 15308) = 0; v18 = g_clientConnection; *(_DWORD *)(g_clientConnection + 6024) = 0; *(_DWORD *)(v18 + 14216) = 0; dword_E0C6CC = 0; dword_E0C650 = 0; dword_E0C600 = 0; dword_E0C864 = 0; if ( dword_CFA754 ) { v19 = dword_CFA754; sub_4011F0((int)dword_CFA754); SMemFree(v19, (int)"delete", -1, 0); dword_CFA754 = 0; } FriendList__Destroy(); AreaListShutdown(); CGItem_C__Shutdown(); CGPlayer_C__Shutdown(); PlayerClientShutdown(); LootDestroy(); CGUnit_C__Shutdown(a2, a3, a4, a5, a6, a7, a8); CGGameObject_C__Shutdown(); CGObject_C__Shutdown(); MovementDestroy(v20, a2, a3, a4, a5, a6, a7, a8); LootRollDestroy(); EventUnregister(5, (int)MovementIdleMoveUnits); Spell_C_StopTargeting(a2, a3, a4, a5, a6, a7, a8); ClntObjMgrDestroyStd(a2, a3, a4, a5, a6, a7, a8); CGUnit_C__PostShutdown(); CGObject_C__PostShutdown(); SpellVisualsClear(a2, a3, a4, a5, a6, a7, a8); CEffect__Destroy(a2, a3, a4, a5, a6, a7, a8); CGWorldFrame__SetScreenEffect(a7, a8, 0); if ( dword_E96728 ) CGWorldFrame__UnitClear((void *)dword_E96728); World__UnloadMap(); WeaponTrailsShutdown(); PlayerNameShutdown(); WorldTextShutdown(); CGGameUI__ShutdownGame(); ClientDestroyGameTime(); SI3__ShutDownAmbienceFlavor(); SI3__ShutdownZoneSoundsHandler(); SI3__FreeZoneIntros(); SE3__StopAllSounds(-2, 0); dword_DE74F0 = 0; ClntObjMgrDestroyShared(); AccountDataDestroy(0); dword_CFA6A4 = 0; LoadingScreenDisable(); if ( dword_CFA6B0 ) { dword_CFA6B0 = 0; dword_CFA6AC = 0; } BattlenetUI_LeaveRealm(); CAnimKitManager__UninitSystem(); CAnimReplacementSetDef__UninitSystem(); if ( dword_CFC8D0 ) { v21 = dword_CFC8D0; CCameraManager___CCameraManager((int)dword_CFC8D0); SMemFree(v21, (int)"delete", -1, 0); dword_CFC8D0 = 0; } if ( !a9 ) ClientDBDisconnect(); if ( a10 ) { CGlueMgr__Resume(a1, a9); if ( a11 ) dword_E7A798 = 11; CGlueMgr__SetScreen(); } } } Последний раз редактировалось Amaru; 25.06.2012 в 11:56. |
|
25.06.2012, 12:40 | #14 | |
Новичок
Регистрация: 04.03.2011
Сообщений: 29
Сказал(а) спасибо: 12
Поблагодарили 9 раз(а) в 7 сообщениях
|
Цитата:
Но необходимости в ручном поиске сейчас по большому счету нет т.к. под основные версии доступны полные дампы маппинга опкодов к хэндлерам. Что там серьезного поменялось? Я глубоко не смотрел и только мак версию, что с дебаг символами, но особых различий в обработке опкодов клиентом я не заметил. |
|
25.06.2012, 13:08 | #15 |
MaNGOS Dev
Регистрация: 16.01.2011
Сообщений: 262
Сказал(а) спасибо: 57
Поблагодарили 73 раз(а) в 59 сообщениях
|
Спасибо за быстрый ответ.
Для 14333 есть функция, Код:
protected override bool NormalCheck(uint opcode) { return (opcode & 0x2322) == 8738 && opcode != 57919 && opcode != 26159; } Код:
//----- (00485940) -------------------------------------------------------- char __thiscall NetClient__ProcessMessage(void *this, int a2, int a3, int a4) { int v4; // ebx@1 void *v5; // edi@1 unsigned int v6; // esi@1 char result; // al@2 unsigned int v8; // eax@3 int v9; // ecx@6 ++dword_D36C28; v4 = a3; v5 = this; CDataStore__GetInt16(a3, (int)&a3); v6 = (unsigned __int16)a3; if ( (a3 & 0x2399) == 0x301 ) { result = NetClient__JAMClientDispatch(v5, 0, a2, a3, v4); } else { (*(void (__thiscall **)(void *, _DWORD))(*(_DWORD *)v5 + 72))(v5, (unsigned __int16)a3); v8 = v6 & 1 | ((v6 & 0x1C | (((unsigned __int8)(v6 & 0xC0) | ((v6 & 0x1C00 | (v6 >> 1) & 0x6000) >> 2)) >> 1)) >> 1); if ( (v6 & 0x2322) == 0x2222 && v6 != 0xE23F && v6 != 0x662F && (v9 = *((_DWORD *)v5 + v8 + 344)) != 0 ) result = ((int (__cdecl *)(_DWORD, unsigned int, int, int))(v9 - ((v6 | (v6 << 16)) ^ 0x62A3A31D)))( *((_DWORD *)v5 + v8 + 2392), v6, a2, v4); else result = (*(int (__thiscall **)(int))(*(_DWORD *)v4 + 24))(v4); } return result; } Правильно ли, что в *((_DWORD *)v5 + v8 + 2392) будет как раз адрес обработчика? |
26.06.2012, 10:38 | #16 | |
Новичок
Регистрация: 04.03.2011
Сообщений: 29
Сказал(а) спасибо: 12
Поблагодарили 9 раз(а) в 7 сообщениях
|
Цитата:
Мне интересно, Amaru, что вы хотите разработать на основе этой информации? Проблема маппинга опкодов к хэндлерам уже решена полностью. Проблема маппинга опкодов между версиями решена удовлетворительно, хотя всегда есть место для улучшения. Основная проблема для поддержки новых и будущих версию, как я вижу - это создание автоматизированного парсера структуры Jam bitstream пакетов. Чтобы в ядре порядок данных в структуре пакетов мог оставаться стабильным между версиями, а порядок сериализации этих данных для конкретной версии основывался бы на информации сгенерированной этим чудесным парсером. Имея такую штуку можно было бы думать о переходе между новыми версиями за разумное время, без траты уймы времени на ручную перестановку порядка филдов в куче JAM пакетов. |
|
25.06.2012, 14:45 | #17 |
MaNGOS Dev
Регистрация: 11.03.2010
Сообщений: 468
Сказал(а) спасибо: 0
Поблагодарили 514 раз(а) в 163 сообщениях
|
Разве обработчик не
Код:
v9 - ((v6 | (v6 << 16)) ^ 0x62A3A31D) ? Код:
char __thiscall NetClient::ProcessMessage(ClientConnection *this, int a2, int opcode, int a4) { CDataStore *data; // ebx@1 ClientConnection *_this; // edi@1 unsigned int op; // esi@1 char result; // al@2 unsigned int condensedId; // eax@3 void *jamOffs; // ecx@6 ++dword_D36C28; data = opcode; _this = this; CDataStore::GetInt16(opcode, &opcode); op = opcode; if ( (opcode & 0x2399) == 0x301 ) { result = NetClient::JAMClientDispatch(_this, 0, a2, opcode, data); } else { (*(_this->vTable + 18))(_this, opcode); condensedId = op & 1 | ((op & 0x1C | (((op & 0xC0) | ((op & 0x1C00 | (op >> 1) & 0x6000) >> 2)) >> 1)) >> 1); if ( (op & 0x2322) == 0x2222 && op != 0xE23F && op != 0x662F && (jamOffs = _this->Handlers[condensedId]) != 0 ) result = ((jamOffs - ((op | (op << 16)) ^ 0x62A3A31D)))(_this->HandlersState[condensedId], op, a2, data); else result = data->vTable->IsRead(data); } return result; } Последний раз редактировалось TOM_RUS; 25.06.2012 в 14:56. |
Пользователь сказал cпасибо: | Amaru (25.06.2012) |
25.06.2012, 17:03 | #18 |
MaNGOS Dev
Регистрация: 16.01.2011
Сообщений: 262
Сказал(а) спасибо: 57
Поблагодарили 73 раз(а) в 59 сообщениях
|
Да, так и есть... Значит, чтобы получить адрес хендлер нужен адрес jamOffs или адрес _this.
Написал функцию получения jamOffst и _this по известным хендлерам, для разных пакетов _this получается разный... При этом jamOffst совпадает с данными от LordJZ Код:
//unsigned int op = 30375; //smsg notification //unsigned int * handle = (unsigned int *)0x00401230; unsigned int op = 43638; //smsg whois unsigned int * handle = (unsigned int *)0x004CF590; unsigned int * jamOffs = (unsigned int*)((unsigned int)handle + ((op | (op << 16)) ^ 0x62A3A31D)); unsigned int condensedId = op & 1 | ((op & 0x1C | (((op & 0xC0) | ((op & 0x1C00 | (op >> 1) & 0x6000) >> 2)) >> 1)) >> 1); std::cout << std::hex << "Jam: " << jamOffs << std::endl; unsigned int * _this = (unsigned int *)(jamOffs - 344 - condensedId); std::cout << std::hex << "This: " << _this << std::endl; Последний раз редактировалось Amaru; 26.06.2012 в 10:07. |
26.06.2012, 11:10 | #19 |
MaNGOS Dev
Регистрация: 16.01.2011
Сообщений: 262
Сказал(а) спасибо: 57
Поблагодарили 73 раз(а) в 59 сообщениях
|
Я пытаюсь получить handle для конкретного опкода
Как выяснилось, надо дополнительно знать либо _this, либо jamOffs Решая обратную задачу, располагая найденным адресом какого либо хендлера, можно получить и _this и jamOffs, а далее использовать их для получения хендлеров всех остальных окодов. Но то ли я что-то не понимаю, либо что-то не так считаю, но адреса остальных хендлеров не получаются При этом посчитанный в начале jamOffs совпал с адресом, указанным LordJZ в его xml, что означает, что хендлер, из которого он считался, был найден правильно Последний раз редактировалось Amaru; 26.06.2012 в 11:18. |
27.06.2012, 02:01 | #20 |
MaNGOS Dev
Регистрация: 16.01.2011
Сообщений: 262
Сказал(а) спасибо: 57
Поблагодарили 73 раз(а) в 59 сообщениях
|
Запарившись разбираться, написал патч, который считает все auth и special оффсеты, а также перехватывает адрес хендлера обычных пакетов, когда идет к нему обращение
https://github.com/Zakamurite/OpcodeHandleDumper Офсет выдается относительно IDA, для известных опкодов адреса корректны, но можно найти и левые адреса, но среди них использующихся/известных опкодов не находил, скорее всего это несуществующие опкоды |
2 пользователя(ей) сказали cпасибо: | blackmanos (03.08.2013), Ranger (19.07.2012) |
27.04.2014, 20:04 | #21 |
Пользователь
Регистрация: 05.04.2010
Сообщений: 53
Сказал(а) спасибо: 4
Поблагодарили 24 раз(а) в 15 сообщениях
|
"обработки пакета под номером 459(SMSG_NOTIFICATION)" - подскажите откуда берется символьное значение опкода SMSG_NOTIFICATION=459, из клиента?
|
27.04.2014, 21:16 | #22 |
Умный
Регистрация: 17.06.2010
Сообщений: 397
Сказал(а) спасибо: 58
Поблагодарили 55 раз(а) в 38 сообщениях
|
из сниффа
|
27.04.2014, 22:38 | #23 |
Пользователь
Регистрация: 05.04.2010
Сообщений: 53
Сказал(а) спасибо: 4
Поблагодарили 24 раз(а) в 15 сообщениях
|
Это не ответ, тот кто писал сниф - выдумал названия, или гдето в клиенте есть точные названия?
|
27.04.2014, 23:17 | #24 |
MaNGOS Dev
Регистрация: 11.03.2010
Сообщений: 468
Сказал(а) спасибо: 0
Поблагодарили 514 раз(а) в 163 сообщениях
|
|
27.04.2014, 22:54 | #25 |
Умный
Регистрация: 17.06.2010
Сообщений: 397
Сказал(а) спасибо: 58
Поблагодарили 55 раз(а) в 38 сообщениях
|
насколько я знаю было, но давно, очень давно
|
28.04.2014, 13:48 | #26 |
Пользователь
Регистрация: 05.04.2010
Сообщений: 53
Сказал(а) спасибо: 4
Поблагодарили 24 раз(а) в 15 сообщениях
|
Я начал собирать снифом с оффа информацию с билда 18019. Парсер пакетов как оказалось устарел и неисправен - опкоды изменились. Где можно взять последнюю возможную информацию как теперь в вов 5.4 искать среди дезасемблированого кода нужную информацию по обработке пакетов а также соответствие опкода - номеру. Выложеный вариант файла для IDA 6.1 у меня не открывается.
|
06.05.2014, 18:09 | #27 | |
MaNGOS Dev
Регистрация: 16.01.2011
Сообщений: 262
Сказал(а) спасибо: 57
Поблагодарили 73 раз(а) в 59 сообщениях
|
Цитата:
Номера опкодов пока что одинаковые для всех билдов 5.4.7 Если ида не открывает - качай другую ида |
|
08.06.2014, 14:30 | #28 |
Пользователь
Регистрация: 05.04.2010
Сообщений: 53
Сказал(а) спасибо: 4
Поблагодарили 24 раз(а) в 15 сообщениях
|
Я разобрался с SMSG_..., остался вопрос как в клиенте 18291 искать подготовку пакетов CMSG_... ?
|
06.05.2014, 17:24 | #29 |
Пользователь
Регистрация: 05.04.2010
Сообщений: 53
Сказал(а) спасибо: 4
Поблагодарили 24 раз(а) в 15 сообщениях
|
Я так понял, что вов 5.4 - табу? никто так и не ответил.
|
25.09.2014, 19:58 | #30 |
Пользователь
Регистрация: 05.04.2010
Сообщений: 53
Сказал(а) спасибо: 4
Поблагодарили 24 раз(а) в 15 сообщениях
|
Поделитесь .pkt или .bin с оффа для 4.3.4?
|
|
|
Похожие темы | ||||
Тема | Автор | Раздел | Ответов | Последнее сообщение |
Разбор кода Спелов | partizanes | Прочая документация | 7 | 24.01.2012 15:29 |
шифрование пакетов в 3.3.2 | 84ivan | Копаем клиент | 8 | 05.10.2010 19:10 |
Шифрование пакетов в 11685 | Konctantin | Корзина | 2 | 27.03.2010 12:34 |