News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

How to Call StringCchCat in Win64 Assembly?

Started by wanker742126, October 30, 2024, 07:30:05 PM

Previous topic - Next topic

ognil

The same but shorter... :smiley: 

include  \masm64\include64\masm64rt.inc

.data
szTitle DB      "Test StringCchCat",0
str1    DB      "I learn ",40h DUP (0)
str2    DB      "assembly language.",0

.code
main    proc
        lea    r8,str1
        lea    r9,str2
@@: 
        cmp    byte ptr[r8],0 
        lea    r8,[r8+1] 
        jne    @b
@@:
        mov    al,byte ptr[r9]
        add    r9,1
        mov    byte ptr[r8-1],al 
        add    r8,1
        test    al,al
        jne    @b
        invoke  MessageBox,0,addr str1,addr szTitle,MB_OK
        call    ExitProcess
main    endp
end
"Not keeping emotions under control is another type of mental distortion."

wanker742126

Hi, ognil:
The reason I called StringCchCatA was to avoid buffer overflow security issues, and since strsafe.lib wasn't available, I raised this question. Thank you for your concise and elegant code.

zedd151

Quote from: wanker742126 on October 31, 2024, 09:12:01 AMPlease give me some advice for improvement...
* Keep it simple  :biggrin: . Even the legacy API's will work fine for such a simple example.
A large buffer and a simple test of the concatenated string lengths against the buffer size will prevent buffer overflow. Keep in mind that the buffer needs to be at least one byte (assuming ascii strings) longer than the length of the concatenated strings for proper zero termination of the buffer. I should have wrote that into this code, my bad.  :tongue:

include \masm64\include64\masm64rt.inc

.data

    szTitle db "Test lstrcat", 0
    buffer  db 256 dup (0)
   
    str1 db "I learn ", 0
    str2 db "assembly language.", 0
    slen dd 0
   
.code

main    proc
        invoke lstrlen, addr str1  ;; get length of str1
        mov slen, eax
        invoke lstrlen, addr str2  ;;  add length of str2
        add eax, slen
        cmp eax, sizeof buffer      ;; compare length with buffer size
        jg buffererror              ;; if length of strings greater than buffer size, show error message
       
        invoke lstrcpy, addr buffer, addr str1
        invoke lstrcat, addr buffer, addr str2
        invoke MessageBox, 0, addr buffer, addr szTitle, MB_OK
        jmp xit
      buffererror:
        fn MessageBox, 0, "buffer overflow", addr szTitle, 0
      xit:
        invoke ExitProcess, 0
main    endp
end

Just use the included batch file to build this example.  :smiley:
Edit: I just noticed that I used "eax" here, which should be fine in this context... the buffer size is well below the limits of eax.  :mrgreen:  Others may find that objectionable.   :tongue:

wanker742126

zedd151:

Indeed, I got a bit fixated on using StringCchCatA. Your code certainly prevents buffer overflow, and it's simple and clear. However, during my search, I also learned more about the nature of strsafe.lib and how to call StringCchCatA and other StringCch* functions.

zedd151

It's all good. More knowledge is always a good thing. But I like the more simple solutions myself.

Happy coding.  :thumbsup:

wanker742126

Who doesn't love simple solutions? It's just that multiple books mention, and even MSDN points out, that functions like lstrlen, lstrcpy, wcscat, etc., have security concerns, recommending the use of StringCch* functions instead. This sparked my curiosity. In fact, I've traced StringCchCatA, and it essentially just checks that the buffer size is greater than the length of the two strings being concatenated.

zedd151

I just used lstrlen lstrcpy and lstrcat for demonstration of a simple approach to the problem, using widely available API's.
Normally I would "roll my own" algorithms to do the same functionality, and ognil also showed some of that type of code (replacing functionality of lstrcat). I would put that code into its own procedure though, for reusability in other programs.  :smiley:




wanker742126

I learned from this experience that "blindly trusting books can be worse than having no books at all." :biggrin:

TimoVJL

Have to know context, like using untrusted data.
MSDN don't tell all details of functions.
May the source be with you

zedd151

Quote from: zedd151 on November 01, 2024, 02:21:35 AMI just used lstrlen lstrcpy and lstrcat for demonstration of a simple approach to the problem, using widely available API's.
Normally I would "roll my own" algorithms to do the same functionality ... I would put that code into its own procedure though, for reusability in other programs.  :smiley:

Here is an example of "self rolled" functions to get string length, concatenate two strings, copy string to a buffer:

stringcat proc dst:qword, src:qword  ;; rcx = dst (destination), first argument  ;; rcx = src  (source), second argument
    dec rcx
  findend:
    inc rcx
    cmp byte ptr [rcx], 0
    jnz findend
    dec rdx
    dec rcx
  copyit:
    inc rdx
    inc rcx
    mov al, [rdx]
    mov [rcx], al
    cmp byte ptr [rdx], 0
    jnz copyit
    mov byte ptr [rcx], 0  ;; ensure zero termination.
    ret
stringcat endp

stringcpy proc dst:qword, src:qword  ;; rcx = dst (destination), first argument  ;; rdx = src  (source), second argument
    dec rdx
    dec rcx
  copyit:
    inc rdx
    inc rcx
    mov al, [rdx]
    mov [rcx], al
    cmp byte ptr [rdx], 0
    jnz copyit
    mov byte ptr [rcx], 0  ;; ensure zero termination.
    ret
stringcpy endp

stringlen proc src:qword  ;; rcx = src (source), first and only argument
    dec rcx
    xor rax, rax
    dec rax
  countbyte:
    inc rax
    inc rcx
    cmp byte ptr [rcx], 0
    jnz countbyte
    ret
stringlen endp

Full source for attached example:      You cannot view this attachment.

include \masm64\include64\masm64rt.inc

.const
    str1        db "Hello", 0
    strspace    db " ", 0
    str2        db "World!", 0Dh, 0Ah, 0
    str3        db "64 bit assembly example", 0
    strerror    db "Buffer overflow", 0
.data
    str1len     dq 0
    spacelen    dq 0
    str2len     dq 0
    buffer      db 128 dup (0)
   
.code

start proc
    invoke stringlen, addr str1
    mov str1len, rax            ;; length of str1
    invoke stringlen, addr strspace
    mov spacelen, rax            ;; length of strspace
    invoke stringlen, addr str2
    mov str2len, rax            ;; length of str2
    invoke stringlen, addr str3 ;; length of str3 (in rax after call, not in a variable)
   
    add rax, str1len          
    add rax, spacelen          
    add rax, str2len            ;; add all the string lengths
    cmp rax, sizeof buffer      ;; compare to buffer size
    jge buffererr               ;; jump if greater or equal to display error if failed

    invoke stringcpy, addr buffer, addr str1  ;; else display message box with all strings concatenated
    invoke stringcat, addr buffer, addr strspace
    invoke stringcat, addr buffer, addr str2
    invoke stringcat, addr buffer, addr str3
    fn MessageBox, 0, addr buffer, 0, 0
   
    jmp xit                      ;; jump over error message if succesful
  buffererr:
    invoke MessageBox, 0, addr strerror, 0, 0


  xit:
    invoke ExitProcess, 0
start endp

stringcat proc dst:qword, src:qword  ;; rcx = dst (destination), first argument  ;; rcx = src  (source), second argument
    dec rcx
  findend:
    inc rcx
    cmp byte ptr [rcx], 0
    jnz findend
    dec rdx
    dec rcx
  copyit:
    inc rdx
    inc rcx
    mov al, [rdx]
    mov [rcx], al
    cmp byte ptr [rdx], 0
    jnz copyit
    mov byte ptr [rcx], 0  ;; ensure zero termination.
    ret
stringcat endp

stringcpy proc dst:qword, src:qword  ;; rcx = dst (destination), first argument  ;; rdx = src  (source), second argument
    dec rdx
    dec rcx
  copyit:
    inc rdx
    inc rcx
    mov al, [rdx]
    mov [rcx], al
    cmp byte ptr [rdx], 0
    jnz copyit
    mov byte ptr [rcx], 0  ;; ensure zero termination.
    ret
stringcpy endp

stringlen proc src:qword  ;; rcx = src (source), first and only argument
    dec rcx
    xor rax, rax
    dec rax
  countbyte:
    inc rax
    inc rcx
    cmp byte ptr [rcx], 0
    jnz countbyte
    ret
stringlen endp

end

edit: added code to ensure zero termination in the 'stringcpy' and 'stringcat ' functions

sinsi

Slightly optimised...just as an exercise :biggrin:
stringcat proc dst:qword, src:qword  ;; rcx = dst (destination), first argument  ;; rcx = src  (source), second argument
@0: inc rcx
cmp byte ptr [rcx-1],0
jnz @0
@1: mov al,[rdx]
        mov [rcx-1],al
inc rdx
inc rcx
test al,al
jnz @1
        ret
stringcat endp

stringcpy proc dst:qword, src:qword  ;; rcx = dst (destination), first argument  ;; rdx = src  (source), second argument
@0:     mov al,[rdx]
        mov [rcx],al
inc rdx
inc rcx
        test al,al
jnz @0
        ret
stringcpy endp

stringlen proc src:qword  ;; rcx = src (source), first and only argument
or rax,-1
@@: inc rax
cmp byte ptr [rcx+rax],0
jnz @b
@@: ret
stringlen endp
Since the procs don't call any other proc you could turn off frame generation, but I'm not sure how to disable then enable it with the MASM64 SDK  :sad:

zedd151

Hi sinsi, yes the algos I presented could be optimized, but that would not be Campus material. Better suited for the Laboratory imo.  :tongue:

Also you should ensure zero termination in both stringcat and stringcpy (by placing a zero byte when finished there) since the buffer may already have data in it from a possible previous use of the buffer. correction, my mistake.  :tongue:

:biggrin:

sinsi

Quote from: zedd151 on November 01, 2024, 04:46:16 AMHi sinsi, yes the algos I presented could be optimized, but that would not be Campus material. Better suited for the Laboratory.  :tongue:
The Campus is for learning, maybe someone can learn from this?

Quote from: zedd151 on November 01, 2024, 04:46:16 AMAlso you should ensure zero termination in both stringcat and stringcpy (by placing a zero byte when finished there) since the buffer may already have data in it from a possible previous use of the buffer.  :icon_idea:
All characters are copied, even the terminating NULL, then end-of-string is tested for.

zedd151

Oh, ok. I only did a quick perusal at the code. Apologies.  :cool:
Quote from: sinsi on November 01, 2024, 04:50:02 AMAll characters are copied, even the terminating NULL, then end-of-string is tested for.
Clever indeed.  I just tested your algos in the program using a "full" buffer...  :biggrin:  Works 100% as it should.  :thumbsup:
Yes, someone could learn from your example.  :smiley:  My critique was only a suggestion.

ognil

#29
One more.. :smiley:

include \masm64\include64\masm64rt.inc
extern  malloc:proc
extern  free:proc

.data
hMemo   DQ      0 
szTitle DB      "Test StringCchCat",0
str1    DB      "I learn ",0
nLen1   equ     $-str1-1
str2    DB      "assembly language.",0
nLen2   equ     $-str2-1

.code
main    proc
        mov     eax,nLen1
        mov     ecx,nLen2
        lea     rcx,[rcx+rax+32]
        call    malloc
        mov     rcx,rax
        jrcxz   @ErrMemory
        mov     r8d,nLen1 
        lea     rdx,str1
        mov     hMemo, rax
        lea     r9,[rcx+r8]
        call    memcpyA   
        mov     rcx, r9
        mov     r8d,nLen2 
        lea     rdx,str2
        call    memcpyA   
        invoke  MessageBox,0,hMemo,addr szTitle,MB_OK
        ;mov    rcx,hMemo
        ;call   free
@ErrMemory:
        call    ExitProcess
main    endp
;*******************************************************************;
memcpyA proc  
push rsi
push rdi
cld
mov rdi,rcx
mov rcx,r8
mov rsi,rdx
shr rcx, 3
rep movsq
mov rcx,r8
and rcx, 7
rep movsb
pop rdi
pop rsi
ret
memcpyA endp
end
"Not keeping emotions under control is another type of mental distortion."