Reversing

memcpy 분석

한희수 2024. 7. 9. 13:52

`memcpy`는 메모리 영역을 복사하는 C 언어의 표준 라이브러리 함수이다.

#include <string.h>
#include <sys/types.h>
#if !_ASM_MEMCPY
typedef long word;
#define lsize sizeof(word)
#define lmask (lsize - 1)
void *memcpy(void *dest, const void *src, size_t count) {
    char *d = (char *)dest;
    const char *s = (const char *)src;
    int len;
    if (count == 0 || dest == src)
        return dest;
    if (((long)d | (long)s) & lmask) {
        // src and/or dest do not align on word boundary
        if ((((long)d ^ (long)s) & lmask) || (count < lsize))
            len = count; // copy the rest of the buffer with the byte mover
        else
            len = lsize - ((long)d & lmask); // move the ptrs up to a word boundary
        count -= len;
        for (; len > 0; len--)
            *d++ = *s++;
    }
    for (len = count / lsize; len > 0; len--) {
        *(word *)d = *(word *)s;
        d += lsize;
        s += lsize;
    }
    for (len = count & lmask; len > 0; len--)
        *d++ = *s++;
    return dest;
}
#endif

 

if (count == 0 || dest == src)
        return dest;
  • `count`가 0이거나 `dest`와 `src`가 같은 주소를 가리키면 그대로 `dest`를 반환해 준다.
    if (((long)d | (long)s) & lmask) {
        // src and/or dest do not align on word boundary
        if ((((long)d ^ (long)s) & lmask) || (count < lsize))
            len = count; // copy the rest of the buffer with the byte mover
        else
            len = lsize - ((long)d & lmask); // move the ptrs up to a word boundary
        count -= len;
        for (; len > 0; len--)
            *d++ = *s++;
    }
  • `if (((long)d | (long)s) & lmask) {`
    • `d`와 `s` 포인터가 word 경계에 맞지 않을 경우를 체크하는 용도
    • `lmask`는 word 경계를 나타내는 비트 마스크로 3-word의 경계를 맞추기 위해 `0b11` 값으로 설정
  • `if ((((long)d ^ (long)s) & lmask) || (count < lsize))`
    • word 경계에 맞지 않거나, 복사할 바이트 수 `count`가 `lsize`보다 작을 경우를 처리
    • `lsize`는 한 번에 복사할 수 있는 최대 바이트 수를 나타내는 변수
  • `len = lsize - ((long)d & lmask);`
    • `(long)d & lmask` : 목적지 포인터 `d`를 `lmask`와 비트 AND 연산을 수행하여 `d`를 `lmask`에 정의된 word 경계에 맞게 정렬
    • `lsize - ((long)d & lmask)` : 정렬된 값을 `lsize`에서 빼서 현재 포인터 `d`가 word 경계로 정렬되기까지 필요한 바이트 수를 계산
  • `for(; len > 0; len--)`
    • `len`은 복사할 바이트 수로 설정되어 있으며, 이를 기반으로 `s`에서 `d`로 메모리를 복사

 

for (len = count / lsize; len > 0; len--) {
        *(word *)d = *(word *)s;
        d += lsize;
        s += lsize;
    }
  • `for (len = count / lsize; len > 0; len--) {`
    • 초기에 `count`를 `lsize`로 나눈 몫을 `len`에 할당하여 총 복사할 word의 개수를 표시
    • word 단위로 메모리 복사
for (len = count & lmask; len > 0; len--)
        *d++ = *s++;
  • `for (len = count & lmask; len > 0; len--)` 
    • `count`와 `lmask`를 비트 단위 AND 연산하여 `len`을 결정하는데 이는 `count`를 `lmask`로 나눈 나머지를 구하는 것과 같다.

 

#include <stdio.h>
#include <string.h>

int main() {
    char src[] = "Hello!";
    char dest[50];

    // Using memcpy to copy src to dest
    memcpy(dest, src, strlen(src) + 1);

    // Printing the copied string
    printf("Copied string: %s\n", dest);

    return 0;
}

 

위는 간단한 `memcpy` 예시이다.

 

`memcpy`를 실행하는 부분에 bp를 걸고 확인한다.

 

  • `rsi`에 `src`의 주소가 들어간다.
  • `rdi`에 `dest`의 주소가 들어간다.
  • `rdx`에 입력할 size가 들어간다.

 

`memcpy`는 내부에서 `__memmove_evex_unaligned_erms`를 호출한다. 

`__memmove_evex_unaligned_erms`는 glibc에서 제공하는 메모리 복사 함수의 내부적인 구현 중 하나이다.

 

해당 부분이 메모리를 복사하는 과정인 것 같다.

`src`의 주소가 있는 `rsi`를 `dest`의 주소가 있는 `rdi`에 넣는 과정이다.

이때, `rdx`의 값이 3인데, 여기서 3을 더하는 이유는 아마 이전에 설명한 `memcpy`가 word 단위로 복사하기 때문이 아닌가 싶다.