flat assembler
Message board for the users of flat assembler.
Index
> Windows > NOOB Alert! - Trying to understand the basics Goto page Previous 1, 2, 3 |
Author |
|
macomics 13 Sep 2021, 03:24
In fact, it is just necessary to strive to use the processor registers as often as possible. You cannot directly put a value from another global variable into a global variable. Moreover, in the declaration
Code: l_hInstance dq [GetModuleHandle] Code: l_hInstance dq $ - rva $ Code: lea rax, [$] xor ax, ax avidichard wrote: As I understood with the pure assembly code you provided me, I understood that the invoke macro actually executes the function and returns it's value inside rax. Code: invoke func, arg0, arg1, arg2, arg3, arg4, arg5 Code: sub rsp, 48 mov [rsp+40], arg5 mov [rsp+32], arg4 mov r9, arg3 mov r8, arg2 mov rdx, arg1 mov rcx, arg0 call func add rsp, 48 Code: wndProc: ... xor rax, rax ; <- result lea rsp, [rbp-24] pop rdi pop rsi pop rbx retn avidichard wrote: Of course, I'm in the basics because I think the program does interpret db, dd and dq and such operands to identify if the next line is required to be executed such as the 2 last lines which contains the rva alignment but the one after is another variable identifier so the program automatically stops executing after the rva alignement. Code: <label_name> directive value0<,value1,...,valueM,>\ <valueM+1,...,valueN,>\ <lastValue> directive - one of the data definition directives: (1 byte) file, db,rb (unicode characters, 2 bytes)du (2 bytes) dw, rw (4 bytes) dd, rd (6 bytes) df,dp,rf,rp (8 bytes) dq,rq (10 bytes) dt,rt and a set of values added to the output file of the program (written to the EXE file). The values in triangular brackets are optional, but at least one must be present. Also, in the db and du directives, the string can be present instead of the value avidichard wrote: I also just need to have my answer to this question: Code: push ebp mov ebp, esp push mess call [printf] mov esp, ebp push 0 call [exit] ; mov esp, ebp ; But the program will not return from the exit function ; pop ebp avidichard wrote: You seem to opt more with the push and mov links than the simple "push mess" I use. Is there a reason for going the more complexe way? I'm just asking because I find it much easier to use "push mess" rather than using "push ebp", "mov ebp, esp", "sub esp, 4". I also wondered why use "sub esp, 4". Can I do 8, 16 or 32 or 1? What's so important about that number 4? https://software.intel.com/content/www/us/en/develop/articles/intel-sdm.html#combined 5.10.5 Checking Alignment wrote:
6.2.2 Stack Alignment wrote: The stack pointer for a stack segment should be aligned on 16-bit (word) or 32-bit (double-word) boundaries, depending on the width of the stack segment. The D flag in the segment descriptor for the current code segment sets the stack-segment width (see “Segment Descriptors” ... ). The PUSH and POP instructions Last edited by macomics on 13 Sep 2021, 11:38; edited 1 time in total |
|||
13 Sep 2021, 03:24 |
|
macomics 13 Sep 2021, 03:42
In order not to deal with the code using the tuck method, but to clearly see what is happening, take the debugger program (32-bit-OllyDbg or 64-bit-x64dbg)
https://www.ollydbg.de/download.htm https://x64dbg.com/#start |
|||
13 Sep 2021, 03:42 |
|
avidichard 14 Sep 2021, 03:04
macomics wrote: In fact, it is just necessary to strive to use the processor registers as often as possible. You cannot directly put a value from another global variable into a global variable. Moreover, in the declaration I don't understand what you mean about those lines of code. Where would you put them? Which lines would they replace in the snipets I initially provided? Also, as I understand, push eax and mov eax would be recommended instead of the push mess just to get used to always initialise a space in memory for the information we're about to handle. Meaning, it works, but it's better to start off with the right practices for futur proofing our programming. That's what I seem to understand from what you say. Again, I have no idea where where these would go in the example you provided. Code: lea rax, [$] xor ax, ax I also was questioning as to why use rbp and rsp in the "Hello, world!" command line example instead of eax such as: Code: push eax mov eax, mess call [printf] Is it because console PE uses different than PE GUI? As for the RVAs, yes, I had forgoten to put them back. I corrected my code. |
|||
14 Sep 2021, 03:04 |
|
macomics 14 Sep 2021, 06:09
avidichard wrote: I don't understand what you mean about those lines of code. Where would you put them? Which lines would they replace in the snipets I initially provided? Code: format PE ... ; or PE64 ... entry entry_point section '.code' ... entry_point: anywhere: ; first option ; for PE 64 lea eax, [entry_point] ; lea rax, [entry_point] I use the label entry_point because it is closest to the beginning of the file. xor ax, ax ; xor ax, ax If the label is further than 64 kb from the beginning of the file, you will have to use a longer command: ; sub eax, rva entry_point ; sub rax, rva entry_point section '.data' ... ; second option ; for PE 64 l_hInstance dd $ - rva $ ; or l_hInstance dq $ - rva $ ; the disadvantage is that you will get in l_hInstance not the real address, but the one that was used ; as the default value (or specified in the directive: format ... at hInstance_address ) section '.idata' ... include 'INCLUDE/MACRO/IMPORT32.INC' ; include 'INCLUDE/MACRO/IMPORT64.INC' library ... import ... import ... ... import ... ; the following lines are only for the DLL (32-bit), but then you can only use the first method (with commands) section '.reloc' fixups data readable discardable if $$=$ dd 8,0 end if ; for PE64, the first method calculates the label address relative to the current address in (rip) and it no longer needs fixes avidichard wrote: Also, as I understand, push eax and mov eax would be recommended instead of the push mess just to get used to always initialise a space in memory for the information we're about to handle. Meaning, it works, but it's better to start off with the right practices for futur proofing our programming. That's what I seem to understand from what you say. As for 32-bit code, you'd better start working with stack only with the commands push <any>/pop <any> and mov ebp, esp/mov esp, ebp. Then, with a high degree of probability, you will not violate the stack alignment, but you will not think about this problem either. And when you gain the necessary experience, you will choose the options for working with stack yourself. It is more difficult with 64-bit code, because it is impossible to do without taking into account the alignment of stack addresses. It is necessary to make sure that the value in the rsp is always a multiple of 16, but the push/pop commands work with half the required amount of data. Therefore, you have to monitor the rsp value and you can't do without it. But when using macro instructions, this problem disappears because they will monitor the alignment themselves, and you will not have to think about it. Otherwise, it is also better to start working with the stack in 64-bit mode by using the commands push <any>/pop <any> and mov rbp, rsp/mov rsp, rbp. Then you will have a minimal chance of breaking something. avidichard wrote: Again, I have no idea where where these would go in the example you provided. avidichard wrote: I also was questioning as to why use rbp and rsp in the "Hello, world!" command line example instead of eax such as: Because you can only use them in 64-bit programs, and this piece of code apparently refers to a 32-bit program. But when calling this function, the stack will not be cleared of the value that will be put in it by the command push eax. Therefore, for this particular example, there are two ways to solve this problem. Due to the fact that the value is one, it is possible to use just one additional command (pop edx, the register does not play a role here, but in eax-the result and it is better not to rewrite it). Code: push mess call [printf] pop edx However, this function may have more than one parameter, depending on the format string (mess). Then the second solution will be more universal. Code: ; !!!only 32-bit mode!!! push ebp ; now [esp] = old ebp <- saving the old ebp value mov ebp, esp ; saving the current value of the stack top push 0 ; now [esp] = 0, [esp+4] = old ebp push 1 ; now [esp] = 1, [esp+4] = 0, [esp+8] = old ebp push 2 ; now [esp] = 2, [esp+4] = 1, [esp+8] = 0, [esp+12] = old ebp push 3 ; now [esp] = 3, [esp+4] = 2, [esp+8] = 1, [esp+12] = 0, [esp+16] = old ebp push mess ; now [esp] = mess, [esp+4] = 3, [esp+8] = 2, [esp+12] = 1, [esp+16] = 0, [esp+20] = old ebp call [printf] ; now [esp] = returned_here, [esp+4] = mess, [esp+8] = 3, [esp+12] = 2, [esp+16] = 1, [esp+20] = 0, [esp+24] = old ebp returned_here: ; now [esp] = mess, [esp+4] = 3, [esp+8] = 2, [esp+12] = 1, [esp+16] = 0, [esp+20] = old ebp mov esp, ebp ; now [esp] = old ebp pop ebp ; now esp clear, ebp will be restored ; these are the features of the type of function being called ; most functions of the msvcrt library have similar features ; However: push 0 ; now [esp] = 0 push 0 ; now [esp] = 0, [esp+4] = 0 push 0 ; now [esp] = 0, [esp+4] = 0, [esp + 8] = 0 push msg ; now [esp] = msg, [esp+4] = 0, [esp+8] = 0, [esp+12] = 0 call [GetMessage] ; now [esp] = second_return, [esp+4] = msg, [esp+8] = 0, [esp+12] = 0, [esp+16] = 0 second_return: ; now esp clear ; For Windows API functions, a different type of call is usually used. And you don't need to clear the stack behind them. avidichard wrote: As for the RVAs, yes, I had forgoten to put them back. I corrected my code. While you are dealing with the basics of assembly language, it is better to leave the automatic tools that relate to the PE file format. Moreover, when using macros, listing the entire list of functions in all the libraries you know will not lead to the creation of a huge structure. It will leave only those libraries and functions that you use in your code. Whereas once compiled such a list will save you from constantly digging in various sources to list the names of functions you need. |
|||
14 Sep 2021, 06:09 |
|
avidichard 14 Sep 2021, 07:17
My days are from the VB6 era. I had to manually put in modules the constants, structs and functions. They were not supplied for me. The problem with the included files using the "include" command is that it includes the entire file with a bunch of stuff I won't even be using. For instance, one example I got simply included the kernel32 functions but still only used the msvcrt dll. That was stupid for me. I don't need kernel32 when all I want to do is write a single string in command line.
So I am used to import and write things manually and YES, from time to time, forget about a thing or 2. But that's how I learn and program. I don't like to use prebuilt stuff just because it's easy. That's the exact reason I moved from other programming. AT the limit, I'll even want to know how the program compiles and converts ASM to binary. After all, that's what FASM does. What if I wanted to get rid of FASM and simply have it my way? That's the entire freedom of learning assembly without depending on macros. Yes, I know it's a very hard, long path. But in the end, it provides freedom. No more dependecies on .Net frameworks or other dependencies other than the necessary to run apps on Windows such as using msvcrt for console because nowdays, 16 bit is not supported without an emulator of some sort. The other thing I read was that eax was for 32 bits and rax for 64 bits or "e$$" for 32 and "r$$" for 64. Code: push eax mov eax, mess call [printf] macomics wrote: Because you can only use them in 64-bit programs, and this piece of code apparently refers to a 32-bit program. I know I kind of messed a bit the console 32 bit and the Windows 64 bits. But the idea I am trying to make is to see something that carries on to the next. Of course eax, rbp, rcx, etc will have to be used properly depening on the environement we are programming in. When you look at the low end of things, you initially had to defin your starting address at the beginning of your code then work your way down by playing with your stacks. I just want to find a universal (kind-of) way to always properly code without depending on macros. For example: 1. Reserve memory 2. Set data in memory 3. Use data 4. Clear memory of unused data I guess this is pretty much universal. Then depending on your 32/64, you may need to align and add additional steps in between them for example, step: 2-a: Align stack for 64 bits But I expect to do that on my own and not need someone to walk me through it. All I am asking is what is the basics of the 4 steps I initially wrote because clearly THIS, to me, is not normal: Code: ; SIMPLE form ; Print's the message on screen push mess call [printf] push 0 ; Exit the program call [exit] ; OTHER POSSIBILITY with more lines ; Print's the message on screen push ebp mov ebp, esp sub esp, 4 mov dword[esp], mess call [printf] mov dword[esp], 0 ; Exit the program call [exit] Why would I chose the simplest form if the later form is a better practice? AND, as I did not receive an answer yet for this question: Where does ebp come from? WHY use ebp/esp and not ax or eax or any other things that can be pushed with the same supported data? Also, this, in your example: Code: call [GetMessage] ; now [esp] = second_return, [esp+4] = msg, [esp+8] = 0, [esp+12] = 0, [esp+16] = 0 second_return: ; now esp clear To me, a label is nothing more than a pointer to help us put logics in our programming or jump to when needed. BUT as you are stating, the label seems to play a role in memory/stack clearing for some weird reason. How can the label "second_return" clear esp if there is no code after it to clear the said esp? Yes, ASM is low level programming, but there needs to be some logics somewhere. In other example, you cleared rax with "xor rax, rax", other examples will clear with "push 0" and now, we can clear using a simple label. That's what's messing my mind up. I understand the stack thing, although I see it as a string and a position inside a string as a table with rows, but that's just for my own comprehension. It's easier for me to understand: ; String style push 0 ; Creates "_ _ _ _" rather than: ; Stack style push 0 ; creates ; _ ; _ ; _ ; _ I'm sorry if my noob questions look so basic, but there's weird inconsistencies between what I've read elsewhere and what is being said here so I just want to clear my head up and understand how to make things work in the good way to write it the right way and not just go for the easy way out because everything is done for you. I'm proud that because of this thread, I have been able to understand how to program a simple console program and able to create a Windows program in x64. All I want to know is now how to integrate other Window API calls to add buttons and lables and other stuff in my window (I'll figure that out by myself). I'm just trying to find the basics here and see how I can interact with a user in console and transform it to make it more complex to make it interact in Windows. I was also wondering if my message length is important and if I can overflow the allocated space for the stirng to be stored in a register and thus, crash the program. I added a "Press any key to continue." message and nothing bad happened and the porgram executes without any problem and all I did was add text so I'm wondering if we also need to specify how much space in memory wee need to allocate for the string to fit because in some placed, we need to do this "str_len equ $ - mess" and if it's a good practice to allocate this space, how would I go upon using this in the console example we have in this thread? |
|||
14 Sep 2021, 07:17 |
|
macomics 14 Sep 2021, 09:46
avidichard wrote: For instance, one example I got simply included the kernel32 functions but still only used the msvcrt dll. That was stupid for me. I don't need kernel32 when all I want to do is write a single string in command line. Code: ; All the functions that I have ever used section '.idata' import data readable writeable include "INCLUDE/MACRO/IMPORT", CODE_MODE, ".INC" ; This file contains only 2 macros library kernel32.dll, "kernel32.dll",\ user32.dll, "user32.dll",\ gdi32.dll, "gdi32.dll",\ msvcrt.dll, "msvcrt.dll" import kernel32.dll,\ ExitProcess, "ExitProcess",\ GetModuleHandle, "GetModuleHandleA" import user32.dll,\ CreateWindowEx, "CreateWindowExA",\ DefWindowProc, "DefWindowProcA",\ DispatchMessage, "DispatchMessageA",\ GetMessage, "GetMessageA",\ LoadCursor, "LoadCursorA",\ LoadIcon, "LoadIconA",\ PostQuitMessage, "PostQuitMessage",\ RegisterClassEx, "RegisterClassExA",\ TranslateMessage, "TranslateMessage" import gdi32.dll,\ CreateSolidBrush, "CreateSolidBrush" import msvcrt.dll,\ exit, "exit",\ printf, "printf" and in your project Code: format ... CODE_MODE equ "32" ; CODE_MODE equ "64" include "my_include.inc" ... avidichard wrote: So I am used to import and write things manually and YES, from time to time, forget about a thing or 2. But that's how I learn and program. I don't like to use prebuilt stuff just because it's easy. That's the exact reason I moved from other programming. AT the limit, I'll even want to know how the program compiles and converts ASM to binary. After all, that's what FASM does. What if I wanted to get rid of FASM and simply have it my way? That's the entire freedom of learning assembly without depending on macros. see https://flatassembler.net/docs.php?article=win32 https://flatassembler.net/docs.php?article=manual#2.1 avidichard wrote: Why would I chose the simplest form if the later form is a better practice? This is the beauty of assembler. There is no universal way to do everything right for all cases without exception. At the moment, there is no great harm if you do something wrong. But in the window program there is a message processing cycle that must perform an infinite number of iterations for hours (just by the way). And if each iteration of the loop will lose a piece of the stack, then you may not quickly detect it. 64 kb of stack can leak very slowly because the message queue of the program will not be constantly filled. And the passes of this ~16000 cycle passes will be completed in 1-2 hours. At the same time, the memory will leak and someday the program will give an error. Therefore, it is important to approach the call of each specific function very carefully. You will have to monitor the state of the stack and the safety of all processor registers without exception in each specific operation. Perhaps you had to calculate something and the result of the calculation is in the edx register, then you will have to save this value somewhere before calling the function. Otherwise, if you need it after calling the function, you will lose it. avidichard wrote: AND, as I did not receive an answer yet for this question: Where does ebp come from? WHY use ebp/esp and not ax or eax or any other things that can be pushed with the same supported data? Although when organizing your internal call types, no one restricts you to these rules. They are only needed for organizing inter-module interaction (calls to external functions). avidichard wrote: How can the label "second_return" clear esp if there is no code after it to clear the said esp? avidichard wrote: I'm sorry if my noob questions look so basic, but there's weird inconsistencies between what I've read elsewhere and what is being said here so I just want to clear my head up and understand how to make things work in the good way to write it the right way and not just go for the easy way out because everything is done for you. Code: ; in eax = Class style ; in edx = lpfnWndProc ; in ebx = lpszClassName ; out ax = hAtom or NULL RegisterMyClasses: push ebp mov ebp, esp sub esp, 48 ; sizeof.WNDCLASSEX mov dword [esp], 48 ; wcx.cbSize mov dword [esp+4], eax ; wcx.style mov dword [esp+8], edx ; wcx.lpfnWndProc mov dword [esp+12], 0 ; wcx.cbClsExtra mov dword [esp+16], 0 ; ecx.cbWndExtra mov dword [esp+20], entry_point push 32512 ; IDI_APPLICATION push 0 call [LoadIconW] mov word [esp+20], 0 ; hInsance fixup mov dword [esp+24], eax ; wcx.hIcon mov dword [esp+44], eax ; wcx.hIconSm push 32512 ; IDC_ARROW push 0 call [LoadCursorW] mov dword [esp+28], eax ; wcx.hCursor mov dword [esp+32], 17 ; wcx.hbrBackground = COLOR_WINDOWFRAME mov dword [esp+36], 0 ; wcx.lpszMenuName mov dword [esp+40], ebx ; wcx. lpszClassName push esp call [RegisterClassExW] mov esp, ebp pop ebp retn ; For clarity, I replaced everything with mov commands and commented, but with the use of macros, it looks like this: ; func RegClassEx, cdecl, rsi ; begin ; shl rax, 32 ; xor rcx, rcx ; lea rax, [rax+80] ; add rsp, 32 ; My macros prepare the stack for external calls automatically, so I have to take this place in the stack into account. 64-bit eh. ; push rcx rbx rcx COLOR_WINDOWFRAME rcx rcx rcx rcx rdx rax ; mov rsi, rsp ; sub rsp, 32 ; call API LoadIcon, rcx, IDI_APPLICATION ; or rax, rax ; jz @f ; lea rdx, [entry_point] ; mov [rsi+40], rax ; xor dx, dx ; mov [rsi+72], rax ; mov [rsi+32], rdx ; call API LoadCursor, NULL, IDC_CURSOR ; or rax, rax ; jz @f ; mov [rsi+48], rax ; call API RegisterClassEx, rsi ; @@: ; return DONE avidichard wrote: "str_len equ $ - mess" and if it's a good practice to allocate this space, how would I go upon using this in the console example we have in this thread? Code: mess db "My message", 0 correct_address: str_len equ $ - mess ; saved: str_len is "$ - mess" str_len2 = $ - mess ; calculated: str_len2 is 11 ... that_address: push str_len ; push $ - mess, but $ = that_address instead of correct_address push str_len2 ; push 11 Because str_len2 will be calculated at the moment it appears in the text ADD: Try that Code: push mess call [printf] . . . mess file "name_of_that_file.asm" db 0 |
|||
14 Sep 2021, 09:46 |
|
Goto page Previous 1, 2, 3 < Last Thread | Next Thread > |
Forum Rules:
|
Copyright © 1999-2025, Tomasz Grysztar. Also on GitHub, YouTube.
Website powered by rwasa.