Version 3.
Download: winbox64_ESR_3.zip (755.3 KB)
-
Fixed invisible selection in Message field of 'Log Entry' window. Relevant to RouterOS 7.20+, where this field has become multiline. The only problem is that it selects all text, when the window becomes focused after being unfocused. (may be they've "fixed" this problem by just hiding selection...
). Will try to fix it later in a normal way.
-
Fixed filter value field was cleared when changing filter parameter.
-
Added ability to change the order of user@host and identity in main window title. The new setting is available under 'Settings' menu in main window. It's saved on app close and stored in
%AppData%\Roaming\MikroTik\WinBox\settings.dat, i. e. in the same place as original WinBox'ssettings.cfg.viwfile. I had no wish to figure out in its format, so added own file. It will be used to store other settings in future releases. The file is saved on app close and only if a setting was modified.
-
Fixed own bug that was causing an exception. It's not critical and unnoticable, because it was occuring only when the app was closed, but anyway, it wasn't good.
For advanced users:
Added new own .text, .rdata and .data sections to executable.
This solves many current and potential future problems:
- Existing code cave at address
0x531460is already not enough to store my own code, new.textsection removes this size limitation and can be increased as needed - Better data organization - can store strings and other constants in new
.rdatasection, no need to store them in the code - Runtime data, like addresses and some values will be stored in new
.datasection - No risk to interfere with anything (like in the issue 4)
Log Entry selection fix
The multiline message field is a standard Windows' "EDIT" class, and it's created with CreateWindowEx WinAPI function. In original, its dwClass parameter is equal to 0x40200044, i. e. it consists of these values: WS_CHILD | WS_VSCROLL | ES_AUTOVSCROLL | ES_MULTILINE. It also adds other values, depending on some conditions, but I wasn't looking into that. The parameter is calculated at address 0x4445E2 while creating the object. I've just added ES_NOHIDESEL value (0x100) and it solved the problem. So, the whole fix is just changing one bit (0x40200044 -> 0x40200144).
Filter field clear fix
There are 2 possible field types in filters - TextEdit and ComboBox. When you add a new filter, it creates both objects and hides one of them, depending on selected filter parameter. For example, if you add a new firewall filter, you see 'Action' parameter by default and a ComboBox field to choose or type a value. If you then select 'Comment' parameter, it hides ComboBox and shows TextField. But in background both object do exist, the app only changes their visibility. So, to keep the typed value, it's required not only to intercept string removal, but also to copy it between TextEdit and ComboBox, when needed.
Everything happens in the function, that is located at address 0x4566C8. It's called, when you change a filter parameter. It clears the field value just before making it visible, i. e., when the field is hidden in background, it keeps the value. Attached original code with some comments. If selected parameter requires a ComboBox, it executes the code at address 0x45675F, if it requires TextField, it executes the code at address 0x45679E. In both cases rbx register is loaded with the address of a method, that clears the value and then executed in call rbx instruction.
Some info about objects:
TextField object contains a pointer to its string value address at offset [base+60h]
ComboBox consists of the same TextField object and the button. Inside ComboBox, a pointer to TextField object is located at offset [base+48h]
I've modified 2 instructions in the code above (marked as Inject1 and Inject2) so, that rbx is loaded with the addresses of my own code, that handles moving string values between TextField and ComboBox, when needed. Original values emptying code is not executed. Below is the code itself.
// Call ComboBox clear (from Inject1)
// Need to copy value from TextEdit to ComboBox
mov rcx, [r12+50h] // Load ComboBox object pointer
call IWindow::isVisible
test al, al // If visible, then skip
jnz @End
mov rsi, [r12+48h] // Pointer to TextEdit object (source)
mov rbx, [rsi+60h] // String address in TextEdit object (source)
mov rdi, [r12+50h] // Pointer to ComboBox object (destination)
mov rdi, [rdi+48h] // Pointer to TextEdit object of ComboBox
lea rcx, [rdi+60h] // Pointer to string address in TextEdit object of ComboBox (destination)
cmp [rcx], Zero_Addr // Check if string was zeroed before replacing
je @SkipFree1
call FreeStringMem // sub_4CF0DA
@SkipFree1:
mov [rdi+60h], rbx // Move string address from TextEdit to ComboBox
mov [rsi+60h], Zero_Addr // Load zero string address to avoid app crash on objects destroy
ret
// ---
// Call TextEdit clear (from Inject2)
// Need to copy value from ComboBox to TextEdit
mov rcx, [r12+48h] // Load TextEdit object pointer
call IWindow::isVisible
test al, al // If visible, then skip
jnz @End
mov rsi, [r12+50h] // Pointer to ComboBox object (source)
mov rsi, [rsi+48h] // Pointer to TextEdit object of ComboBox
mov rbx, [rsi+60h] // String address in TextEdit object of ComboBox (source)
mov rdi, [r12+48h] // Pointer to TextEdit object (destination)
lea rcx, [rdi+60h] // Pointer to string address in TextEdit object (destination)
cmp [rcx], Zero_Addr // Check if string was zeroed before replacing
je @SkipFree2
call FreeStringMem // sub_4CF0DA
@SkipFree2:
mov [rdi+60h], rbx // Move string address from ComboBox to TextEdit
mov [rsi+60h], Zero_Addr // Load zero string address to avoid app crash on objects destroy
@End:
ret
Swap title order mod
Main menu for main window is created in sub_4B53CA. Injected redirection to my item adding code at address 0x4B6255.
New item adding code:
mov eax, Proc_FreeString_Addr // Replaced original instruction
call rax
mov rcx, [r12+50h]
mov edx, $999
mov rax, [rcx]
call qword ptr [rax+28h] // Add separator (sub_441540)
mov r14, [r12+50h]
mov edx, ItemCaption
mov rcx, r13
mov rax, [r14]
mov rsi, [rax+18h]
mov eax, Proc_ExtractStoredString_Addr
call rax
xor r9d, r9d
mov r8, r13
mov edx, SwapTitleOrderMenuItemID
mov rcx, r14
call rsi // Add menu item
mov rcx, r13
mov eax, Proc_FreeString_Addr
call rax
mov rcx, [r12+50h]
mov edx, SwapTitleOrderMenuItemID // item ID
mov r8d, data_TitleOrder_Addr // Address of checked flag value in .data
mov r8, [r8]
and r8, 1
mov rax, [rcx]
call qword ptr [rax+38h] // Set checked state (sub_4415DA)
ret
Menu click handling for main window is performed in sub_4B41BA. Injected redirection to own item handling code at address 0x4B41F8.
New menu item click handling code:
cmp dx, SwapTitleOrderMenuItemID - 64h // DX = Menu item ID - 64h
je @ProcessSwapTitle // New menu item clicked
cmp dx, 0Fh // Original replaced code
ja 0x4B42D9
jmp 0x4B4202 // Some other item clicked, return back
@ProcessSwapTitle:
xor byte ptr [data_TitleOrder_Addr], 1 // Invert value
or qword ptr [data_ModifiedFlags_Addr], 1 // Flag as modified
mov r8b, byte ptr [data_TitleOrder_Addr] // Load checked state value
mov rcx, [r12+50h]
mov edx, SwapTitleOrderMenuItemID // item ID
mov rax, [rcx]
call qword ptr [rax+38h] // Set checked state (sub_4415DA)
mov rcx, r12
mov eax, MakeTitleString_Addr // Update window title (sub_4B46E8)
call rax
jmp 0x4B42D9 // default case
To load/save menu item state, wrote 2 functions that read/write its state to settings.dat file.
Current file format:
| Offset (bytes) | Description | Data | Data size (bytes) | Notes |
|---|---|---|---|---|
| 0h | Signature | 0xAA555354 | 4 | |
| 4h | Title order | 0 or 1 | 4 | 0 - standard order; 1 - swapped admin@host and Identity |
The file is read on app initialization in sub_4B8994. Injected redirection to reading function at address 0x4BC758.
Reading code:
mov eax, GetAppDataDirSub_Addr // call GetAppDataDir (sub_4AEF59, replaced instruction)
call rax
mov rdx, r14 // r14 contains a pointer to the address of AppData path
mov r8d, rdata_SettingsFileName_Addr
mov ecx, data_FullSettingsFileName_Addr // Address in .data to receive full file name
mov eax, ConcatStringsSub_Addr
call rax // call ConcatStringsSub (sub_4B6C4C)
mov ecx, [data_FullSettingsFileName_Addr] // Full file name string
add rcx, 4
mov edx, GENERIC_READ
mov r8d, FILE_SHARE_READ
xor r9, r9
mov dword ptr [rsp+20h], OPEN_EXISTING
mov dword ptr [rsp+28h], FILE_ATTRIBUTE_NORMAL
mov [rsp+30h], 0
call CreateFileA
mov FileHandle, rax
cmp FileHandle, INVALID_HANDLE_VALUE
je @KeepDefaultValue
@DoRead:
mov rcx, FileHandle
lea rdx, Buffer
mov r8d, 8
lea r9, BytesCount
mov [rsp+20h], 0
call ReadFile
test eax, eax // some read error
jz @CloseAndLeaveDefaultValues
cmp BytesCount, 8 // check read bytes
jne @CloseAndLeaveDefaultValues
mov rax, [Buffer]
cmp eax, SettingsFileSignature // Check signature
jne @CloseAndLeaveDefaultValues
ror rax, 32 // Swap halves
mov edx, eax
and edx, $FFFFFFFE // Check if 0 or 1, otherwise incorrect value
test edx, edx
jnz @CloseAndLeaveDefaultValues
mov ecx, data_TitleOrder_Addr // Load the value read from the file into address in .data
mov byte ptr [rcx], al
@CloseAndLeaveDefaultValues:
mov rcx, FileHandle
call CloseHandle (modify address) $C4
@KeepDefaultValue:
ret
If the file doesn't exist or contains any error, default value is loaded (standard title order).
Writing to the file occurs on app close. Placed redirection to writing code right after return from the main application loop at address 0x42DAFC.
Writing code:
cmp dword ptr [data_ModifiedFlags_Addr], 0 // Check if some parameter was modified
jz @End
mov ecx, [data_FullSettingsFileName_Addr] // Full file name kept in .data after reading on app initialization
add rcx, 4
mov edx, GENERIC_WRITE
xor r8, r8
xor r9, r9
mov dword ptr [rsp+20h], CREATE_ALWAYS
mov dword ptr [rsp+28h], FILE_ATTRIBUTE_NORMAL
mov [rsp+30h], 0
call CreateFileA
mov FileHandle, rax
cmp FileHandle, INVALID_HANDLE_VALUE
je @End
@DoWrite:
mov dword ptr [Buffer+0], SettingsFileSignature
mov ecx, [data_TitleOrder_Addr]
mov dword ptr [Buffer+4], ecx // Move title order value to buffer
mov rcx, FileHandle
lea rdx, Buffer
mov r8d, 8
lea r9, BytesCount
mov [rsp+20h], 0
call WriteFile
mov rcx, FileHandle
call CloseHandle
@End:
mov ecx, data_FullSettingsFileName_Addr // Full file name ptr
call FreeStringMem // sub_4CF0DA
mov rcx, off_53DC00 // replaced original instruction
ret
Exception bug
I've noticed, that the app was throwing an exception at address 0x4D3530 after closing. There is some cleanup happening in this place, and for some reason the cleaning code is reading the address of a code cave (0x531460) and it was looking for zero bytes, that were originally in this place. These zeros act as a condition to complete the cleanup loop. So, I just shifted all my code 8 bytes down and left first 8 zero bytes in a code cave to make cleanup code finish successfully.



