Сбербанк или туда и обратно



ГЛАВА 1. Нежданные гости


Все началось в то злополучное утро, когда Project Manager сообщил, что сроки реализации проекта должны быть быстро и решительно сокращены на месяц. Точнее говоря проект должен быть готов через 4 дня. Нет, наш PO не зверь, и ничуть не похож на сову (разве что чуть-чуть на ворона), просто так сложилось. Ну раз надо, так надо, тем более что команде (а я являюсь ведущим разработчиком команды «С») было обещано что-то вкусное. На часах и календаре был четверг, 11:00, к понедельнику проект должен быть готов.

Для начала, чем мы вообще занимаемся. Мы занимаемся автоматизацией кинотеатров — автоматическим и дистанционным управлением оборудования, автоматизацией кинопоказа, мониторингом, видеопанелями, а теперь еще и терминалами продажи билетов и бара. Конкретно последнему пункту и посвящена данная статья.

Сам проект, который нужно было завершить до понедельника представляет из себя некую прослойку между основным сервером на Scala и железным терминалом оплаты VeriFone VX 820 (на самом деле терминалов больше, но для примера возьмем только его). Понятно, что просто так проводить через него транзакции нам никто не даст, поэтому используются утилиты и библиотеки Сбербанка/Arcus и UCS. Таким образом схема работы в итоге должна быть следующей:



Внешне он выглядит вот так:



Также данная подсистема должна использоваться на стандартных кассовых машинах которые все видели в любом кинотеатре у кассиров.

Согласно внутренней традиции каждый проект нашей команды мы называем именем из древнескандинавской мифологии, для данной подсистемы было выбрано имя Gefjon — Имя богини плодородия и изобилия (неплохое название для сервера оплаты, разве нет? Ну и легенда о быках отрезающих остров идеально ложится на текущую архитектуру, отрезая работу с оборудованием от высокоуровневого языка).

Формат входящих и выходящих сообщений — HTTP сервер с JSON нагрузкой. Это оптимальный компромисс между Scala, которой сложно опуститься до вычленения бинарных данных из socket-потоков и C, которому трудно подняться до передачи объектов через сеть. Возможных операций, которыми необходимо оперировать не так много: оплата, отмена, возврат, разные типы отчетов, открытие сервисного меню и ping. С виду ничего сложного. Так как банковских систем целых три (а в последствии ожидается пополнение семейства), то было решено разделить проект на компоненты:



Зеленым покрашены блоки, которые нам нужно было сделать, синим — те, которые нельзя поменять и которые предоставляет банк.

Так как основные проблемы возникли только с ПО от Сбербанка, то статья в целом будет посвящена подводным камням, которые мы пересчитали своей ладьей.

ГЛАВА 2. Баранье жаркое



(фото: heaclub.ru)

… выглядит примерно так. Примерно так же выглядел код того прототипа, который был написан несколько месяцев назад для того, чтобы дать понять всем вышестоящим людям, что мы можем работать с банковскими приложениями.

        char buf[BUF_KB * 2];
        char * null;
        char * grep;
 
#ifdef _WIN32_WINNT
        char * ptr;
        null = "nul";
        grep = "findstr";
#else
        null = "/dev/null";
        grep = "grep";
#endif
        sprintf(buf, "%s %"PRIi32"= %sops.ini >%s 2>%s || "
                     "echo %"PRIi32"=9,6,PINPAD_TEST >> %sops.ini",
               grep,
               TERM_ARCUS_TEST_PINPAD,
               TERM_PATH,
               null,
               null,
               TERM_ARCUS_TEST_PINPAD,
               TERM_PATH);
 
#ifdef _WIN32_WINNT
        ptr = buf;
        while (*ptr)
        {
            if (*ptr == '/')
                *ptr = '\\';
            ptr++;
        }
#endif

Понятное дело, что для Production варианта это не годилось, поэтому нужно было по сути написать все заново.

Каждый банк, который предоставляет библиотеки для работы с терминалом обычно предоставляет два варианта подключения: через функции библиотеки (.so/.dll) или посредством готовой утилиты, которой всего-то нужно передать два значения — тип операции и сумму (когда нужно). В теории ничего сложного, всего-то

char buffer[100];
sprintf(buffer, "%d %d", atoi(argv[1]), atoi(argv[2]));
system(buffer);

Результат операции при этом будет помещен в файл «e», а слип-чек — в файл «p». Просто отправим эти файлы на stdout с преобразованием в JSON, чтобы HTTP-сервер просто отправил их наверх как payload без размышлений о том, что там.

Но эта статья не вышла бы, если бы все было так просто.

ГЛАВА 4. Через гору и под горой


Первоначальный вариант реализации представлял из себя простой вызов приложения — HTTP-сервер вызывал нужную обертку с унифицированными параметрами (например X-отчет это 4), а утилита например gfj_pilot запускала sb_pilot с параметром, который требовался для это операции (например X-отчет это 9). Затем утилита-обертка читала из е-файла результат операции (например 2000 — «отказ оплаты, повторите операцию») и преобразовывала в универсальную ошибку (например 3 — «Ошибка чтения или процессинга карты/счета, повторите операцию»). После этого файл «p» преобразовывался в base64 для избежания ломания форматирования и отсылался вместе с результатом в stdout в виде JSON.

Все это прекрасно работало, пока в один прекрасный момент нам не сообщили, что…

… это не работает под Windows.



Ну точнее у самого Windows проблем нет (кроме того, что слип генерируется в кодировке Cp-1251, а консоль работает в CP866). Просто не генерировался «е» файл. Запустили банковскую утилиту напрямую:

C:\banks\sber\sb_pilot>dir
 Том в устройстве C не имеет метки.
 Серийный номер тома: B401-6B9D
 
 Содержимое папки C:\banks\sber\sb_pilot
 
04.02.2019  12:28    <DIR>          .
04.02.2019  12:28    <DIR>          ..
31.01.2019  17:12            10 832 F12X24.BIN
31.01.2019  17:12           128 000 gate.dll
31.01.2019  17:12            72 192 loadparm.exe
31.01.2019  17:12            36 204 OPT0.R
31.01.2019  17:12            20 716 OPT1.R
31.01.2019  17:12             1 806 OPT3.R
31.01.2019  17:12           388 608 pilot_nt.dll
31.01.2019  23:06               463 pinpad.ini
31.01.2019  17:12            91 136 posScheduler.exe
31.01.2019  17:12               418 printers.ini
01.02.2019  16:51            91 646 sbkernel1902.log
31.01.2019  17:12           653 312 sbrf.dll
31.01.2019  17:12           840 192 SBRFCOM.dll
31.01.2019  17:12         3 142 656 sb_kernel.dll
01.02.2019  16:51                 9 SESS.D
01.02.2019  16:51               715 SPLC.D
31.01.2019  17:12            72 192 upwin.exe
              20 файлов      5 659 718 байт
               2 папок  37 567 004 672 байт свободно
 
# Отправляем команду оплаты (1) на 10 рублей (1000 копеек)
C:\banks\sber\sb_pilot>loadparm.exe 1 1000
 
C:\banks\sber\sb_pilot>dir
 Том в устройстве C не имеет метки.
 Серийный номер тома: B401-6B9D
 
 Содержимое папки C:\banks\sber\sb_pilot
 
04.02.2019  12:28    <DIR>          .
04.02.2019  12:28    <DIR>          ..
04.02.2019  12:28               216 commerr.log
31.01.2019  17:12            10 832 F12X24.BIN
31.01.2019  17:12           128 000 gate.dll
31.01.2019  17:12            72 192 loadparm.exe
31.01.2019  17:12            36 204 OPT0.R
31.01.2019  17:12            20 716 OPT1.R
31.01.2019  17:12             1 806 OPT3.R
01.02.2019  18:51             1 349 p
31.01.2019  17:12           388 608 pilot_nt.dll
31.01.2019  23:06               463 pinpad.ini
31.01.2019  17:12            91 136 posScheduler.exe
31.01.2019  17:12               418 printers.ini
04.02.2019  12:28            92 218 sbkernel1902.log
31.01.2019  17:12           653 312 sbrf.dll
31.01.2019  17:12           840 192 SBRFCOM.dll
31.01.2019  17:12         3 142 656 sb_kernel.dll
01.02.2019  16:51                 9 SESS.D
01.02.2019  16:51               715 SPLC.D
31.01.2019  17:12            72 192 upwin.exe
              19 файлов      5 659 029 байт
               2 папок  37 567 008 768 байт свободно
 
C:\banks\sber\sb_pilot>

Действительно, «e»-файла нет. Камень в сторону Сбербанка #1. Пишем письмо в сбербанк (впоследствии получили ответ, что так и должно быть), а так как времени на переписку нет и надо запускаться вот прям уже, ищем обходные пути получения результата.

04.02 12:28:55 SBKRNL: Failed to open device \\.\COM1, err 2
04.02 12:28:56 SBKRNL: Failed to open device \\.\COM1, err 2
04.02 12:28:56 SBKRNL: Result  = 0
04.02 12:28:56 GATE: unlock:'00000054'
04.02 12:28:56 GATE: lock:'00000054' 'UPOSWINMUTEX2'
04.02 12:28:56 GATE: unlock:'00000054'
04.02 12:28:56 LOADPARM: Unloading GATE.DLL...
04.02 12:28:56 GATE: SB_KERNEL.DLL is unloaded
04.02 12:28:56 LOADPARM: GATE.DLL unloaded

Ага, результат можно получить из лога sbkernelГГММ.log. Неудобно, плюс нет хеша карты чтобы впоследствии прикрутить «Спасибо» от сбербанка. Не годится.

Придется подключаться к библиотеке pilot_nt.dll и импортировать из нее функции. Все бы ничего, но… Камень в сторону Сбербанка #2: под Linux такой библиотеки нет, придется создавать два разных приложения под разные платформы — для linux вызывать утилиту sb_pilot (аналог loadparm.exe, кстати камень #3 за разное название утилиты под разными платформами), под windows подключаться к библиотеке pilot_nt.dll.

ГЛАВА 5. Загадки в темноте


На часах 19:00.

Сбербанк — компания крупная, большинство программных решений производятся по ГОСТам и формальным документам. Залезаем в каталог, который поставляет Сбербанк вместе с библиотеками:

Sberbank$ ls -l Docs
итого 30160
drwx------ 2 alex alex    4096 янв 17 19:31 FAQ
-rw-rw-r-- 1 alex alex 3398465 май  9  2018 Базовая настройка UPOS для автономного решения (АР).docx
-rw-rw-r-- 1 alex alex 1182078 май  9  2018 Базовая настройка UPOS для ИКР.docx
-rw-rw-r-- 1 alex alex  853504 май  9  2018 Версии и изменения.doc
drwx------ 3 alex alex    4096 янв 31 17:11 Для разработчиков ПО ККМ
-rw-rw-r-- 1 alex alex 5280787 май  9  2018 Загрузка ПО в POS-терминалы.docx
-rw-rw-r-- 1 alex alex 1149640 май  9  2018 Коды ошибок.docx
drwx------ 2 alex alex    4096 май 28  2018 Настройка UPOS
drwx------ 2 alex alex    4096 май 28  2018 Настройка кассовых программ
-rw-rw-r-- 1 alex alex 3451601 май  9  2018 Определение схемы автономного решения (АР).docx
-rw-rw-r-- 1 alex alex 1956196 май  9  2018 Определение схемы ИКР.docx
-rw-rw-r-- 1 alex alex 1043161 май  9  2018 Памятка по настройке функции ОПЛАТА авиабилетов (Аэрофлот)_(ИКР).docx
-rw-rw-r-- 1 alex alex 4348157 май  9  2018 Параметры POS-терминалов.docx
-rw-rw-r-- 1 alex alex 3970267 май  9  2018 Подключение отдельных функций.docx
drwx------ 3 alex alex    4096 май 28  2018 Руководства пользователя
-rw-rw-r-- 1 alex alex 2644702 май  9  2018 Руководство по настройке POS-терминалов.docx
drwx------ 2 alex alex    4096 май 28  2018 Сопроводительная документация
-rw-rw-r-- 1 alex alex 1558211 май  9  2018 Схема содержания документов.png

Куча добра, однако нас интересует только каталог для разработчиков:

Sberbank$ ls -l Docs/Для\ разработчиков\ ПО\ ККМ/
итого 8704
-rw-rw-r-- 1 alex alex   47105 май  9  2018 1C.docx
-rw-rw-r-- 1 alex alex    1824 май  9  2018 cardtype.h
-rw-rw-r-- 1 alex alex 2590378 май  9  2018 cr_ttk_protocol_ru.rtf
-rw-rw-r-- 1 alex alex     208 май  9  2018 deprtmnt.h
-rw-rw-r-- 1 alex alex   16681 май  9  2018 errors.h
drwx------ 6 alex alex    4096 май 28  2018 examples
-rw-rw-r-- 1 alex alex   58575 май  9  2018 gate.h
-rw-rw-r-- 1 alex alex    4218 май  9  2018 paramsln.h
-rw-rw-r-- 1 alex alex   61693 май  9  2018 pilot_nt.h
-rw-rw-r-- 1 alex alex   28160 май  9  2018 ReadTrack2.doc
-rw-rw-r-- 1 alex alex    7417 май  9  2018 sbkernel.h
-rw-rw-r-- 1 alex alex  144896 май  9  2018 sb_pilot.doc
-rw-rw-r-- 1 alex alex 3525323 май  9  2018 Интеграция с ККМ через ole-объект sbrf.dll.rtf
-rw-rw-r-- 1 alex alex   46683 май  9  2018 Интеграция с ККМ через библиотеку gate.dll.chi
-rw-rw-r-- 1 alex alex  255414 май  9  2018 Интеграция с ККМ через библиотеку gate.dll.chm
-rw-rw-r-- 1 alex alex  814653 май  9  2018 Интеграция с ККМ через библиотеку gate.dll.pdf
-rw-rw-r-- 1 alex alex   41618 май  9  2018 Интеграция с ККМ через библиотеку pilot_nt.chi
-rw-rw-r-- 1 alex alex  241716 май  9  2018 Интеграция с ККМ через библиотеку pilot_nt.chm
-rw-rw-r-- 1 alex alex  968753 май  9  2018 Интеграция с ККМ через библиотеку pilot_nt.pdf
-rw-rw-r-- 1 alex alex      81 май  9  2018 Подтипы пинпадов.txt

Много макулатуры, на всякий случай еще раз перечитаем pilot_nt, из которой узнаем следующее:
Таблица 1. Поддерживаемые sb_pilot ОС.
ОС Разрядность Имя модуля
Windows 32 sb_pilot.exe
Linux 32 sb_pilot
DOS 16 sb_pilot.exe

Оказывается утилита под windows должна все-таки называться sb_pilot. Что ж, камень в сторону Сбербанка #4 за несоответствие собственной документации.
Передача результатов работы программы.

По окончании работы программы формируются два текстовых файла — файл обмена и файл чека.

Первый имеет имя e и предназначен для передачи вызывающей программе параметров совершенной операции. Первая строка в этом файле содержит код результата операции, и через запятую – поясняющее текстовой сообщение. Код 0 означает успешное проведение платежа, любое другое значение – отказ или невозможность проведения платежа.




Лениво кидаем еще один камень и начинаем изучать документацию на подключение библиотеки напрямую.
Порядок вызова функций библиотеки

При оплате (возврате) покупки по банковской карте кассовая программа должна вызвать из библиотеки Сбербанка функцию card_authorize(), заполнив поля TType и Amount и указав нулевые значения в остальных полях. По окончании работы функции необходимо проанализировать поле RCode. Если в нем содержится значение «0» или «00», авторизация считается успешно выполненной, в противном случае отклоненной. Кроме этого, необходимо проверить значение поля Check.

Если оно не равно NULL, его необходимо отправить на печать (в нефискальном режиме) и затем
удалить вызовом функции GlobalFree(). При закрытии смены кассовая программа должна вызвать из библиотеки Сбербанка функцию close_day(), заполнив поле TType = 7 и указав нулевые значения в остальных полях. По окончании работы функции необходимо проверить значение поля Check.

Если поле Check не равно NULL, его необходимо отправить на печать (в нефискальном режиме) и после этого удалить вызовом функции GlobaFree().
Звучит несложно, даже хэдер файл предоставлен. Что ж, подключаем его, компилируем и…

$ cat main.c && i686-w64-mingw32-gcc main.c -o main.a
#include "pilot_nt.h"
 
int main(void) {
    return 0;
}
 
 
In file included from main.c:1:0:
pilot_nt.h:525:3: error: unknown type name ‘auth_answer’
   auth_answer   ans;                     /**< [in, out]  �������� ��������� ��������. ��. ::auth_answer */
   ^
pilot_nt.h:544:3: error: unknown type name ‘auth_answer’
   auth_answer   ans;               /**< [in, out]  �������� ��������� ��������. ��. ::auth_answer */
   ^
pilot_nt.h:567:3: error: unknown type name ‘auth_answer’
   auth_answer   ans;               /**< [in, out]  �������� ��������� ��������. ��. ::auth_answer */
   ^
pilot_nt.h:590:3: error: unknown type name ‘auth_answer’
   auth_answer   ans;              /**< [in, out]  �������� ��������� ��������. ��. ::auth_answer */
   ^
pilot_nt.h:627:3: error: unknown type name ‘auth_answer’
   auth_answer   ans;              /**< [in, out]  �������� ��������� ��������. ��. ::auth_answer */
   ^
pilot_nt.h:668:3: error: unknown type name ‘auth_answer’
   auth_answer   ans;               /**< [in, out]  �������� ��������� ��������. ��. ::auth_answer */

Эммм… Что? Открываем pilot_nt.h:

#ifdef __cplusplus
extern "C"{
#endif
<...>
/**
 * Основные параметры операции
 * Структура, используемая для описания операции и получения результатов выполнения операции.
 */
struct auth_answer{
   int TType;             /**< [in] тип транзакции. см ::OpetationTypes */
   unsigned long Amount;  /**< [in] сумма в копейках                    */
   char RCode[3];         /**< [out] код результата авторизации         */
   char AMessage[16];     /**< [out] текст результата авторизации       */
   int  CType;            /**< [in,out] тип карты                       */
   char* Check;           /**< [out] образ чека, должен освобождаться GlobalFree в вызывающей программе */
};
<...>
struct auth_answer7{
  auth_answer auth_answ;           /**< [in, out]  Основные параметры операции. См. ::auth_answer */ <---- THIS
  char   AuthCode[MAX_AUTHCODE];  /**< [out] Код авторизации. 7 байт.                            */
  char   CardID [CARD_ID_LEN];     /**< [out] Идентификатор карты. 25 байт.                       */
  int    SberOwnCard;              /**< [out] Флаг принадлежности карты Сбербанку                 */
};

Сразу, не глядя камень за комментарии на русском в кодировке CP1251.

Ну и самый серьезный камень: дорогие разработчики на С++. Если вы пишете extern «C» — это означает, что код внутри блока должен компилироваться С-компилятором. Если вы НЕ сделали `typedef` структуры, то при каждом ее упоминании в качестве указания типа необходимо писать ключевое слово `struct`.

Патчим файл для разработчиков, подставляя везде, где нужно слово `struct`. Линкуемся с библиотекой `pilot_nt.dll`. Победа, не? Запускаем наше приложение.

ГЛАВА 6. Из огня да в полымя


Ну вы поняли, да? Приложение просто падает. Сразу, до main. Медитируем, добавляем NIH-аналог функции errno для windows: GetLastError (камень #3 в сторону Microsoft, первые два за кодировки).

C:\banks\sber\WIN>sb_pilot.exe 1 1000
E: !g_sblibrary (0xc0000096)

0xc0000096? А разве GetLastError не должна возвращать адекватный код ошибки?
For a complete list of error codes provided by the operating system, see System Error Codes.
Ага, открываем статью по ссылке:
The following topics provide lists of system error codes. These values are defined in the WinError.h header file.

  • System Error Codes (0-499) (0x0-0x1f3)
  • System Error Codes (500-999) (0x1f4-0x3e7)
  • System Error Codes (1000-1299) (0x3e8-0x513)
  • System Error Codes (1300-1699) (0x514-0x6a3)
  • System Error Codes (1700-3999) (0x6a4-0xf9f)
  • System Error Codes (4000-5999) (0xfa0-0x176f)
  • System Error Codes (6000-8199) (0x1770-0x2007)
  • System Error Codes (8200-8999) (0x2008-0x2327)
  • System Error Codes (9000-11999) (0x2328-0x2edf)
  • System Error Codes (12000-15999) (0x2ee0-0x3e7f)
Отлично, мы получили незадокументированную ошибку, кидаем камень и открываем всезнающий google:


Суть ошибки сводится к тому, что какая-то подпрограмма использует одну из инструкций

  • _inp()
  • _inpw()
  • _inpd()
  • _outp()
  • _outpw()
  • _outpd()

Использование которых запрещено под NT-ядрами, так как они пытаются работать с параллельным портом напрямую. Судя по всему этот код вызывается в инициализаторе библиотеки, т.е. библиотека при старте хочет опросить порты на наличие устройств, но NT-ядро требует работы через драйвер.

Безвыходная ситуация?

ГЛАВА 8. Пауки и мухи


22:00. На всякий случай возникает идея проверить, что это не из-за того, что мы используем кросскомпиляцию с Linux с помощью mingw. Параллельно понимаем, что Сбербанк поставляет только 32хбитное приложение, поэтому слинковаться с 64хбитным приложением не выйдет, ну ладно, но все равно запустим камень в сторону Сбербанка за 32-only версию в 2019м году.

Дано: установленная в virtualbox windows 7;
Необходимо: установить Visual Studio и скопилировать MVP.

Заходим на сайт Microsoft, качаем Visual Studio 2017. Берем лицензию сообщества, так как мы берем ее для проверки, для бизнеса лицензия будет куплена, если взлетит.
Скачиваем несколько сотен мегабайт и…

Видим, что наша версия ОС (Windows 7) не поддерживается.

Ок, идем на всякие непотребные сайты, ищем Visual Studio 2008, скачиваем несколько сотен мегабайт заново и…

Получаем iso файл.

Ладно, попытаемся установить Daemon Tools 10 (так как это та версия, которую предлагает сайт), чтобы вставить этот виртуальный диск.

Запускаем скачанный бинарь. Осечка, требуется .NET Framework 4.5, скачиваем, ставим.
Запускаем скачанный бинарь, установка началась, загрузчик говорит что ему нужна 4.5.2, скачиваем, ставим.
Запускаем скачанный бинарь, установка началась, загрузчик говорит что никуда не поедет, пока мы не поставим обновление безопасности KB3033929, скачиваем, ставим.

И получаем оплеуху от Microsoft в виде сообщения:



Яростно кидаем очень острый камень в сторону Microsoft, качаем с торрентов старый Daemon Tools, успешно распаковываем Visual Studio, устанавливаем, наконец-таки (00:00) компилируем MVP, получаем такую же ошибку. Что ж, хорошая была версия, но не срослось.

ГЛАВА 11. На пороге


Пишем второму программисту, который в этот момент в срочном порядке допиливает сервер и процедуру регистрации. Он вспоминает, есть гит-репозиторий, который на NT-подключает эту библиотеку и работает с ней.

Подозрительно глядя на репозиторий скачиваем его, компилируем и запускаем. Работает.



Еще более подозрительно смотрим на код. Код идентичен, разве что написан на C++ а не С.
Понимаем, что язык тут не причем. Смотрим библиотеки сбербанка, которые тянет за собой код.
Видим последний коммит.

И вот тут нас поджидает очередной сюрприз.

Оказывается, что версии библиотеки Сбербанка могут быть разными. Последний коммит увеличивает версию с 23 до 27й. Копируем себе на тестовый компьютер версию из гита — РАБОТАЕТ!

Проверяем все архивы, которые присылал Сбербанк, сравниваем версии и строим табличку:
Версия Работает
26.0.15 — Основная нет
27.4.12 — Из репозитория да
23.0.13 — Из репозитория да
29.0.9 — Самая свежая от СБ да
23.0.13 — С патчем для системы «Криптера» да

Отлично, вот теперь заживем. На тех системах где стоит 26 обновим до 29 или 27 и все взлетит.
Кидаем камень #9 в сторону Сбербанка за то, что сломали поведение на NT системах.

ГЛАВА 12. Что ждало их внутри


Не хватает «е» файла? Не беда, берем патченные заголовочники, динамически линкуемся с библиотекой чтобы корректно вернуть ошибку, пишем код, который просто запишет код возврата из функции в файл «е», назовем бинарь sb_pilot.exe и…

Работать-то оно работает.

Вот только на версии для системы «Криптера» не создается «р» файл.

Грустно смотрим на капающую по костяшкам кровь и на вмятину в стене.

Для начала, что такое система «Криптера».

Cryptera — это датская компания, выпускающая шифрующее оборудование/оборудование безопасности/ключи и пр. Думаю, что вы все видели один из экземпляров их продукции:



Так вот Сбербанк использует их криптомодуль для пинпадов и выпускает специальную «патченную» библиотеку, в которой, как мы уже поняли, не создается файл «р». Пишем по этому поводу в Сбербанк и через несколько дней получим ответ, что «под оригинальной системой файл „р“ будет создаваться, а под патченной на Криптеру — нет». Выдадим им камень # 10 через несколько дней, ведь работать нужно уже сейчас.

К счастью, или к несчастью, но функции, которые мы используем для проведения операций возвращают уже упомянутую структуру:

struct auth_answer{
   int TType;             /**< [in] тип транзакции. см ::OpetationTypes */
   unsigned long Amount;  /**< [in] сумма в копейках                    */
   char RCode[3];         /**< [out] код результата авторизации         */
   char AMessage[16];     /**< [out] текст результата авторизации       */
   int  CType;            /**< [in,out] тип карты                       */
   char* Check;           /**< [out] образ чека, должен освобождаться GlobalFree в вызывающей программе */
};

О, отлично, чек уже есть, можем сами сохранить его в файл или сразу вывести в JSON…

printf("%s\n", answer.Check);

И получаем падение приложение из-за обращения по невалидному указателю.

ГЛАВА 14. Огонь и вода


4:00. Выполняем Сету Бандха Сарвангасану чтобы успокоиться, и внимательно читаем мануал:
[out] образ чека, должен освобождаться GlobalFree в вызывающей программе
Что это нам дает? Очень многое. Во-первых то, что раз указатель требует очистки с помощью GlobalFree то его саллоцировали с помощью GlobalAlloc. Следовательно она выдает не указатель на память, как было в 16битной версии, а номер объекта с семантически объявленым типом HGLOBAL, который можно скормить в функции GlobalSize чтобы получить размер выделенного блока и GlobalLock чтобы заблокировать кусок памяти, но получить оригинальный указатель. Кстати, камень #6 в сторону Microsoft за NIH malloc и free, которые есть в стандартной библиотеке.

printf("%s\n", GlobalLock(answer.Check));

И все равно получаем падение. Окей, а что показывае GlobalSize? Ноль? Как-то странно.

Проверяем другие функции, которые тоже должны отдавать слип — видим ту же картину.

В голову приходит разве что самостоятельно сгенерировать слип по тем данным, которые может выдать самая крутая функция оплаты (да, у Сбербанка функции именуются card_authorize2..14, камень кидать за это не буду):

struct auth_answer14 {
  auth_answer   ans;               /**< [in, out]  Основные параметры операции. См. ::auth_answer */
  char   AuthCode[MAX_AUTHCODE];  /**< [out] Код авторизации. 7 байт.              */
  char   CardID[CARD_ID_LEN];      /**< [out] Идентификатор карты. 25 байт. Для международных карт все символы, кроме первых 6 и последних 4, будут заменены символами ‘*’.*/
  int    ErrorCode;                /**< [out] Код ошибки.                                         */
  char   TransDate[TRANSDATE_LEN]; /**< [out] Дата и время операции                               */
  int    TransNumber;              /**< [out] Номер операции за опер. день, см. номер на чеке     */
  int    SberOwnCard;              /**< [out] Флаг принадлежности карты Сбербанку                 */
  char   Hash[CARD_HASH_LEN];      /**< [in, out] хеш SHA1 от номера карты, в формате ASCII с нулевым байтом в конце. 40 байт.*/
  char   Track3[CARD_TRACK3_LEN];  /**< [out] третья дорожка карты*/
  DWORD  RequestID;                /**< [in,out] Уникальный номер операции. Только PCI DSS решения.*/
  DWORD  Department;              /**< [in] Порядковый номер отдела от 0 до 14-ти, включительно.
                                            При установке номера отдела в 0xFFFFFFFF, номер отдела
                                            будет запрошен через интерфейс терминала после вставки карты.
                                            Если номер отдела будет указан вне настроенного диапазона,
                                            то терминал вернет код ошибки 4191. */
  char   RRN[MAX_REFNUM];          /**< [in,out] Номер ссылки операции, присвоенный хостом. Используется
                                                для операций возврат, множественной авторизации и завершения расчета.
                                                Содержит уникальный 12-значный ссылочный номер.
                                                При предавторизации это поле является выходным
                                                (его заполняет библиотека pilot_nt.dll), а при
                                                завершении расчета – входным (значение должно
                                                быть заполнено вызывающей программой; оно должно
                                                совпадать со значением, возвращенным при предавторизации).*/
  DWORD  CurrencyCode;             /**< [in] Международный код валюты (810, 643, 840, 978 и т.д.) */
  char   CardEntryMode;            /**< [out] Способ чтения карты ('D'-магн.полоса, 'M'-ручной ввод, 'C'-чип, 'E'-бесконтакт EMV, 'R'-бесконтакт magstripe, 'F'-fallback)*/
  char   CardName[MAX_CARD_NAME_LEN]; /**< [out] Название типа карты */
  char   AID[MAX_AID_ASCII_LEN];   /**< [out] Application ID чиповой карты (уже в виде ASCIIZ-строки)*/
  char   FullErrorText[MAX_FULL_ERROR_TEXT]; /**< [out] Полный текст сообщения об ошибке*/
  DWORD  GoodsPrice;                /**< [in] Цена за единицу товара, коп (34.99->3499)*/
  DWORD  GoodsVolume;               /**< [in] Количество товара, в тыс. долях (30.234->30234)*/
  char   GoodsCode[MAX_GOODS_CODE+1]; /**< [in] Код товара во внешней системе.*/
  char   GoodsName[MAX_GOODS_NAME]; /**< [in] Наименование товара во внешней системе. Внимание! В структуре auth_answer14 название товара на один символ короче чем в gate.dll TGoodsData. Зафиксируем эту ошибку как стандарт*/
};
 
 
/** @brief Выполнение операций по картам
 *  @param[in] track2 данные дорожки карты с магнитной полосой. Если NULL, то будет предложено считать карту.
 *  @param[in,out] auth_answer см. ::auth_answer14
 *  @param[in,out] payinfo Информация для платежной системы
 *  @return int Код ошибки.
 */
PILOT_NT_API int  card_authorize14(
  char *track2,
  struct auth_answer14 *auth_answer,
  struct payment_info_item *payinfo
);

Пробуем подбирать поля… Выясняем, что от счастья нас отделяло всего одно — Фамилия и Имя носителя карты. Без них слип не считается законным:
Реквизиты: идентификатор банкомата, электронного терминала или другого технического средства, предназначенного для совершения операций с использованием платежных карт; вид операции; дата совершения операции; сумма операции; валюта операции; сумма комиссионного вознаграждения код авторизации; реквизиты платежной карты.
Жаль, но сформировать законный слип с теми данными, что у нас есть не получится.

Покопаемся в документации еще раз.

Находим пример, который Сбербанк поставляет в каталоге «examples»

std::cout << "Authorization completion finished with code '" << result << "'" << std::endl;
 
std::ofstream file(CHEQUE_FILENAME);
file << argument.auth_answ.Check;
file.close();
 
if (argument.auth_answ.Check) {
  std::cout << "Cheque saved to file " << CHEQUE_FILENAME << std::endl;
  //GlobaFree(argument.auth_answ.Check);
}

Просто выводится текст, находящийся по указателю. Но ведь мы уже убедились, что так оно не работает… На всякий случай скомпилируем их пример и запустим. Вылет на строчке `file << argument.auth_answ.Check;`, что ж, Сбербанк, держите камень #11 за неработающие примеры.

7:00. Уже можно писать разработчикам другой обертки, которая несколько лет назад была написана на Delphi. Получаем ответ, что у них все работает. Ищем основу их обертки и находим на github:

TAuthAnswer = packed record
  TType: integer;
  Amount: UINT; // IN Сумма операции в копейках
  Rcode: array [0 .. 2] of AnsiChar;
  AMessage: array [0 .. 15] of AnsiChar;
  CType: integer;
  Check: PAnsiChar;
end;
 
 
    Result := Func(nil, @FAuthAnswer);
    FLastError := Result;
    FCheque := PAnsiChar(FAuthAnswer.Check);

Простое преобразование типа в указатель без каких-либо вызовов функций.

Начинаем подозревать злых духов.

ГЛАВА 17. Гроза разразилась


Люди начинают возвращаться в офис, сочувственно кивая головой. PO выглядит не очень веселым узнав последние новости.

Тут вспоминается одна деталь. Когда мы выводили поля структуры #14 чтобы увидеть их значения то один байт каждой строки был отрезан. С одной стороны это, с другой
Внимание! В структуре auth_answer14 название товара на один символ короче чем в gate.dll TGoodsData. Зафиксируем эту ошибку как стандарт
Может это все же связано с…

Страшная догадка осеняет мозг словно молния. Объявим структуру как

typedef struct __attribute__((packed)) {
   int TType;             /**< [in] тип транзакции. см ::OpetationTypes */
   unsigned long Amount;  /**< [in] сумма в копейках                    */
   char RCode[3];         /**< [out] код результата авторизации         */
   char AMessage[16];     /**< [out] текст результата авторизации       */
   int  CType;            /**< [in,out] тип карты                       */
   char* Check;           /**< [out] образ чека, должен освобождаться GlobalFree в вызывающей программе */
};

И…

Ничего не меняется.

Все так же Size = 0, Все так же Lock = NULL.

Боль.

Тлен.

Невольно ищешь глазами удобную балку на потолке, такую, чтобы выдержала вес. После стольких нон-стоп часов кодинга и изучения документации перед глазами плывут стройные ряды байт. А что если вывести байты, которые вообще возвращаются?

    u32 i;
    for (i = 0; i < sizeof(answ); i++) {
        printf("%02x ", *((u8 *)&answ + i));
    }
    printf("\n");
 
 
C:\banks\sber\sb_pilot>sb_pilot.exe 1 1000
01 00 00 00 e8 03 00 00 30 00 00 ce e4 ee e1 f0 e5 ed ee 00 00 00 00 00 00 00 00 02 00 00 00 f8 6c 7a 00 00

`30 00 00 ce` — а это значит, что Сбербанк все же использует Packed структуры. Вот только в хэдерах об этом нет ни слова. Поэтому не работают примеры, поэтому не получается получить указатель на текст в конце — ведь он битый из-за сдвига на 1 байт. Огромный и колючий камень в сторону Сбербанка!

И тут в глаза бросился один мааааленький нюанс. 4 + 4 + 3 + 16 + 4 + 4 = 35. А тут 36 байт, Обеликс.

Раз тут 36 байт, значит компилятор все еще выравнивает структуру. Значит между RCode и AMessage все еще вставлен дополнительный байт. Но почему? Ведь мы указали `__packed__`!

ГЛАВА 18. Обратный путь


Причины того, что выравнивание все еще включено появились в 2012м году: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991. Починен баг только в GCC 8 (камень за 6 лет забагованности!), обновиться на который пока нет возможности. К счастью существует workaround:

-mno-ms-bitfields

Не будем сейчас разбирать механизм работы этого флага, просто передадим его компилятору:



Слип! Родненький! Я по тебе скучал, даже не буду ругаться из-за кракозябр, камень за это я уже кидал.

И скормим, наконец, Майкрософт камень, за то, что GlobalSize/Lock выдают нули на невалидные указатели.

ГЛАВА 19. Последняя глава


Чтобы максимально снизить количество ifdef'ов для прослойки для sb_pilot мы написали отдельное приложение, которое полностью имитирует linux-версию sb_pilot. Таким образом оставив код прослойки #1 прежним, оставив лишь одно условие:

#if defined(BXI_OS_GLX)
#define GFJ_PILOT_EXECUTABLE "./sb_pilot"
#elif defined(BXI_OS_WIN)
#define GFJ_PILOT_EXECUTABLE "./sb_pilot.exe"
#endif



Итоги сражения:

  • Сбербанк: 12 камней
  • Майкрософт: 7 камней
  • GCC: 1 камень

Ачивка-воспоминание на нашу командную доску:

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 106

    +19

    Статья вызывает желание перейти на наличные...

      +12
      Каждый программист, который хотя бы немного, хоть по касательной имел дело с клиент-банками, знает, что это на самом деле нормальное положение дел в банковском софте.

      Немножко удивляет разве что то, что оно и в 2006 году всё было практически точно так же, как и сейчас. Но только немножко.
        0
        Эникейщик тоже.
        Не помню уже что за софт, но что-то бухгалтерско-банковское.
        Поддержка даёт инсталятор с инструкцией и говорит устанавливать СТРОГО по инструкции.
        В инструкции строка: оставить папку для установки по умолчанию C:\Program Files\xxx
        В инсталяторе папка по умолчанию C:\xxx
        +2
        И, желательно, на чужие.
          0
          Скорее увидеть ответы по пунктам от Sberbank.
            0

            Подозреваю, что это не специфика сбера – см. выше ответ JustDont.
            Но разбор от сбербанк интересен, да.

          +8

          А зачем вам в 10 главе daemon tools? ISO файл прекрасно монтируется в virtualbox из коробки. Это же ISO.

            +6
            Вот пока вы не напомнили я даже и не помнил, что когда-то уже монтировал так диски. Что поделать, ночь накладывает ограничения на когнитивные функции.
              +5
              Аналогичный вопрос был и у меня, но когда 11 часов подряд пилишь костыли и workaround-ы, то простые пути исчезают из поля зрения.
                0

                "Нам нужно погрузиться глубже"

                +1
                А ещё у M$ был IMAPI v2 (ныне недоступен к загрузке, +1 камешек).
                Устанавливается на Windows 7 и позволяет монтировать диски. В 8-10 встроен.
                  0
                  Вообще сама концепция распространения софта для установки которого вам нужен другой софт звучит и выглядит странно.
                    +7

                    Ну как же древний анекдот? Драйвер для модема на CD, драйвер для CD в интернете

                      +1
                      Не так давно имели проблему установки Windows 7 на thinkpad — с флешки устанавливаться отказывается ссылаясь на то, что USB3.0 драйверов нет, CD-привода нет, в биосе перевести USB на 2.0 нельзя. Решили вытаскиванием жесткого и установкой на другой машине. Был бы распаянный SSD — не решили бы.
                        0
                        Но ведь есть куча инструкций по интеграции драйверов в дистрибутив. В том числе на оф. сайте — support.microsoft.com/en-us/help/2345135. Судя по статье вы даже умеете пользоваться Google. Так в чём проблема?
                          0
                          Берем любой лайв-сд(USB) с десяткой. Тот же стрелец. Грузимся с него. Кидаем драйвера на жесткий. Ставим как обычно.
                            0
                            Вот только без USB клавиатура с мышь/тачпадом не работает. И при всей доступности драйверов загрузить их не выйдет.
                              0
                              На всех нормальных ноутах клава идет напрямую в мультиконтроллер и работает всегда. Она никак не завязана на USB. Есть исключения вроде недобуков Prestigio — собранных на планшетной плате, но туда лучше лить заводской образ. А так дрова на хард скинули и tab/alt+tab/enter достаточно, чтобы поставить дрова.
                          +2
                          winzip.zip, winrar.rar
                            +2
                            pkunzip.zip
                              +9

                              ha.ha

                            +1
                            А это не анекдот. Где-то в 98-99 году был куплен какой-то CDD, драйвер от которого был любезно вложен на CD. Пришлось ехать до товарищей.
                            0
                            ну это нормально.
                            Чтобы что-то поставить, нужен менеджер пакетов.
                          +6
                          А ещё iso распаковывается архиватором вроде 7zip. И в данном случае это бы прокатило:)
                            +1
                            Еще его можно просто распаковать каким-нибудь 7-zip, обычно установка заработает и в распакованном виде.

                            P.S. Я буду дочитывать ветки до конца перед ответом. Возможно. Когда-нибудь.
                            +3
                            На проект нужно было месяц(20 дней примерно если я правильно понимаю) вы уложились за 4 дня, получили ли вы премию в размере 16 рабочих дней?
                              +11
                              Премия была, размер удовлетворил.
                                0
                                получили ли вы премию в размере 16 рабочих дней

                                16 рабочих дней это оплата, а премия должна быть за седые волосы.
                                  0

                                  Я себе представил, если бы к нам так пришёл PO. Сразу бы встретил вопросы:


                                  • кто берёт на себя все возможные риски;
                                  • как оплачивается сверхурочная работа.
                                    И только после достижения договорённости по этим пунктам с командой принималось бы решение. В противном случае PO отправляется туда, откуда пришёл. У людей семьи, личная жизнь, диета, фитнесс, планы. Никто просто так подрываться не станет.
                                    0
                                    Никто просто так подрываться не станет.

                                    Я сейчас скажу очень гадкую вещь. Но полно тех кто станет, и будут считать что это норм, мол в выходные им делать нечего, а тут сделали «доброе дело».
                                      +1
                                      Лично с такими сталкивался. Сон 3-5 часов, а все остальное время (включая не оплачиваемые выходные) кодить на контору (я бы еще понял на самого себя) — норма. Без особых доп. плюшек за это со стороны компании разумеется.

                                      Мне кажется пару лет в таком ритме и это будет тотальное выгорание.
                                        +1
                                        Люди в кайф работают — какое уж выгорание.
                                          0

                                          Тогда пусть работодатель просто кайфует от того, что я делаю и не ставит мне задачи, которые должны прибыль получать. Работа делается для прибыли, которой нужно делиться. Для кайфа пусть студенты делают )

                                            0
                                            Найдите того работодателя, который будет кайфовать от того, что вы делаете
                                              0

                                              Даже если найду. Сегодня я хочу делать одно, а завтра — другое. Тоже самое и с прибылью. Сегодня для прибыли надо делать одно, а завтра — другое. В сфере разработки ПО всё это меняется очень стремительно

                                        0
                                        Простите, если не по основной теме статьи, но хочу спросить.
                                        А что плохого что команда в данной истории решила ради «блага фирмы» поработать много и плотно (возможно, чтобы минимизировать возможные убытки)?
                                        Речь не идет о случаях типа как у меня — скотина дома не кормлена и попросить
                                        положить сена некого, а ты на работе сидишь. Если смотреть в отрыве от таких случаев «польза фирме с сильным вредом себе», то чем плохо?
                                        Мне кажется что у хорошего работодателя (правда это не про мой завод, у нас овертаймы конкретно IT-отделу не оплачиваются, скорее спросят «почему допустили?») это оценят и при сокращениях/премировании/повышении и в целом лояльным отношением?
                                        Просто очень хочу понять, лично я (видимо наивно) испытываю чувство удовлетворения от успешно завершенного овертайма, и от пользы которая в итоге пришла от затраченных усилий на какой-нибудь квест типа как в статье.
                                          +1
                                          А можно я отвечу. Вообще ничего плохого, но есть одно но(да в хорошой фирме так никогда не будет):
                                          1. Команда выполнила работу быстрей изначальных планов, значит в следующий раз это может стать нормой.
                                          2. Когда в следующий раз «вдруг» придётся сократить сроки, то вы не скажете просто так что вы не можете, из за пункт 1, это уже норма.
                                          3. Как так получилось что вдруг сократили срок на разработку, я достаточно давно работаю в этой отрасли и не помню что заказчик брал и урезал сроки. А значит как то это уже сделано по договоренности, у ИТ отдела при этом никто не спросил, им сказали надо.
                                          4. «А что плохого что команда в данной истории решила ради «блага фирмы» поработать много и плотно (возможно, чтобы минимизировать возможные убытки)?» Откуда убытки если всё делалось в срок?
                                          5. У всех есть раcпорядок: рабочие дни, выходные, праздники итд, но по воле Project Manager(которому возможно была обещана премия больше чем получили потом все программисты вместе взятые) все должны наплевать на свои планы и пойти работать на чужие балага фирмы. Можно сказать конечно что никто всех не заставлял, но по факту если в коллективе 25% согласилось на это, то скорей всего подтянутся остальные чтобы криво на них никто не смотрел(в особенности начальство).
                                          6. Вышел ли на сверхурочную работу начальник фирмы и PM 90% что нет, они свою премию получат за то что они заставили за печеньки работать других.
                                          7. «это оценят и при сокращениях/премировании/повышении и в целом лояльным отношением?» в пока ещё хорошей фирме да оценят, только когда вышел весь отдел, это не кто то отличился и поработал на благо, а все сделали одно дело. Всех не повысишь, при сокращениях тоже выделить некого, лояльней ко всем относится тоже не понятно как все ведь одинаковый вклад сделали. Премию да можно и дать, например в размере двойного оклада на время переработки(ну или чуть больше чтобы особо вредные не вякали).
                                          Просто очень хочу понять, лично я (видимо наивно) испытываю чувство удовлетворения от успешно завершенного овертайма, и от пользы которая в итоге пришла от затраченных усилий на какой-нибудь квест типа как в статье.

                                          Это нормально и прекрасно, но часто это перерастает в то что ты уже обязан это делать, на постоянной основе а премия становится всё меньше.

                                          Чтобы кратко подвести итог:
                                          1. Соглашаясь на переработку ты подставляешь своих коллег, которым она не надо, но они почти вынуждены её делать.
                                          2. Ты обесцениваешь свой труд и труд своих коллег(на проект было 20 дней вы сделали за 4 получили как за 8 вроде все в плюсе, но по факту получить должны были как за 20).
                                          3. Ты загоняешь себя и своих коллег в кабалу постоянных переработок, потому что вы уже показали что можете, а этим грех не воспользоваться.
                                          PS. Всё что я описал сильно передернуто, у вас в фирме такое ещё не случилось.
                                            0

                                            Полностью согласен

                                              0

                                              У нас кзот прямо об этом говорит — факап не должен быть нормой

                                            0

                                            Ну тогда это волонтёрство какое-то. Имхо, мухи от котлет надо отделять.

                                        0
                                        POSы — это всегда весело в части работы с ним.
                                        Помню как пытался срастить этот верифон с win системой на тему того, чтобы он корректно работал и loadparm грузился. Стабильная версия ПО отказывалась работать у меня на компе, завелся только снапшот, а вот у коллеги все работало ок. Так и не смогли выяснить причину :)
                                          0
                                          Возможно как раз таки дело было в прямом доступе к порту, который запрещен на NT, но который использовался в одной из версий loadparm. Что касается работы у коллеги, возможно причина в том, что у него в системе стоял какой-нибудь драйвер, установленный вместе с какой-нибудь VisualStudio или какой-нибудь пачкой банковского софта, который перехватывал доступ.
                                          +1
                                          char Hash[CARD_HASH_LEN]; /**< [in, out] хеш SHA1 от номера карты ...

                                          Это настоящий sha1 от номера карты?
                                          Если это так, то можно восстановить номер карты, который нельзя раскрывать организациям, у которых нет PCI DSS.

                                            0
                                            В теории — настоящий. Не проверяли. Возможно соленый, но при слабой соли можно и подобрать, ели известен номер карты и этот хэш. Даже если тут можно получить номер карты, (а радужка займет 999 999 999 999 999 номеров x 20 байт на SHA1 = 177 Петабайт и при условии что хэш нигде не повторится) без CVV/C вы не сделаете покупку.
                                              0

                                              Цифр меньше, всего 11:


                                              • Последние 4 цифры номера карты нам возвращают.
                                              • Первую цифру легко восстановить по AID

                                              Производительно одной Nvidia 1080 около 1 GH/s для sha-1 https://gist.github.com/epixoip/a83d38f412b4737e99bbef804a270c40 Примерно 27 дней на одну карту.


                                              Есть точки, которые не требуют CVV.

                                                0
                                                не проще взять BIN и подбирать остальные 9 цифр, из которых реальных номеров будет десятки тысяч?
                                                  +1

                                                  Насколько я понял, терминалы Сбербанка не возвращают BIN, только последние 4 цифры.

                                                    0
                                                    Зачем вам именно карта из терминала? Берите любой BIN.
                                                      +1

                                                      Речь идёт о нарушении безопасности.
                                                      Если, конечно, там обычный sha-1 без использования MAC-функции с секретным ключом (например, CBC-MAC).

                                                        –1
                                                        нарушение безопасности что бы что? В чем суть атаки?
                                                        27 дней подбирать номер одной карты, на которой денег только на билет в кино?
                                                      0
                                                      количество BIN всетаки не большое. Тоесть можно вначле перебирать популярные BIN номера.
                                                      И не 11 а можно считать что 2 по 10.
                                                      Так-как первая цифры с 90% это либо 4 либо 5.
                                                        0
                                                        Пилот может и в таком виде сохранять маску 111111******1111, зависит от карты
                                                      0
                                                      Поделите 24 дня на 10, последняя цифра в номере карты — контрольная сумма других цифр.
                                                      0
                                                      без CVV/C вы не сделаете покупку.

                                                      Смотря где. На том же Амазоне не просят его. Но строго говоря, там и делать бессмысленно: насколько знаю, Амазон отменяет по любому чиху

                                                      0
                                                      да, настоящий, он используется для бонусной программы. Учитывая, что терминал возвращает 10 цифр номера из 16, брутфорс по хэшу выполняется за доли секунды
                                                        0
                                                        Для бонусной программы «спасибо от СБ» используется урезанный хеш. Что конкретно там урезано — не помню (в понедельник могу доки глянуть) но по тому хешу номер карты вы не восстановите.
                                                          0
                                                          Забавно, что в доках про хэши ни слова:
                                                          DWORD spasibo(dynamic_function function)
                                                          {
                                                            auth_answer9 argument;
                                                          
                                                            memset(&argument,0,sizeof(argument));
                                                            argument.ans.TType = OP_PURCHASE;
                                                            argument.ans.Amount = 100000;
                                                            std::cout << "Let's try to pay!" << std::endl;
                                                            DWORD result = function(NULL,&argument);
                                                            // after call 'argument' contains card hash which required for second call
                                                            if (result == 4353)
                                                            {
                                                              std::cout << "Sberbank Spasibo detected!" << std::endl;
                                                              // KKM code can change amount (payment by spasibo)
                                                              argument.ans.Amount = 90000;
                                                              result = function(NULL,&argument);
                                                            }
                                                            
                                                            std::cout << "Operation completed with code '" << result << "'!" << std::endl;
                                                            return result;
                                                          }
                                                        +1

                                                        Хеш тут только для видимости, он по сути эквивалентен номеру карты, так как из 16 цифр первые 6 — это BIN, их список для Сбербанка при желании можно найти (хотя список официально не публикуется), и еще одна цифра — контрольное число. То есть остается 9 цифр энтропии, которые перебираются на раз.


                                                        Напишу на всякий случай: хеш от данных с малым числом вариаций: номера телефона, паспорта, ФИО, почтового адреса легко обращается и эквивалентен самим данным.


                                                        Поэтому это хеширование, конечно, число символично.

                                                        +3
                                                        Хех. Читается на одном дыхании. Спасибо )
                                                          +2
                                                          В первой половине 2000х несколько лет работал в ритейле, вспоминал как страшный сон.
                                                          такой концентрации говнокода, говонософта, костылей и велосипедов не видел нигде.
                                                          После того как ушел, вспоминал как страшный сон и радовался

                                                          В этом году скинули новый проект связанный с ритейлом, пришлось окунуться
                                                          Ничего не изменилось. Встретил даже windows2000 в продакшене в 2к19 ))))
                                                            +4
                                                            Я не так давно в налоговой видел DOS не в CMD, так что не удивлен, что где-то есть Windows2000.
                                                            –2
                                                            Использовать loadparm.exe, утилиту для тестирования и техобслуживания, вместо sb_pilot.exe — это сильно. Хотя использовать надо отличную библиотеку sbrf.dll (почему все хватаются за неудобную pilot_nt.dll?). Удобство стоит написания разного кода под Windows и Linux. Специфичного кода там будет немного.
                                                            Читайте доки ДО кодинга, а не после, и будет вам счастье.
                                                              +4
                                                              1) Я рекомендую вам перечитать статью, где вы увидите, что sb_pilot.exe для windows в последних сборках сбербанка попросту отсутствует.
                                                              2)
                                                              Хотя использовать надо отличную библиотеку sbrf.dll (почему все хватаются за неудобную pilot_nt.dll?).

                                                              Ну, наверное потому, что sbrf.dll согласно документации работает черезх OLE объекты (в 2019м году) а еще, наверное, потому, что OLE объектов под linux нет?
                                                              Удобство стоит написания разного кода под Windows и Linux

                                                              Не вижу ничего неудобного в чтении данных из структуры, а не из Sberbank.OLEObjects(«payment»).OLEObjects(«amount»).Object.Value. Два разных приложения под разные платформы — это в два раза больше времени на разработку, в два раза больше проблем при поддержке кода и в два раза больше FAQ записей для NOC при поддержке продукта.

                                                              Ну и еще, наверное, потому, что в официальной поставке хэдеры для работы с библиотеками есть только под pilot_nt. Под sbrf предлагается только пара примеров работы через visual basic для отдельных функций.
                                                                0
                                                                Да нет никакого sb_pilot.exe для Винды! И не было никогда, sb_pilot — только для Linux и DOS. А за отсутствием нужного ПО, использовать утилиту для тестирования — это как вместо туалетной бумаги взять наждачную.

                                                                Да, в Linux нет OLE. Но откуда «в два раза больше времени на разработку»? Семь-десять строчек кода — это безумно много? Сделать обёртку и запилить условную компиляцию — это просто. И это более правильно, чем надевать штаны через голову использовать ПО для DOS в Винде.
                                                                Между прочим, фунционал sb_pilot.exe не будет расширяться, ибо он уже не лезет в нижнюю память 640K, «которой хватит всем».

                                                                Насчёт документации. Она есть, и там описана работа со всеми интерфейсами под Винду. Для каждой либы — свой документ, с примерами. Просто запросите новый релиз UPOS, и не «огрызок» папку PC_STUB, а полный архив.
                                                                  0
                                                                  Я уже второй раз вас прошу — перечитайте еще раз статью; loadparm.exe НЕ используется.
                                                                  Семь-десять строчек кода — это безумно много?

                                                                  gefjon$ cloc .
                                                                       312 text files.
                                                                       291 unique files.                                          
                                                                      2083 files ignored.
                                                                  
                                                                  http://cloc.sourceforge.net v 1.60  T=5.97 s (45.4 files/s, 5132.2 lines/s)
                                                                  -------------------------------------------------------------------------------
                                                                  Language                     files          blank        comment           code
                                                                  -------------------------------------------------------------------------------
                                                                  C                              112           3130            691          13099
                                                                  C/C++ Header                   132           1316           1758           9306
                                                                  Bourne Shell                     8            114             75            684
                                                                  ASP.Net                         17            105              0            291
                                                                  Perl                             2             17              4             74
                                                                  -------------------------------------------------------------------------------
                                                                  SUM:                           271           4682           2528          23454
                                                                  -------------------------------------------------------------------------------
                                                                  


                                                              +2
                                                              На главе 12 наконец-то обратил внимание на заголовки.
                                                              Отличная история.
                                                                0
                                                                Ну и самый серьезный камень: дорогие разработчики на С++. Если вы пишете extern «C» — это означает, что код внутри блока должен компилироваться С-компилятором. Если вы НЕ сделали `typedef` структуры, то при каждом ее упоминании в качестве указания типа необходимо писать ключевое слово `struct`.
                                                                Это разве так? Поигрался с godbolt.org, варианты
                                                                код
                                                                struct tst
                                                                {
                                                                
                                                                };
                                                                extern "C"
                                                                {
                                                                    void f(tst *a){ }
                                                                }
                                                                int main()
                                                                {
                                                                    tst A;
                                                                    f(&A);
                                                                    return 0;
                                                                }
                                                                
                                                                и
                                                                
                                                                extern "C"
                                                                {
                                                                    struct tst
                                                                    {
                                                                
                                                                    };
                                                                    void f(tst *a){ }
                                                                }
                                                                int main()
                                                                {
                                                                    tst A;
                                                                    f(&A);
                                                                    return 0;
                                                                }
                                                                


                                                                прокатывают, во втором случае clang выдаёт warning: empty struct has size 0 in C, size 1 in C++ [-Wextern-c-compat]
                                                                extern «C» не даёт C++ компилятору уродовать имя, а компилируется всё по прежнему в C++ режиме.
                                                                  0
                                                                  $ gcc file.c -o file.o
                                                                  file.c:15:3: error: unknown type name ‘auth_answer’
                                                                     auth_answer auth_answ;
                                                                     ^
                                                                                    ^
                                                                  $ cat file.c
                                                                  #ifdef __cplusplus
                                                                  extern "C"{
                                                                  #endif
                                                                  
                                                                  struct auth_answer{
                                                                     int TType;             
                                                                     unsigned long Amount;  
                                                                     char RCode[3];      
                                                                     char AMessage[16];  
                                                                     int  CType;         
                                                                     char* Check;        
                                                                  };
                                                                  
                                                                  struct auth_answer7{
                                                                    auth_answer auth_answ;
                                                                    char   AuthCode[5];
                                                                    char   CardID [5];  
                                                                    int    SberOwnCard;           
                                                                  };
                                                                  
                                                                  #ifdef __cplusplus
                                                                  }
                                                                  #endif
                                                                  
                                                                    +1
                                                                    Ага, теперь понятно. Выглядит как будто пытались, но протестить на чистом C не смогли.
                                                                  +4
                                                                  А когда-то сбер вытащил с рынка всех программистов, вообще было не найти никого и они написали вот это в итоге?
                                                                    +1
                                                                    Они это поддерживали. Уверен, корни этого кода уходят в нулевые, если не раньше. Чего одна версия для 16 бит стоит.
                                                                      0
                                                                      Так переписать не проще? Все мы так делали и проект становился лучше(ну в редких случаях только не помогало).
                                                                        +1
                                                                        А куда девать миллионы касс и терминалов, которые разбросаны по всей стране и работают на MS-DOS/windows 98/Windows XP/Windows 7/Windows 8/Windows 10/Linux?
                                                                    0
                                                                    Подскажите пожалуйста, не сталкивались ли вы с ошибкой «2002; Описание: Превышено время ожидания;»? Какое её правильное описание? Может лечится как-то?

                                                                    Так как по долгу службы я работаю в сфере технического сопровождения кассового П.О. под управлением OS Ubuntu GNU/Linux в одной из розничных сетей… Так вот Данная ошибка встречается мной очень часто и раздражает меня уже не первый год. После выполнения операции оплаты sb_pilot возвращает в p файл слип в котором написано «ОДОБРЕННО» а в e файл эту ошибку с указанным выше описанием. При этом деньги с клиента списываются и судя по сверке итогов операция в банке считается успешной, но само кассовое П.О. парсит этот статус и считает что ошибка есть ошибка и блокирует продажу…
                                                                    Конечно Я мог бы поставить костыль и игнорировать это дело, но это же всё же банковские документы, потому хотелось бы лишний раз их не трогать…
                                                                      0
                                                                      На тестовых терминалах — не сталкивались.
                                                                      Согласно документации:
                                                                      2002
                                                                      Превышено время ожидания
                                                                      Повторить операцию. На ввод ПИНа дается 1 минута.

                                                                      Точно ли деньги списываются? Судя по описанию похоже на ситуацию когда кто-то прошляпил время ввода пин-кода.

                                                                      Конечно Я мог бы поставить костыль и игнорировать это дело, но это же всё же банковские документы, потому хотелось бы лишний раз их не трогать…

                                                                      *шепотом*: у Альфа-Банка (точнее UCS-терминалов которые они используют) есть ошибка I6, при который НЕ печатается слип, кассиру выводится сообщение об ошибке, однако оплата проходит и клиенту нужно сказать что все хорошо, но бумажки он не получит.
                                                                        0
                                                                        Точно ли деньги списываются?

                                                                        Точно, слип выходит, проверяли и не раз, у и банка запрашивали корректность всех операций они говорят всё ОК. Я нахожусь в патовой ситуации, так как СберБанк утверждает что всё нормально и они никогда с данными ошибками не сталкивались, а разработчики кассового П.О, говорят что Мы не можем игнорировать ошибки и обязаны по ним отработать…
                                                                          0
                                                                          Тогда скорее всего это ошибка не связи с банком и подтверждения операции, а ошибка связи с самим пинпадом после совершения платежа.

                                                                          Могу предложить следующий путь решения — запустить тестовую транзакцию, и дождаться автоотмены по таймауту (не вводить ПИН).
                                                                          *Если будет ошибка 2008 — то в реальных условиях ориентироваться на наличие слип-чека.
                                                                          *Если будет НЕ ошибка 2008 — то игнорировать 2008 в реальных условиях.

                                                                          Это все если пытаться решить самостоятельно. Если хочется официального решения — то надо писать в Сбербанк, давать им запись с видеонаблюдения того момента когда оно выдало 2008 (чтобы они могли задетектить например должгое ожидание), давать им sbkernel.log за то число с указанием времени проблемы и описание. Раз у вас есть их терминал, то у вас есть полное право затребовать поддержку.
                                                                        0
                                                                        да, есть такой баг, если не ошибаюсь, проявляется при бесконтактной оплате
                                                                          +1
                                                                          Ни в коем случае не делайте костыль на коды возврата UPOS. Это плохо закончится.
                                                                          Если эта ошибка лезет постоянно, и на разных ККМ, попробуйте следующее.
                                                                          1) не отменяется ли операция, когда Вы делаете следующую операцию?
                                                                          2) обновите ПО UPOS на одной группе ККМ. Перед этим позвоните в техподдержку и закажите удалённую загрузку ПО на эту группу терминалов;
                                                                          3) выдайте права «777» на папку с UPOS и файлы в ней;
                                                                          4) проверьте настройки терминала (TlvEdit.exe --> Ctrl+F6 --> дождаться выгрузки настроек из терминала --> Дополнительные опции):
                                                                          Порядок выполнения операции — По команде от ККМ, с печатью чека на кассе
                                                                          Подтверждение результата кассиром — Никогда
                                                                          Возвращать промежуточный код ответа — Не возвращать
                                                                          Ожидать изъятия карты после операции — Ожидать.

                                                                          Если Вы не сможете разобраться с настройками сами, выгрузите настройки в файл *.TLV и вышлите его для анализа в техподдержку Сбера, снабдив логом и комментарием про ошибку 2002.

                                                                          Кстати, можете выслать настройки мне. Напишите мне письмо, я Вам вышлю свой служебный адрес, куда можно выслать настройки и логи.
                                                                          0

                                                                          Интересно, а Сбербанк не даёт описание протокола, как общаться через USB или rs-232 с терминалом?
                                                                          Тогда было бы намного проще интегрироваться.

                                                                            +1
                                                                            Не дает. Потому что это требует другой уровень сертификации в PA DSS, поскольку идет работа с незашифрованными данными пользователей.
                                                                              0

                                                                              Терминал не должен по USB или по rs-232 выдавать чувствительные данные (PAN, PIN block).
                                                                              Поэтому здесь нарушения стандарта PCI нет.

                                                                            0
                                                                            В последний раз пришлось испытать нечто подобное при попытке завести .NET Core на linux (и windows кстати тоже), причем на уровне запуска хеллоуворд приложения. С интервалом в год (стало еще хуже). Ну не любит MS linux, и самих себя тоже не особо жалуют.
                                                                              0
                                                                              Настолько не любит, что свежая версия десятки будет включать в себя ядро Линукс :) могу только предположить, что MS и сами далеко не всегда в восторге от своей продукции, но у тёмной стороны очень уж много печенек…
                                                                                +1
                                                                                Мне, если честно, сложно представить как вы так пытались его завести что он у вас не завелся с полпинка. Сижу на .NET Core со времен 1.0 release candidate и не помню чтобы у меня были проблемы с установкой SDK и Runtime'ов и компиляцией приложений как на винде так и на линуксе.
                                                                                  0
                                                                                  Хотел писать статью тогда (с камнями, как тут). Там на каждом шагу были какие-то несоместимости, основная цель была подружить это все с Азуром из VS code. Даже пол-года назад (когда уже была релизная версия «стабильная») впечатление было, как будто все это даже не бета еще, не знаю как вам удалось без запинки все завести с release candidate…
                                                                                    0
                                                                                    Ну может конкретно с Азуром там какие-то косяки по деплою были, я все что с ним связанно не ковырял особо так как не было необходимости. Но с компиляцией и запуском локально точно проблем не было в различных комбинациях деплоя (self-contained/framework-dependent) и платформ (win/linux).
                                                                                0
                                                                                3 года назад внедрили терминалы сбера. Через gate.dll все работает, особых проблем не возникло. Правда не завелось из-под сервисной сессии, но это оказалось для нас некритично.
                                                                                  0
                                                                                  Судя по их документации работа под gate.dll отличается от pilot_nt только названием функций, остально практически дословно:
                                                                                  in.in_struct = &InDetail;
                                                                                  out.out_struct = &OutDetail;
                                                                                  out.Reserved = &outExtra;
                                                                                  outExtra.size = sizeof(outExtra);
                                                                                  inExtra.size = sizeof(inExtra);
                                                                                  in.Reserved = &inExtra;

                                                                                  Для linux она опять же не поставляется, для нас это было критично.
                                                                                  +3
                                                                                  Вот список багов с которыми сталкивались:
                                                                                  1) Иногда терминал возвращает неверный виртуальный номер отдела и соотвественно мерчант. Возвращает тот, который в списке отделов стоит первым. Но деньги уходят правильно, в сверке итогов все корректно
                                                                                  2) Как писали выше, при бесконтактной оплате при сумме больше 1000, если не ввести пинкод за 1 минуту, деньги все равно списываются. Возвращается код ошибки 2002.
                                                                                  3) При бесконтактной оплате cardholder name не отражается в слип-чеке.
                                                                                  4) У них обновление конфигурации терминалов при сверке итогов, которое может закачать неверную конфигурацию терминала. После такого кривого обновления в лучшем случае терминал встанет, в худшем — деньги пойдут не туда, куда нужно
                                                                                    +3
                                                                                    при бесконтактной оплате при сумме больше 1000, если не ввести пинкод за 1 минуту, деньги все равно списываются

                                                                                    Мне кажется, или это смахивает на серьёзную уязвимость? Помнится аргументом за бесконтактную оплату было то, что преступник в каком-нибудь людном месте особо много не сможет собрать с одного человека. А тут получается снимать можно любые суммы, чтобы машинлёнинг подозрительные платежи не задетектил. А если при такой оплате через таймаут ещё и СМС не приходит о списании, то вообще ппц какой-то.

                                                                                      0
                                                                                      Предполагаю, при возврате ошибки средства не уходят с эмитента, просто блокируются им. У меня со сбер терминалом неделю назад произошла эпичная ситуация. При оплате телефоном средства списались, а слип не распечатался и на кассе никакой информации, я думал такого вообще не должно быть. Вряд ли это проблема клиентского оборудования, т.к. слип выдает сам терминал.
                                                                                        0
                                                                                        «машинлёнинг», конечно, такое за подозрительное действо не посчитает, но просто обязан посчитать в рамках «N покупок без пин-кода».
                                                                                          0

                                                                                          Дак в том и фишка, что если можно снять любую сумму, то не будет «N покупок без пин-кода», будет одна большая

                                                                                      0

                                                                                      Блин, я думал у нас все плохо…
                                                                                      P.S. тоже ритейл, но более мелкий и специализированный.

                                                                                        0
                                                                                        Интересная статья, но я не могу понять особенность чисел 3, 7, 9, 10, 13, 15 и 16. Почему они были изгнаны?
                                                                                          +11
                                                                                          Все названия глав взяты (как и частично название статьи) из произведения Д.Р.Р.Толкина «Хоббит или туда и обратно». Те главы, названия которых не подходили по смыслу повествования были исключены.
                                                                                            +3
                                                                                            Неожиданно
                                                                                          +1
                                                                                          сообщил, что сроки реализации проекта должны быть быстро и решительно сокращены на месяц. Точнее говоря проект должен быть готов через 4 дня.

                                                                                          Похоже, вы друг друга стоите. :)

                                                                                            +1
                                                                                            [стеб] М-м-м, что? Visual Studio 2017 не поддерживается на Windows 7 32bit? Ха-ха-ха. Может там у вас ARM? Или 1ГБ оперативной памяти? А может просто «лагает» на 4ГБ… Ой, бедные. Клик, кому интересно, что за машина у меня. [/стеб]
                                                                                              0
                                                                                              Я скорее поверю, что MS блокирует установку в виртуалке.
                                                                                                0
                                                                                                Я уже 3-й год программирую на таком конфиге. И я не помню случаев, где кроме битности системы что-то мешало. Лаги не в счет.
                                                                                              0
                                                                                              Вот это техно-детектив. Я, как c# разраб, половины не понял, но было интересно!
                                                                                              Спасибо!
                                                                                                0
                                                                                                команде было обещано что-то вкусное
                                                                                                Вы там за еду что-ли работаете?
                                                                                                А если серьёзно, то меня пугает эта тенденция что сроки спускаются сверху… обычно это заканчивается тем, что профессионалы уходят.
                                                                                                  0
                                                                                                  Вкусное можно еще и пить
                                                                                                  0
                                                                                                  Мы занимаемся автоматизацией кинотеатров — автоматическим и дистанционным управлением оборудования, автоматизацией кинопоказа, мониторингом, видеопанелями, а теперь еще и терминалами продажи билетов и бара.
                                                                                                  «Киноплан»?

                                                                                                  P.S. Зачем вы мне снова напомнили про эти ваши кинотеатры? Я лишь недавно — спустя полгода после увольнения из одного из них (кстати, из того, который у вас на одном из фото изображён) — начал забывать этот ужас.

                                                                                                  Only users with full accounts can post comments. Log in, please.