Мой подход к работе с Git

Второй день знакомлюсь с Git. Читаю книжку Pro Git, попутно загоняя буковки в консоль =)

Расскажу, как организовал процесс разработки на своём компьютере. Если что-то не правильно или есть лучшие способы, то смело пишите в комментах!

Более опытные коллеги подсказали, что ставить на локальный компьютер "Git сервер" не очень разумно, лучше обойтись одной папкой в которой будут размещаться голые (bare) репозитории и которая будет служить центральным хранилищем.

Итак. Создаём папку под голые репозитории, например C:\GitRepos (да да, я сижу на Windows):

$ mkdir /c/GitRepos

Создаём голый репозиторий myproject.git:

$ cd /c/GitRepos
$ mkdir myproject.git
$ cd myproject.git
$ git init --bare

Переходим в каталог, в котором располагаются исходники проекта myproject и создаём там новый локальный репозиторий:

$ git init

Связываем его с основным:

$ git remote add origin /c/GitRepos/myproject.git

Добавляем в локальный репозиторий файлы и делаем первый коммит:

$ git add .
$ git commit -a -m 'First commit'

Отправляем проект на "сервер" (в папку C:\GitRepos):

$ git push origin master

Теперь чтобы продолжить разработку myproject в другом месте, нужно сделать копию основного репозитория:

$ git clone /c/GitRepos/myproject.git

и после очередного коммита в локальный репозиторий, обновить основной:

$ git push

Получить свежую версию из основного репозитория, можно так:

$ git pull

Добавлено: 27 Июля 2018 22:52:42 Добавил: Андрей Ковальчук

Полезные параметры командной строки

Версия PHP

# php -v
PHP 5.2.12 (cli) (built: Mar 01 2010 12:00:00)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2009 Zend Technologies

Список установленных расширений
# php -m
[PHP Modules]
ctype
curl
date
iconv
...

Аналог phpinfo()
# php -i

Список прочитанных конфигов
# php --ini
Configuration File (php.ini) Path: /usr/local/etc
Loaded Configuration File:         /usr/local/etc/php.ini
Scan for additional .ini files in: /usr/local/etc/php
Additional .ini files parsed:      /usr/local/etc/php/extensions.ini

Добавлено: 22 Июля 2018 10:03:32 Добавил: Андрей Ковальчук

Выполнение нескольких команд в консоли Windows (cmd)

Для того чтобы выполнить несколько команд из одной командной строки, нужно объединить их с помощью символа условной обработки:

команда1 & команда2 — Используется для разделения нескольких команд в одной командной строке. В cmd.exe выполняется первая команда, затем вторая команда.

команда1 && команда2 — Запускает команду, стоящую за символом &&, только если команда, стоящая перед этим символом была выполнена успешно. В cmd.exe выполняется первая команда. Вторая команда выполняется, только если первая была выполнена успешно.

команда1 || команда2 — Запускает команду, стоящую за символом ||, только если команда, стоящая перед символом || не была выполнена. В cmd.exe выполняется первая команда. Вторая команда выполняется, только если первая не была выполнена (полученный код ошибки превышает ноль).

Пример:

attrib -H "file.txt" && ren "file.txt" "file.tmp"

С файла file.txt будет снят атрибут "Скрытый" и если команда attrib при этом не вернёт ошибку, файл будет переименован в file.tmp.

Добавлено: 18 Июля 2018 07:40:08 Добавил: Андрей Ковальчук

Диалог открытия файлов и юзабилити Windows

При всех удобствах Windows некоторые моменты меня очень сильно раздражают. Особенно поведение системы при вызове диалогов открытия файлов. Сперва немного предыстории. При работе с файлами через функцию GetOpenFileName или GetSaveFileName в структуре OPENFILENAME есть возможность указать путь, который должен открыться по умолчанию. Если это значение не задано, то система сама где-то запоминает папку, в которой последний раз был удачно открыт файл (то есть окно выбора файла было закрыто через кнопку "Ok"). Где именно хранится эта информация - я пока не выяснил, да и не особо надо. Второй вариант. Предположим, что некоторая программа самостоятельно запоминает путь к папке, в которой последний раз ею выполнялись какие-то действия с файлами. Это может быть, например, текстовый редактор, просмотрщик графики и т.п., не суть. Главное, что задумка очень хорошая и правильная. При следующем запуске или вызове диалога выбора файла в соответствующее поле OPENFILENAME будет подставлен сохраненный путь и пользователь продолжит работу с того места, где он в прошлый раз остановился. Что-то типа такого:

        ...
        invoke  GetModuleHandle,0
        mov     [ofn.hInstance],eax
        mov     [ofn.lStructSize], sizeof.OPENFILENAME
        mov     [ofn.hwndOwner],0
        mov     [ofn.nMaxFile],MAX_PATH
        mov     [ofn.lpstrFile],buff
        ; Открывать с последней сохраненной папки
        mov     [ofn.lpstrInitialDir],saved_dir
        mov     [ofn.Flags],OFN_EXPLORER+OFN_FILEMUSTEXIST
        invoke  GetOpenFileName,ofn
        ...

Неадекватное, на мой взгляд, поведение системы заключается в следующем. Вполне может возникнуть ситуация, что какая-то часть из сохраненного или запрошенного пути пропала. Например, я в просмотрщике рассортировал папку с фотографиями, в графическом редакторе подправил несколько файлов, а затем в файловом менеджере перенес всю папку с фотографиями в другое место на диске. В этом случае при попытке вернуться к просмотру в просмотрщике, повторно вызвать диалог открытия или сохранения файла в графическом редакторе, при любом раскладе в качестве дефолтного пути будет открыта какая-нибудь херня типа Библиотеки, Моих документов или вообще папки, куда установлена программа. Закономерности я тут тоже не уловил, видимо принятие решения остается за Windows и зависит от уровня осадков в Зимбабве. В итоге пользователю приходится снова топать весь путь из библиотеки до места работы.

После очередной серии чудесатых чудес я решил сделать для себя небольшую вспомогательную функцию. Она проверяет сохраненный путь и возвращает последнюю папку максимального уровня вложенности, которая существует на диске в текущий момент. Например, если ваша программа в последнем сеансе работы сохранила, а затем пытается открыть путь
D:\PICTURES\Путешествия\2011\Разобрать\Китай\NIKOND90\001

но при этом папки "\Китай" и, соответственно, вложенных в нее папок уже не существует, то должна открываться папка
D:\PICTURES\Путешествия\2011\Разобрать

и никак иначе! По-моему, это единственно правильное поведение системы. Почему разработчики Windows до сих пор открывают непонятно что вместо ПОСЛЕДНЕЙ ДОСТУПНОЙ папки из запрошенного пути - непонятно. Какая-то дефолтная папка может открываться только в одном единственном случае - когда ВЕСЬ запрошенный путь, включая букву диска, недоступен.
;------------------------------------------------------------
; Функция проверки доступности пути в файловой системе
; (C) ManHunter / PCL
; http://www.manhunter.ru
;------------------------------------------------------------
; Параметры:
; lpRaw - указатель на буфер размером MAX_PATH, в который
; записан проверяемый путь
; lpGood - указатель на буфер размером MAX_PATH, в который
; будет записан максимально доступный путь
;
; На выходе:
; EAX=0 - ни один из составляющих пути, включая носитель, не
; доступен
; EAX=1 - по крайней мере один из составляющих пути доступен,
; результат без финального слеша записан в буфер lpGood
;------------------------------------------------------------
proc    GetLastValidFolder lpRaw:DWORD, lpGood:DWORD
        locals
                result  dd ?
                old_dir rb MAX_PATH
                new_dir rb MAX_PATH
        endl
 
        pusha
 
        ; Сохранить текущую директорию
        lea     eax,[old_dir]
        invoke  GetCurrentDirectory,MAX_PATH,eax
 
        ; Скопировать поверяемый путь
        lea     esi,[new_dir]
        invoke  lstrcpy,esi,[lpRaw]
        mov     edi,esi
        invoke  lstrlen,esi
        or      eax,eax
        jz      .loc_bad
        dec     eax
        add     edi,eax
 
        ; Исправить слеши
.loc_fix_slash:
        cmp     byte [esi+eax],'/'
        jne     @f
        mov     byte [esi+eax],'\'
@@:
        dec     eax
        or      eax,eax
        jnz     .loc_fix_slash
 
.loc_chk:
        ; Попробовать установить текущую директорию
        invoke  SetCurrentDirectory,esi
        or      eax,eax
        jne     .loc_ok
.loc_scan:
        mov     byte [edi],0
        dec     edi
 
        ; Сканируем с конца до ближайшего слеша
        cmp     byte [edi],'\'
        je      .loc_chk
 
        ; Добрались до начала строки?
        cmp     edi,esi
        jne     .loc_scan
.loc_bad:
        ; Результат - ошибка
        mov     [result],0
        ; Обнулить строку
        mov     eax,[lpGood]
        mov     byte [eax],0
        jmp     .loc_ret
.loc_ok:
        ; Убрать финальный слеш
        cmp     byte [edi],'\'
        jne     @f
        mov     byte [edi],0
@@:
        ; Скопировать последний правильный путь
        invoke  lstrcpy,[lpGood],esi
        ; Результат - успешно
        mov     [result],1
.loc_ret:
        ; Вернуть на место текущую директорию
        lea     eax,[old_dir]
        invoke  SetCurrentDirectory,eax
 
        popa
        ; Записать результат в EAX
        mov     eax,[result]
        ret
endp

На входе передаются два указателя: lpRaw - указатель на исходную строку пути, lpGood - указатель на буфер-приемник, куда будет записан последний максимально доступный путь. Исходный путь может содержать не только папки, но и имя файла, в этом случае функция вернет только папки. Также функция исправляет слеши, приводя их к принятому в Windows виду "\". Результат выполнения возвращается в регистре EAX, если он равен 0, то не доступен ни один элемент проверяемого пути, включая диск. Если EAX=1, то доступный путь найден. Функция самодостаточная и не требует наличия каких-либо дополнительных переменных для своей работы.

Я прекрасно понимаю, что ждать подобной милости от разработчиков Windows бесполезно, поэтому предлагаю программистам взять на себя заботу об удобстве пользователей. Ведь именно из таких, казалось бы, незначительных мелочей складывается впечатление о программном продукте и разработчике в целом.

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

Добавлено: 10 Апреля 2018 20:05:34 Добавил: Андрей Ковальчук

Получение списка иконок в трее

Иногда приложению требуется получить список иконок, находящихся в трее, а также список приложений, которые их туда разместили. Это может быть нужно для обнаружения некоторых скрывающихся приложений, для эмуляции кликов на иконках, ну или просто для спортивного интереса. В любом случае для получения списка иконок надо сделать следующее: найти в трее панель с иконками, при помощи сообщения TB_BUTTONCOUNT получить количество иконок, а затем через отправку сообщения TB_GETBUTTON получить всю необходимую информацию по каждой иконке. Теперь рассмотрим все шаги подробнее.

Работать с вложенными окнами трея мы уже умеем, здесь практически то же самое, разница только в названиях классов дочерних окон.

; Сегмент данных
section '.data' data readable writeable 
...
class1  db 'Shell_TrayWnd',0    ; Название класса окна трея
class2  db 'TrayNotifyWnd',0    ; Название класса панели уведомлений
class3  db 'SysPager',0         ; Трей
class4  db 'ToolbarWindow32',0  ; Панель с иконками
 
ToolbarHandle   dd ?            ; Хэндл окна с иконками
...
; Сегмент кода
section '.code' code readable executable
        ...
        ; Найти окно трея
        invoke  FindWindow,class1,NULL
        or      eax,eax
        jz      exit_process
 
        ; Найти панель уведомлений
        invoke  FindWindowEx,eax,NULL,class2,NULL
        or      eax,eax
        jz      exit_process
 
        ; Найти трей
        invoke  FindWindowEx,eax,NULL,class3,NULL
        or      eax,eax
        jz      exit_process
 
        ; Найти панель иконок в трее
        invoke  FindWindowEx,eax,NULL,class4,NULL
        or      eax,eax
        jz      exit_process
 
        ; Сохранить хэндл окна с иконками
        mov     [ToolbarHandle],eax
        ...

Теперь у нас есть хэндл окна панели инструментов с иконками в трее. Получим количество иконок в панели.
        ; Получить количество иконок в трее
        invoke  SendMessage,eax,TB_BUTTONCOUNT,0,0
        or      eax,eax
        jz      exit_process
 
        ; Сохранить количество иконок в трее
        mov     [IconsCount],eax

Количество иконок тоже есть. Осталось перебрать их в цикле и получить всю необходимую информацию. Для этого используется сообщение TB_GETBUTTON и структура TBBUTTON для получения результата. Однако, если сейчас попробовать послать окну панели сообщение TB_GETBUTTON, то в результате не получим ничего. Почему? Потому что память, в которую будут записываться данные, обязательно должна принадлежать процессу, который является владельцем окна трея (обычно это explorer.exe).

Ненадолго отвлечемся от трея и выделим блок памяти нужного размера в контексте процесса-владельца трея. Размер блока равен размеру структуры TBBUTTON.
        ; Получить ID процесса-владельца трея
        invoke  GetWindowThreadProcessId,[ToolbarHandle],ProcId
        ; Открыть процесс с полным доступом
        invoke  OpenProcess,PROCESS_ALL_ACCESS,FALSE,[ProcId]
        or      eax,eax
        ; Фокус не удался
        jz      exit_process
 
        ; Сохранить хэндл процесса-владельца трея
        mov     [hProcess],eax
 
        ; Выделить блок памяти в контексте процесса
        invoke  VirtualAllocEx,[hProcess],NULL,dword sizeof.TBBUTTON,\
                MEM_COMMIT,PAGE_READWRITE
        or      eax,eax
        jz      exit_process
 
        ; Сохранить указатель на блок памяти
        mov     [lpData],eax

Теперь все готово для приема данных, можно приступать к перебору иконок в трее. В структуре TBBUTTON двойное слово dwData - указатель на блок расширенных данных, которые определяются приложением. В нашем случае по этому адресу лежит структура EXTRADATA, не описанная в FASM:
; Структура пользовательских данных иконки
struct EXTRADATA
        Wnd dd ?  ; Хэндл родительского окна иконки
        uID dd ?  ; Стиль отображения иконки
ends

Поскольку все нужные данные находятся в другом процессе, читать их будем через функцию ReadProcessMemory: сперва структуру TBBUTTON, а затем соответствующую ей структуру EXTRADATA. Зная хэндл окна-владельца каждой иконки, можно получить идентификатор процесса, которому принадлежит окно, а по нему, в свою очередь, можно узнать имя исполняемого файла. Для получения имени есть несколько методов, в этом примере я буду использовать функцию CreateToolhelp32Snapshot.
        ; Перебрать все иконки в трее
loc_loop:
        dec     [IconsCount]
 
        ; Получить иконку из трея с индексом IconsCount
        invoke  SendMessage,[ToolbarHandle],TB_GETBUTTON,[IconsCount],[lpData]
        ; Прочитать структуру иконки
        invoke  ReadProcessMemory,[hProcess],[lpData],button,\
                dword sizeof.TBBUTTON,BytesRead
        or      eax,eax
        jz      exit_process
        ; Прочиталась вся структура?
        cmp     [BytesRead],sizeof.TBBUTTON
        jnz     exit_process
 
        ; Прочитать пользовательские данные иконки
        invoke  ReadProcessMemory,[hProcess],[button.dwData],extra,\
                dword sizeof.EXTRADATA,BytesRead
        or      eax,eax
        jz      exit_process
        ; Прочиталась вся структура?
        cmp     [BytesRead],sizeof.EXTRADATA
        jnz     exit_process
 
        ; Это скрытая иконка?
        mov     eax,[extra.uID]
        and     eax,80000000h
        or      eax,eax
        ; Да, пропустить
        jnz     loc_loop
 
        ; Окно процесса существует?
        invoke  IsWindow,[extra.Wnd]
        or      eax,eax
        jz      loc_loop
 
        ; Получить Id процесса, чья иконка находится в трее
        invoke  GetWindowThreadProcessId,[extra.Wnd],ProcTrayId
 
        ; Снимок процессов системы
        invoke  CreateToolhelp32Snapshot,TH32CS_SNAPPROCESS,0
        mov     ebx,eax
 
        ; Перебрать в цикле все процессы
        mov     eax,sizeof.PROCESSENTRY32
        mov     [ProcEntry.dwSize],eax
        invoke  Process32First,ebx,ProcEntry
@@:
        cmp     eax,FALSE
        je      @f
        ; Это нужный нам процесс?
        mov     eax,[ProcEntry.th32ProcessID]
        cmp     eax,[ProcTrayId]
        je      @f
        ; Следующий процесс
        invoke  Process32Next,ebx,ProcEntry
        or      eax,eax
        jz      loc_loop
        jmp     @b
@@:
        push    eax
        ; Закрыть хэндл
        invoke  CloseHandle,ebx
        pop     eax
 
        ; Имя файла определить не удалось
        or      eax,eax
        jz      @f
 
        invoke  wsprintf,buff,mask,ProcEntry.szExeFile
        add     esp,12
 
        ; Записать имя файла в консоль
        invoke  lstrlen,buff
        invoke  WriteConsole,[stdout],buff,eax,BytesRead,NULL
@@:
        ; Все иконки обработали?
        cmp     [IconsCount],0
        ja      loc_loop
 
        ; Очистить память и ресурсы
        invoke  VirtualFreeEx,[ProcId],[lpData],0,MEM_RELEASE
        invoke  CloseHandle,[ProcId]

Описанный метод протестирован и гарантированно работает в Windows XP и Windows 7, но не работает в альтернативных оболочках типа Aston Desktop, потому что в них используются другие названия классов окон и их иерархия.

В приложении консольная программа с исходником, выводящая на экран список исполняемых файлов всех приложений, иконки которых видны в трее.

Добавлено: 10 Апреля 2018 20:02:48 Добавил: Андрей Ковальчук

Проверка и обнаружение зависших приложений

Иногда для работы требуется определение зависших приложений, окна которых не отвечают на сообщения. Для этого есть два способа. Первый - официально документированный, через функцию SendMessageTimeOut. Особенность ее работы заключается в том, что после отправки сообщения окну она ждет ответ заданное время, и, если ответа от приложения не последовало, то возвращает FALSE. Вот пример использования функции. Нужные константы, как обычно, в FASM не определены, пришлось брать их из других источников.

        ...
        ; Определить таймаут 50 миллисекунд
        TIMEOUT = 50
        ; Определить константу SMTO_ABORTIFHUNG
        SMTO_ABORTIFHUNG = 2
        ; hwnd - хэндл проверяемого окна
        invoke  SendMessageTimeout,[hwnd],NULL,0,0,SMTO_ABORTIFHUNG,TIMEOUT,NULL
        ; Если вернулся 0, то приложение "висит"
        or      eax,eax
        jz      app_hung_up
        ...

Минус использования этой функции в том, что при частой проверке большого количества окон зависших приложений, каждый раз будет отрабатываться таймаут для каждого такого окна, что суммарно может дать снижение производительности вашего приложения. Но повторюсь, Microsoft официально рекомендует к использованию именно эту функцию.

Второй способ - недокументированный. В Windows есть особые функции, которые используются, например, во встроенном Диспетчере задач. Для операционных систем Windows 2000 и выше это IsHungAppWindow, а для систем Windows 9x и Windows ME - функция IsHungThread (она вообще никак не документирована на MSDN). Разница между ними в том, что IsHungAppWindow работает с хэндлом окна, а IsHungThread с хэндлом потока окна. Поскольку в системной библиотеке user32.dll одновменно может быть только одна из этих функций, то включать их в секцию импорта нельзя, иначе ваше приложение вылетит с ошибкой. Также есть информация, что они вообще недоступны через экспорт. Стало быть воспользуемся штатными функциями API GetModuleHandle и GetProcAddress, но предварительно в секции данных определим нужные нам переменные:
section '.data' data readable writeable
        ...
hUser   dd ?    ; Хэндл библиотеки user32.dll
hIHW    dd ?    ; Адрес функции IsHungAppWindow (Win2k и выше)
hIHT    dd ?    ; Адрес функции IsHungThread (Win9x)
 
strUser db      'user32.dll',0
strIHW  db      'IsHungAppWindow',0
strIHT  db      'IsHungThread',0
        ...
Перед использованием надо получить все необходимые адреса функций:
Code (Assembler) : Убрать нумерацию
section '.code' code readable executable
        ...
        mov     [hIHW],0          ; Инициализировать переменные
        mov     [hIHT],0
 
        ; Получить хэндл библиотеки user32.dll
        invoke  GetModuleHandle,strUser
        mov     [hUser],eax
 
chk_WinNT:
        ; Попытаться получить адрес функции IsHungAppWindow
        invoke  GetProcAddress,[hUser],strIHW
        or      eax,eax
        jz      chk_Win9X
        mov     [hIHW],eax        ; Сохранить адрес функции
        jmp     @f
 
chk_Win9X:
        ; Попытаться получить адрес функции IsHungThread
        invoke  GetProcAddress,[hUser],strIHT
        or      eax,eax
        jz      loc_fatal_error
        mov     [hIHT],eax        ; Сохранить адрес функции
        jmp     @f
 
loc_fatal_error:
        ; По какой-то немыслимой причине не найдено ни одной функции 
        ...
@@:
        ; Продолжить обработку
        ...

И теперь сама функция проверки окна приложения на предмет его зависания. Для большей универсальности я дополнил ее кодом первого документированного способа, он будет вызываться в случае, когда не определена ни одна из функций второго способа. Если время выполнения функции имеет критическое значение, например, при частом обновлении большого списка процессов или окон, то предпочтительнее воспользоваться именно этим ее вариантом.
;----------------------------------------------------------------
; Процедура проверки окна на зависание его родительского приложения
; Предварительно должны быть определены переменные hIHW или hIHT
; с адресами функций IsHungAppWindow или IsHungThread
;
; На входе:
;   hwnd - хэндл проверяемого окна
; На выходе:
;   EAX = 0 - приложение работает или такое окно не найдено
;   EAX = 1 - приложение зависло и не отвечает
;----------------------------------------------------------------
proc IsAppHung hwnd:dword
        local   tmp:DWORD     ; Временная переменная
 
        pusha
 
        ; Такое окно есть?
        invoke  IsWindow,[hwnd]
        or      eax,eax
        jz      .loc_ret
.chk_WinNT:
        ; Адрес функции IsHungAppWindow известен?
        cmp     [hIHW],0
        je      .chk_Win9x
        stdcall [hIHW],[hwnd]
        jmp     .loc_ret
.chk_Win9x:
        ; Адрес функции IsHungThread известен?
        cmp     [hIHT],0
        je      .chk_All
        invoke  GetWindowThreadProcessId,[hwnd],NULL
        stdcall [hIHT],eax
        jmp     .loc_ret
.chk_All:
        ; Определить таймаут 50 миллисекунд
        TIMEOUT = 50
        ; Определить константу SMTO_ABORTIFHUNG
        SMTO_ABORTIFHUNG = 2
        invoke  SendMessageTimeout,[hwnd],NULL,0,0,SMTO_ABORTIFHUNG,TIMEOUT,NULL
        sub     eax,1
        neg     eax
.loc_ret:
        ; Сохранить значение
        mov     [tmp],eax
 
        ; Восстановить регистры и сохраненное значение
        popa
        mov     eax,[tmp]
 
        ret
endp

Если очень частый вызов проверки не требуется, то код инициализации можно включить в саму функцию IsAppHung. В результате получится универсальная и самодостаточная функция, не требующая для работы вообще никаких сторонних переменных.
;----------------------------------------------------------------
; Процедура проверки окна на зависание его родительского приложения
;
; На входе:
;   hwnd - хэндл проверяемого окна
; На выходе:
;   EAX = 0 - приложение работает или такое окно не найдено
;   EAX = 1 - приложение зависло и не отвечает
;----------------------------------------------------------------
proc IsAppHung hwnd:dword
        local   tmp:DWORD     ; Временная переменная
 
        pusha
 
        ; Такое окно есть?
        invoke  IsWindow,[hwnd]
        or      eax,eax
        jz      .loc_ret
 
        ; Получить хэндл библиотеки user32.dll
        invoke  GetModuleHandle,strUser
        mov     [tmp],eax
 
.chk_WinNT:
        invoke  GetProcAddress,[tmp],strIHW
        or      eax,eax
        jz      .chk_Win9x
        stdcall eax,[hwnd]
        jmp     .loc_ret
.chk_Win9x:
        invoke  GetProcAddress,[tmp],strIHT
        or      eax,eax
        jz      .chk_All
        mov     [tmp],eax
        invoke  GetWindowThreadProcessId,[hwnd],NULL
        stdcall [tmp],eax
        jmp     .loc_ret
.chk_All:
        ; Определить таймаут 50 миллисекунд
        TIMEOUT = 50
        ; Определить константу SMTO_ABORTIFHUNG
        SMTO_ABORTIFHUNG = 2
        invoke  SendMessageTimeout,[hwnd],NULL,0,0,SMTO_ABORTIFHUNG,TIMEOUT,NULL
        sub     eax,1
        neg     eax
.loc_ret:
        ; Сохранить значение
        mov     [tmp],eax
 
        ; Восстановить регистры и сохраненное значение
        popa
        mov     eax,[tmp]
 
        ret
 
strUser db      'user32.dll',0
strIHW  db      'IsHungAppWindow',0
strIHT  db      'IsHungThread',0
 
endp

По результатам полевых испытаний функции выявлены некоторые интересные факты. В частности выполнялся полный перебор всех top-level окон с проверкой их на предмет зависания. В процессе перебора на рабочей системе Windows XP были пойманы два системных окна с неизменными заголовками "M" и "Default IME", которые определялись как зависшие. Однако это не так, просто эти окна принадлежат к критическим системным процессам. Поэтому если ваша программа также выполняет проверку всех окон, то эти два окна в обработчике зависших процессов надо будет просто пропускать после проверки их заголовка.

Для имитации зависшего приложения можно воспользоваться программой Bad Application или программой hangup.exe из архива. В архиве примеры программ для обнаружения зависших приложений всеми тремя способами и программа-имитатор зависшего приложения. Имитаторы зависших приложений придется снимать только через менеджер процессов.

Добавлено: 10 Апреля 2018 19:59:01 Добавил: Андрей Ковальчук

Как узнать, что программа запущена под Администратором

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

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

; Сегмент данных
section '.data' data readable writeable
 
SECURITY_NT_AUTHORITY       = 5
TOKEN_READ                  = 0x00020008
SECURITY_BUILTIN_DOMAIN_RID = 0x00000020
DOMAIN_ALIAS_RID_ADMINS     = 0x00000220
TokenGroups                 = 0x00000002
 
BUFF_SIZE = 1024h ;  Размер буфера для групп доступа токена
 
NtAuthority     db 0,0,0,0,0,SECURITY_NT_AUTHORITY
 
hTokenHandle    dd ?
dInfoSize       dd ?
psidAdmins      dd ?
hHeap           dd ?
pTokenGroups    dd ?
 
;---------------------------------------------
 
; Сегмент кода
section '.code' code readable executable
        ...
        ; Получить токен текущего процесса
        invoke  GetCurrentProcess
        invoke  OpenProcessToken,eax,TOKEN_READ,hTokenHandle
 
        ; Выделить память для массива групп
        invoke  GetProcessHeap
        mov     [hHeap],eax
 
        invoke  HeapAlloc,eax,HEAP_ZERO_MEMORY,BUFF_SIZE
        mov     [pTokenGroups],eax
 
        ; Получить информацию о группах доступа токена
        invoke  GetTokenInformation,[hTokenHandle],TokenGroups,\
                [pTokenGroups],dword BUFF_SIZE,dInfoSize
 
        ; Прибраться за собой
        invoke  CloseHandle,[hTokenHandle]
 
        invoke  AllocateAndInitializeSid,NtAuthority,2,\
                SECURITY_BUILTIN_DOMAIN_RID,\
                DOMAIN_ALIAS_RID_ADMINS,0,0,0,0,0,0,psidAdmins
 
        ; Количество записей в структуре TOKEN_GROUPS
        mov     esi,[pTokenGroups]
        mov     ebx,dword [esi]
        ; Указатель на массив SID_AND_ATTRIBUTES
        add     esi,4
@@:
        ; Проверить соответствие SID
        mov     eax,dword [esi]
        invoke  EqualSid,[psidAdmins],eax
        or      eax,eax
        jnz     loc_admin
 
        ; Следующая группа
        add     esi,8
        dec     ebx
        or      ebx,ebx
        jnz     @b
 
loc_not_admin:
        ; Пользователь не Администратор
        ...
 
loc_admin:
        ; Пользователь Администратор
        ...

Обратите внимание, что никаких проверок на ошибки не выполняется, оставлен только рабочий код. Полный вариант вы можете посмотреть в приложении к статье.

Второй вариант очень похож на предыдущий, но будет работать только на системах, начиная с Windows 2000. В нем используется функция CheckTokenMembership, которая и выполняет все громоздкие проверки.
; Сегмент данных
section '.data' data readable writeable
 
SECURITY_NT_AUTHORITY       = 5
SECURITY_BUILTIN_DOMAIN_RID = 0x00000020
DOMAIN_ALIAS_RID_ADMINS     = 0x00000220
 
NtAuthority     db 0,0,0,0,0,SECURITY_NT_AUTHORITY
 
psidAdmins      dd ?
pbAdmin         dd ?
 
;---------------------------------------------
 
; Сегмент кода
section '.code' code readable executable
        ...
        invoke  AllocateAndInitializeSid,NtAuthority,2,\
                SECURITY_BUILTIN_DOMAIN_RID,\
                DOMAIN_ALIAS_RID_ADMINS,0,0,0,0,0,0,psidAdmins
 
        invoke  CheckTokenMembership,NULL,[psidAdmins],pbAdmin
        mov     eax,[pbAdmin]
        ; Если EAX=1, то программа запущена под Администратором
        ...

Следующий вариант самый короткий, но он также будет работать только на новых системах. В нем используется функция IsUserAdmin из библиотеки setupapi.dll. Как вы можете догадаться из ее названия, результатом работы этой функции будет TRUE, если пользователь является Администратором, и FALSE, если нет.
        ...
        invoke  IsUserAdmin
        ; Если EAX=1, то программа запущена под Администратором
        ...

И последний способ, не совсем обычный, заключается в том, что сперва мы получаем логин текущего пользователя, а затем с помощью функции NetUserGetInfo запрашиваем подробную информацию о нем (структура USER_INFO_1). В поле usri1_priv хранится информация о правах доступа этого пользователя.
; Сегмент данных
section '.data' data readable writeable
 
struct  USER_INFO_1
        usri1_name         dd ?
        usri1_password     dd ?
        usri1_password_age dd ?
        usri1_priv         dd ?
        usri1_home_dir     dd ?
        usri1_comment      dd ?
        usri1_flags        dd ?
        usri1_script_path  dd ?
ends
 
dSize           dd 100h
szUname         rb 100h
info            dd ?
 
NERR_SUCCESS    = 0
USER_PRIV_ADMIN = 2
 
;---------------------------------------------
 
; Сегмент кода
section '.code' code readable executable
        ...
        ; Получить логин текущего пользователя
        invoke  GetUserName,szUname,dSize
 
        ; Получить информацию о пользователе
        invoke  NetUserGetInfo,NULL,szUname,1,info
        cmp     eax,NERR_SUCCESS
        jne     loc_error
 
        ; Указатель на структуру USER_INFO_1
        mov     eax,[info]
 
        ; Пользователь админ?
        cmp     dword [eax+USER_INFO_1.usri1_priv],USER_PRIV_ADMIN
        je      loc_admin
 
loc_not_admin:
        ; Пользователь не Администратор
        ...
 
loc_admin:
        ; Пользователь Администратор
        ...

Обратите внимание, что все функции, строки и другие ресурсы, использованные в последнем примере, должны быть юникодными. Также это очень ненадежный способ проверки, например, на домашнем компьютере под Windows 7 она работает нормально, а на работе под Windows XP функция NetUserGetInfo возвращает ошибку, что пользователя не существует.

Ну а в заключении еще один небольшой пример, не совсем относящийся к теме статьи, но очень близкий. Это проверка, загружена система в безопасном режиме или в нормальном.
        ; Получить информацию о загрузке системы
        invoke  GetSystemMetrics,SM_CLEANBOOT
        ; EAX=0 - нормальная загрузка
        ; EAX=1 - безопасный режим
        ; EAX=2 - безопасный режим с поддержкой сети

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

Добавлено: 10 Апреля 2018 17:51:13 Добавил: Андрей Ковальчук

Запись в архивы RAR, ZIP и ARJ без помощи архиватора

Решил вспомнить свою юность, когда я занимался разработкой "самоходного программного обеспечения". Одной из особенностей моих творений было распространение не только через исполняемые файлы, но и через архивы различных форматов. Технология внедрения в архивные файлы не нова, и использовалась в разных вирусах еще со времен MS-DOS. Так как готовых решений на тот момент у меня не было, пришлось доходить до всего самому. Никакого вредоносного кода на этом сайте не появится, а вот некоторыми своими наработками по внедрению в архивы я с удовольствием поделюсь. Поскольку полноценно продублировать алгоритмы сжатия архиваторов в столь малом размере файла не представляется возможным, внедряться в архивы мы будем по методу "Store". Это означает, что файл в архив добавлен с опцией "без сжатия". Внутренние форматы различных архивных файлов, естественно, различаются, но у всех обязательно присутствуют служебные заголовки, используемые архиваторами, и, собственно, сами упакованные данные.

Самый простой для внедрения - формат RAR, используемый одноименным архиватором. Подробную спецификацию формата RAR можно почитать здесь.

;---------------------------------------------
; RAR Header
;---------------------------------------------
rhcrc   dw      ?     ; --> Low-word CRC32 of fields in header
rhtype  db      ?     ; Header type: 0x74
rhflag  dw      ?     ; Bit flags
rhsize  dw      ?     ; File header full size
rcsize  dd      ?     ; Compressed file size
rosize  dd      ?     ; Uncompressed file size
rhoss   db      ?     ; Target OS version
rfcrc   dd      ?     ; --> File CRC32
rdtm    dd      ?     ; File Time/Date
runp    db      ?     ; Archive version to extract
rmeth   db      ?     ; Packing method (store)
rnsize  dw      ?     ; File name size
rfattr  dd      ?     ; File attributes
rfname  rb      (?)   ; File name

Мнемокода "(?)" в FASM нет, просто таким образом я обозначил текстовую строку для записи имени файла неопределенной длины. В реальном проекте будет достаточно MAX_PATH или вообще фиксированного размера.
; "Хвост" архива - признак окончания данных
tail    db      0C4h,03Dh,07Bh,00h,040h,07h,00h
tail_length     = $-tail

Однако надо знать, что не во все RAR-архивы можно безопасно внедряться. Обязательно надо проверять флаговые биты в основном заголовке архива и пропускать заблокированные, многотомные, непрерывные архивы и архивы с цифровой подписью. Все эти проверки вы сможете сделать самостоятельно, здесь я их описывать не буду.

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

1. rhtype = 74h - тип заголовка (файл);
2. rhflag = 8000h - флаги;
3. runp = 14h - версия архиватора, необходимая для распаковки файла;
4. rmeth = 30h - метод упаковки файла (store);
5. rfattr = 20h - атрибуты файла (архивный);
6. rhoss = 2 - целевая ОС (Windows);
7. rdtm = время и дата создания файла в формате MS-DOS;
8. rfcrc = CRC32 оригинального файла;
9. rosize = размер оригинального файла;
10. rcsize = размер сжатого файла, равен оригинальному размеру;
11. rfname = имя файла;
12. rnsize = длина имени файла;

После этого считаем CRC32 заголовка, начиная от поля rhtype и до rfname включительно, затем надо взять младшее слово от результата расчета CRC32 и записать его в поле rhcrc. Все, заголовок заполнен. Открываем целевой архив для записи, и устанавливаем указатель на позицию 7 байт от конца файла, чтобы удалить "хвост" архива. Записываем наш заголовок, следом записываем внедряемый файл. После этого записываем 7-байтовый "хвост" архива. Точно таким же способом можно прицепляться к RAR-SFX архивам. На этом теорию внедрения в архивы формата RAR можно считать освоенной.

Архиватор ARJ во времена MS-DOS фактически являлся стандартом архивирования, но сейчас утратил актуальность. Однако наработки по внедрению в него остались. Есть официальная коммерческая версия и бесплатная с открытым кодом, обе они совместимы между собой и используют одинаковый формат архива. Техническая спецификация формата ARJ есть в файлеTECHNOTE.TXT из дистрибутива коммерческой версии. Дописывание файла к архиву ARJ делается немного сложнее, чем к RAR, но тоже не представляет больших трудностей.
;---------------------------------------------
; ARJ Header
;---------------------------------------------
marker  dw      ?     ; Header ID
bhsize  dw      ?     ; --> Basic header size (acrc-fhsize)
fhsize  db      ?     ; --> First header size (afname-fhsize)
anum    db      ?     ; Archive version number
anum2   db      ?     ; Archive version to extract
osver   db      ?     ; Target OS version
aflag   db      ?     ; No any flags
ameth   db      ?     ; Archive method (0 - stored)
aftype  db      ?     ; File type (0 - binary)
ares    db      ?     ; Reserved
dtm     dd      ?     ; Date/Time last modification
csize   dd      ?     ; Compressed size
osize   dd      ?     ; Original size
crc     dd      ?     ; --> Original file CRC32
fspec   dw      ?     ; Filespec position in filename
faccess dw      ?     ; File access mode
hstdata dw      ?     ; Host data
extra1  dd      ?     ; Extended file position
edtma   dd      ?     ; Date-time accessed
edtmc   dd      ?     ; Date-time created
extra2  dd      ?     ; Original file size even for volumes
afname  rb      (?)   ; File name (ASCIIZ)
acomm   db      ?     ; File comment (ASCIIZ)
acrc    dd      ?     ; --> Basic header CRC32
ehsize  dw      ?     ; Extended header size

; "Хвост" архива - признак окончания данных
tail    db      060h,0EAh,00h,00h
tail_length     = $-tail

Записываемый файл также предварительно надо загрузить в память, после этого заполняем поля заголовка.

1. marker = 0EA60h - маркер заголовка архива;
2. anum = 6 - версия архиватора;
3. anum2 = 1 - версия архиватора для извлечения файла;
4. osver = 11 - ОС для запуска файла (Windows);
5. fhsize = 2Eh - размер первого заголовка от поля fhsize до afname;
6. dtm = время и дата создания файла в формате MS-DOS;
7. crc = CRC32 оригинального файла;
8. osize = размер оригинального файла;
9. csize = размер сжатого файла, равен оригинальному размеру;
10. fname = имя файла в формате ASCIIZ;

После этого надо посчитать размер полученного заголовка от поля fhsize до acommвключительно и вычислить его CRC32. Размер записывается в поле bhsize, а CRC32 в полеacrc. Остальные поля для нас значения не имеют и заполняются нулевыми значениями. Если интересно, можете почитать про них в документации. Открываем целевой архив для записи, и устанавливаем указатель на позицию 4 байта от конца файла, чтобы удалить "хвост" архива, как и в случае с форматом RAR. Записываем наш заголовок, за ним внедряемый файл и 4-байтовый "хвост" архива. Все, с форматом ARJ разобрались. Также не забывайте проверять флаги в главном заголовке архива, чтобы не повредить запароленные, многотомные и другие архивы, в которые нельзя добавлять файлы таким способом. Эти проверки сделайте самостоятельно.

Наиболее сложным для внедрения является формат ZIP. Здесь информация о сжатом файле хранится в нескольких местах: локальном заголовке и так называемой "центральной директории". Это сделано специально, чтобы можно было максимально быстро получить доступ к структуре архива, не просматривая целиком его содержимое. И именно из-за этой особенности при внедрении нам придется обрабатывать весь архив. Для больших архивов придется создавать временный файл, а в нашем случае вполне можно прочитать архив целиком в память. Спецификацию формата ZIP можно почитать на офсайте разработчиков. Этот же формат имеют архивы JAR, по сути это просто переименованные ZIP-файлы.
;---------------------------------------------
; ZIP-header (local)
;---------------------------------------------
zlid    dw      ?     ; Header Id
zlsig   dw      ?     ; Signature
zlvneed dw      ?     ; Version Need
zlflags dw      ?     ; Flags
zlmeth  dw      ?     ; Method
zldtm   dd      ?     ; DateTime
zlfcrc  dd      ?     ; --> CRC32
zlcsize dd      ?     ; Compressed Size
zlosize dd      ?     ; Uncompressed Size
zlnsiz  dw      ?     ; Size of Filename
zlefild dw      ?     ; Size of Extra Field
zlfname rb      (?)   ; Filename

;---------------------------------------------
; ZIP-header (central directory)
;---------------------------------------------
zid     dw      ?     ; Header Id
zsig    dw      ?     ; Signature
zverm   dw      ?     ; Version Made
zvneed  dw      ?     ; Version Need
zflags  dw      ?     ; Flags
zmeth   dw      ?     ; Method
zdtm    dd      ?     ; TimeDate
zfcrc   dd      ?     ; --> CRC32
zcsize  dd      ?     ; Compressed Size
zosize  dd      ?     ; Uncompressed Size
znsiz   dw      ?     ; Size of Filename
zefield dw      ?     ; Size of Extra Field
zcomm   dw      ?     ; Comment Size
zdnumb  dw      ?     ; Disk Number
ziattr  dw      ?     ; Internal Attributes
zeattr  dd      ?     ; External Attributes
zohead  dd      ?     ; Offset Header
zfname  rb      (?)   ; Filename

;---------------------------------------------
; ZIP-header (End of central directory)
;---------------------------------------------
zeid    dw      ?     ; Header Id
zesig   dw      ?     ; Signature
zenumd  dw      ?     ; Number of this disk
zenume  dw      ?     ; Number of this disk
zetotal dw      ?     ; Total number of entries of disk
zedir   dw      ?     ; Total number of entries of the central directory
zesizec dd      ?     ; Size of the central directory
zeoffs  dd      ?     ; Offset of start of central directory
zecomm  dw      ?     ; ZIP file comment length

Локальный заголовок файла и заголовок файла в центральной директории по большинству полей совпадают, а значит, что их можно и нужно заполнять параллельно. Локальный заголовок:

1. zlid = 'PK' - сигнатура заголовка;
2. zlsig = 0403h - тип заголовка - локальный;
3. zlvneed = 10h - версия архиватора для извлечения файла;
4. zlflags = 80h - флаги файла (см. в документации);
5. zlmeth = 0 - метод упаковки без сжатия;
6. zldtm = время и дата создания файла в формате MS-DOS;
7. zlfcrc = CRC32 оригинального файла;
8. zlcsize = размер сжатого файла, равен оригинальному размеру;
9. zlosize = размер оригинального файла;
10. zlnsiz = длина имени файла;
11. zlfname = имя файла;

Центральная директория:

1. zid = 'PK' - сигнатура заголовка;
2. zsig = 0201h - тип заголовка - центральная директория;
3. zverm = 10h - версия архиватора для извлечения файла
4. zvneed = 10h - версия архиватора для извлечения файла;
5. zflags = 80h - флаги файла (см. в документации);
6. zmeth = 0 - метод упаковки без сжатия;
7. zdtm = время и дата создания файла в формате MS-DOS;
8. zfcrc = CRC32 оригинального файла;
9. zcsize = размер сжатого файла, равен оригинальному размеру;
10. zosize = размер оригинального файла;
11. znsiz = длина имени файла;
12. zeattr = 20h - атрибут файла (архивный)
13. zfname = имя файла;

После заполнения заголовков переносим упакованные файлы из исходного архива в новый. Для этого в цикле проверяем сигнатуру локальных заголовков с самого начала архива. Если она равна 0x04034b50, то вычисляем размер блока по сумме размера локального заголовка от поляzlid до zlfname, длины имени файла zlnsiz и длины дополнительных полей zlefild. Когда все упакованные файлы из исходного архива будут перенесены в новый, можно записывать в новый архив наш заполненный локальный заголовок и тело файла. Перед этим надо запомнить абсолютное смещение нашего локального заголовка файла относительно начала нового архива и заполнить этим значением поле zohead заголовка нашего файла в центральной директории. Архив может содержать дополнительные данные (Archive Extra Data), они расположены после файлов и оформлены своим заголовком с сигнатурой 0x08064b50. Формат и значения полей заголовка описаны в документации. Их также надо записать в новый архив. Теперь надо перенести все записи из центральной директории исходного архива в новый. Каждый блок идентифицируется по заголовку 0x02014b50, размер блока равен сумме размера заголовка центральной директории от поля zid до поля zfname, длины имени файла znsiz и длины дополнительных данных zefield. Перед переносом центральной директории надо также запомнить ее абсолютное смещение относительно начала нового архива, это значение потребуется при заполнении поля zeoffs завершающей структуры центральной директории (End of central directory record). Когда центральная директория исходного файла перенесена в новый архив, можно дописывать к ней заголовок центральной директории нашего файла. После этого в новый архив надо перенести завершающую структуру центральной директории, она начинается с сигнатуры 0x06054b50. Предварительно в ней надо увеличить на 1 значения полей, обозначающих количество файлов в архиве (zenumd и zenume) и размер центральной директории (увеличить текущее значение zesizec на размер заголовка нашего файла). Полный размер блока завершающей структуры центральной директории равен сумме размера ее заголовка от поля zeid до поля zecomm включительно и размера комментария архива zecomm. Вот, вроде бы и все. Если из описания процесс обработки ZIP-архивов не очень понятен, то смотрите исходники. Как вариант, можно обрабатывать содержимое архива не с начала файла, а сразу анализируя заголовки центральной директории. Такой способ вполне имеет право на существование и, наверное, может даже считаться более надежным.

UPD: При написании статьи я столкнулся с некоторыми JAR-архивами, у которых в локальном заголовке размеры файла и CRC32 были просто обнулены, в результате этого невозможно посчитать размер блока файла. Видимо программы, работающие с ними, ориентируются на данные центральной директории. Попытка внедриться в такие архивы описанным выше способом приведет к их безвозвратному повреждению, поэтому при этом способе внедрения такие файлы надо пропускать. Я разобрался со способом внедрения в архив через центральную директорию. Здесь алгоритм немного другой. Сперва надо найти End of central directory record. Она имеет фиксированный размер, но архив также может содержать комментарий, который записан ПОСЛЕ End of central directory record в конце файла, а размер комментария записан в нее же. Так что я не придумал ничего лучше, чем просто сканировать файл с конца по сигнатуре0x06054b50, благо что максимальный размер комментария не может превышать значения WORD, то есть 65535 байт. Из найденной структуры мы извлекаем данные о местоположении центральной директории относительно начала файла. После этого начинаем по очереди перебирать записи из нее, а именно смещение упакованных файлов и их размеры, записывая их в новый архив. Здесь важно учитывать следующий момент: если в локальном заголовке файла в поле zlflags установлен 3-й бит, то информация о контрольной сумме, а также о размерах упакованного и оригинального файла содержатся в 16-байтном блоке data descriptor, который записан сразу же после упакованного файла и начинается с сигнатуры 0x08074b50. Причем в архиве могут одновременно быть файлы как с заполненными локальными заголовками, так и с неполными заголовками + data descriptor. Таким образом, мы через центральную директорию находим файл в архиве, записываем его в новый архив, проверяем наличие data descriptor, в случае его наличия также переносим в новый архив сразу после файла. Когда будут перенесены все файлы, записываем наш файл. После этого записываем оригинальную центральную директорию, данные нашего файла из центральной директории и скорректированную End of central directory record с файловым комментарием (при его наличии). Смотрите исходники, там понятнее. Таким образом можно корректно прицепляться к обычным архивам, JAR-архивам и даже документам OpenOffice (odt, ods) и MS Office 2010 (docx, xlsx), которые по сути также являются ZIP-архивами. Кроме того, при обработке центральной директории можно внедряться не только в конец, но и в середину архива, а также подменять собой уже имеющиеся в архиве файлы. Но это уже переходит границу добрых дел, поэтому останется только идеей.

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

К сожалению, современные архиваторы типа 7zip, WinRK, KuaiZip, WinArchiver и WinUHA имеют более сложный внутренний формат, там даже имена файлов подвергаются сжатию или хранятся в отдельном блоке, а KGB Archiver вообще не подразумевает возможность модификации своих архивов.

Добавлено: 10 Апреля 2018 09:15:51 Добавил: Андрей Ковальчук

Время непрерывной работы (Uptime) Windows

Для получения времени непрерывной работы системы обычно используется функция API GetTickCount. Она возвращает количество миллисекунд, прошедших с момента последнего старта системы. Проблема в том, что счетчик имеет тип dword, и по прошествии примерно 50 дней (49,7 если быть точным) достигает предельного значения и обнуляется. Конечно, продержать систему без перезагрузки почти два месяца трудно, но не значит что невозможно. Поэтому для получения гарантированно точного времени работы системы воспользуемся функцией NtQuerySystemInformation. Достаточно долго эта функция относилась к разряду недокументированных, теперь же на MSDN по ней имеется описание с примечанием, что ее использование в прикладных программах все равно нежелательно.

; Сегмент данных
section '.data' data readable writeable
 
; По умолчанию структура в FASM не определена, сделаем это самостоятельно
; Для получения необходимой информации нужны только два первых значения
struct SYSTEM_TIME_INFORMATION
       liKeBootTime       dq ?  ; Время старта системы
       liKeSystemTime     dq ?  ; Текущее время
       liExpTimeZoneBias  dq ?
       uCurrentTimeZoneId dd ?
       dwReserved         dw ?
ends
 
SystemTime   SYSTEM_TIME_INFORMATION  ; Наша структура с данными
 
; Константа нужного класса информации тоже не определена, сделаем это сами
GET_SYSTEM_TIME_INFORMATION = 3 
 
; Сегмент кода
section '.code' code readable executable
...
        invoke  NtQuerySystemInformation, GET_SYSTEM_TIME_INFORMATION,\
                SystemTime, sizeof.SYSTEM_TIME_INFORMATION, 0
        ; Записать в регистры EDX:EAX текущее время в миллисекундах
        mov     eax, dword [SystemTime.liKeSystemTime]
        mov     edx, dword [SystemTime.liKeSystemTime+4]
        ; Вычесть время старта системы
        sub     eax, dword [SystemTime.liKeBootTime]
        sbb     edx, dword [SystemTime.liKeBootTime+4]
        ; Теперь в регистры EDX:EAX записано реальное количество миллисекунд,
        ; прошедшее с момента старта системы
...

Преобразовать миллисекунды в обычный вид даты и времени можно при помощи пары стандартных функций FileTimeToLocalFileTime и FileTimeToSystemTime.
; Сегмент данных
section '.data' data readable writeable
 
; По умолчанию структура в FASM не определена, сделаем это самостоятельно
; Для получения необходимой информации нужны только два первых значения
struct SYSTEM_TIME_INFORMATION
       liKeBootTime       dq ?  ; Время старта системы
       liKeSystemTime     dq ?  ; Текущее время
       liExpTimeZoneBias  dq ?
       uCurrentTimeZoneId dd ?
       dwReserved         dw ?
ends
 
SystemTime   SYSTEM_TIME_INFORMATION  ; Наша структура с данными
 
; Добавляются две стандартные структуры для работы с датой и временем
ftSystemTime FILETIME
stSystemTime SYSTEMTIME
 
; Константа нужного класса информации тоже не определена, сделаем это сами
GET_SYSTEM_TIME_INFORMATION = 3 
 
; Сегмент кода
section '.code' code readable executable
...
        invoke  NtQuerySystemInformation, GET_SYSTEM_TIME_INFORMATION,\
                SystemTime, sizeof.SYSTEM_TIME_INFORMATION, 0
        ; Получить текущее системное время. Для получения времени старта
        ; системы замените в вызове SystemTime.liKeSystemTime на
        ; SystemTime.liKeBootTime
        invoke  FileTimeToLocalFileTime, SystemTime.liKeSystemTime,\
                ftSystemTime
        ; Преобразовать в системный формат даты и времени
        invoke  FileTimeToSystemTime, ftSystemTime, stSystemTime
 
        ; Теперь к элементам структуры stSystemTime можно обращаться
        ; обычными способами: [stSystemTime.wDay], [stSystemTime.wMonth] и т.д.
...

После выполнения этого кода структура stSystemTime (стандартная структура SYSTEMTIME) содержит все значения в удобном для доступа и обработки виде.

Добавлено: 10 Апреля 2018 08:32:20 Добавил: Андрей Ковальчук