首页
社区
课程
招聘
[分享] pwnable.kr memcpy
发表于: 2021-1-21 20:04 10450

[分享] pwnable.kr memcpy

2021-1-21 20:04
10450

考察点: 32 位程序中 movntps 16 字节对齐,malloc(n) 实际分配的堆内存是 Header(4 字节) + n 结构且分配的堆块 8 字节对齐


我们通过nc 0 9022连接题目环境

首先请用户更具提示的范围输入 10 个数字。
输入之后,根据用户输入的数据使用 malloc 函数,在堆上请求大小为用户输入数据的堆块。
然后以 memcpy() 为中介计算、比较 slow_memcpy() 和 fast_memcpy() 这两种方式耗费的时间。
slow_memcpy() 使用的是循环赋值,fast_memcpy() 使用的是汇编指令 movdqa movntps 进行拷贝。
当全部10数字拷贝结束后打印flag。

首先利用源码编译二进制文件
gcc -o memcpy memcpy.c -m32 -lm
然后用普通的数据测试一下

可以看到程序停止到第五次试验,使用 gdb 调试该程序,使用相同的输入。

可以看到出错位置在 fast_memcpy() 的 movntps 汇编语句上
movntps 在 32 位程序中目标操作数是 128 位即 16 字节
C 中的 malloc(n) 实际分配的堆内存是 Header(4 字节) + n 结构,即分配 n + 4 字节的空间。返回给用户的是 n 部分的首地址。且分配的堆块 8 字节对齐

系统不同 Header 的大小可能不同,详情参考 https://git.busybox.net/uClibc/tree/libc/stdlib/malloc/malloc.h 的第 104 行

程序报错的原因是因为没有做到 16 字节对齐
写一个简单的代码,找出 10 个符合输入范围且可以做到 16 字节对齐的数字

我们所得的一串符合预期的值为8 24 40 72 136 264 520 1032 2056 4104
用计算的值带入程序
echo "8 24 40 72 136 264 520 1032 2056 4104" | ./memcpy

在本地通过全流程,在 pwnable.kr 要求的环境中测试
echo "8 24 40 72 136 264 520 1032 2056 4104" | nc 0 9022
获得 flag 1_w4nn4_br34K_th3_m3m0ry_4lignm3nt

// compiled with : gcc -o memcpy memcpy.c -m32 -lm
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>C
#include <sys/mman.h>
#include <math.h>
 
unsigned long long rdtsc(){
        asm("rdtsc");            // 将处理器的时间标签计数器的当前值加载到 EDX:EAX 寄存器。
}
 
char* slow_memcpy(char* dest, const char* src, size_t len){
    int i;
    for (i=0; i<len; i++) {
        dest[i] = src[i];
    }
    return dest;
}
 
char* fast_memcpy(char* dest, const char* src, size_t len){
    size_t i;
    // 64-byte block fast copy
    if(len >= 64){
        i = len / 64;
        len &= (64-1);
        while(i-- > 0){
            __asm__ __volatile__ (
            "movdqa (%0), %%xmm0\n"
            "movdqa 16(%0), %%xmm1\n"        // MOVDQA 目标操作数 源操作数  移动对齐的双、四字
            "movdqa 32(%0), %%xmm2\n"
            "movdqa 48(%0), %%xmm3\n"
            "movntps %%xmm0, (%1)\n"        // MOVNTPS 目标操作数 源操作数  使用非时间提示存储打包的单精度浮点值
            "movntps %%xmm1, 16(%1)\n"        // 目标操作数是 128 位或 256 位内存位置。内存操作数必须在16字节(128位版本)或32字节(VEX.256编码版本)边界上对齐,否则将生成一般保护异常(#GP)。
            "movntps %%xmm2, 32(%1)\n"
            "movntps %%xmm3, 48(%1)\n"
            ::"r"(src),"r"(dest):"memory");        //  : 输出运算符列表 : 输入运算符列表 : 被更改资源列表  
            dest += 64;
            src += 64;
        }
    }
 
    // byte-to-byte slow copy
    if(len) slow_memcpy(dest, src, len);
    return dest;
}
 
int main(void){
 
    /*    int setvbuf(FILE * stream, char * buf, int type, unsigned size);
    **    stream为文件流指针,buf为缓冲区首地址,type为缓冲区类型,size为缓冲区内字节的数量。
    **        type: _IOFBF (满缓冲):当缓冲区为空时,从流读入数据。或当缓冲区满时,向流写入数据。
    **              _IOLBF (行缓冲):每次从流中读入一行数据或向流中写入—行数据。
    **              _IONBF (无缓冲):直接从流中读入数据或直接向流中写入数据,而没有缓冲区。
    **    成功返回0,失败返回非0
    */
    setvbuf(stdout, 0, _IONBF, 0);
    setvbuf(stdin, 0, _IOLBF, 0);
 
    printf("Hey, I have a boring assignment for CS class.. :(\n");
    printf("The assignment is simple.\n");
 
    printf("-----------------------------------------------------\n");
    printf("- What is the best implementation of memcpy?        -\n");
    printf("- 1. implement your own slow/fast version of memcpy -\n");
    printf("- 2. compare them with various size of data         -\n");
    printf("- 3. conclude your experiment and submit report     -\n");
    printf("-----------------------------------------------------\n");
 
    printf("This time, just help me out with my experiment and get flag\n");
    printf("No fancy hacking, I promise :D\n");
 
    unsigned long long t1, t2;
    int e;
    char* src;
    char* dest;
    unsigned int low, high;
    unsigned int size;
 
    /*    void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);
    **    mmap()用来将某个文件内容映射到内存中,对该内存区域的存取即是直接对该文件内容的读写。
    **        start    指向欲对应的内存起始地址,通常设为NULL,代表让系统自动选定地址,对应成功后该地址会返回。
    **        length    代表将文件中多大的部分对应到内存。
    **        prot    代表映射区域的保护方式,有下列组合:
    **            PROT_EXEC  映射区域可被执行;
    **            PROT_READ  映射区域可被读取;
    **            PROT_WRITE  映射区域可被写入;
    **            PROT_NONE  映射区域不能存取。
    **        flags    会影响映射区域的各种特性:
    **            MAP_FIXED  如果参数 start 所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此 flag。
    **            MAP_SHARED  对应射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
    **            MAP_PRIVATE  对应射区域的写入操作会产生一个映射文件的复制,即私人的"写入时复制" (copy on write)对此区域作的任何修改都不会写回原来的文件内容。
    **            MAP_ANONYMOUS  建立匿名映射,此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
    **            MAP_DENYWRITE  只允许对应射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
    **            MAP_LOCKED  将映射区域锁定住,这表示该区域不会被置换(swap)。
    **            在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。
    **        fd    open()返回的文件描述词,代表欲映射到内存的文件。
    **        offset    文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。
    **    若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。
    **        错误代码:
    **            EBADF  参数fd 不是有效的文件描述词
    **            EACCES 存取权限有误。如果是 MAP_PRIVATE 情况下文件必须可读,使用 MAP_SHARED 则要有 PROT_WRITE 以及该文件要能写入。
    **            EINVAL 参数 start、length 或 offset 有一个不合法。
    **            EAGAIN 文件被锁住,或是有太多内存被锁住。
    **            ENOMEM 内存不足。
    */
    // allocate memory
    char* cache1 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);    // 使用了 MAP_ANONYMOUS 建立匿名映射,此时会忽略参数 fd。
    char* cache2 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);    // 7 代表映射区域可被读、写、执行。PROT_READ:0x1,PROT_WRITE:0x2        /* Page can be written.  */
    src = mmap(0, 0x2000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);                // PROT_EXEC:0x4,PROT_NONE:0x0
    size_t sizes[10];
    int i=0;
 
    // setup experiment parameters
    for(e=4; e<14; e++){    // 2^13 = 8K
        low = pow(2,e-1);    // double pow(double x, double y);  x^y
        high = pow(2,e);
        printf("specify the memcpy amount between %d ~ %d : ", low, high);
        scanf("%d", &size);
        if( size < low || size > high ){
            printf("don't mess with the experiment.\n");
            exit(0);
        }
        sizes[i++] = size;
    }
 
    sleep(1);
    printf("ok, lets run the experiment with your configuration\n");
    sleep(1);
 
    // run experiment
    for(i=0; i<10; i++){
        size = sizes[i];
        printf("experiment %d : memcpy with buffer size %d\n", i+1, size);
        dest = malloc( size );    // 在堆区分配一块指定大小的内存空间(未初始化),用来存放数据。分配成功返回指向该内存的地址,失败则返回 NULL。
 
        /*    void * memcpy ( void * dest, const void * src, size_t num );
        **    复制 src 所指的内存内容的前 num 个字节到 dest 所指的内存地址上。逐字节地进行复制,不关心被复制的数据类型。
        **    与 strcpy() 不同的是,memcpy() 会完整的复制 num 个字节,不会因为遇到 "\0" 而结束。
        **    返回指向 dest 的指针。注意返回的指针类型是 void,使用时一般要进行强制类型转换。
        */
        memcpy(cache1, cache2, 0x4000);        // to eliminate cache effect
        t1 = rdtsc();
        slow_memcpy(dest, src, size);        // byte-to-byte memcpy
        t2 = rdtsc();
        printf("ellapsed CPU cycles for slow_memcpy : %llu\n", t2-t1);
 
        memcpy(cache1, cache2, 0x4000);        // to eliminate cache effect
        t1 = rdtsc();
        fast_memcpy(dest, src, size);        // block-to-block memcpy
        t2 = rdtsc();
        printf("ellapsed CPU cycles for fast_memcpy : %llu\n", t2-t1);
        printf("\n");
    }
 
    printf("thanks for helping my experiment!\n");
    printf("flag : ----- erased in this source code -----\n");
    return 0;
}
// compiled with : gcc -o memcpy memcpy.c -m32 -lm
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>C
#include <sys/mman.h>
#include <math.h>
 
unsigned long long rdtsc(){
        asm("rdtsc");            // 将处理器的时间标签计数器的当前值加载到 EDX:EAX 寄存器。
}
 
char* slow_memcpy(char* dest, const char* src, size_t len){
    int i;
    for (i=0; i<len; i++) {
        dest[i] = src[i];
    }
    return dest;
}
 
char* fast_memcpy(char* dest, const char* src, size_t len){
    size_t i;
    // 64-byte block fast copy
    if(len >= 64){
        i = len / 64;
        len &= (64-1);
        while(i-- > 0){
            __asm__ __volatile__ (
            "movdqa (%0), %%xmm0\n"
            "movdqa 16(%0), %%xmm1\n"        // MOVDQA 目标操作数 源操作数  移动对齐的双、四字
            "movdqa 32(%0), %%xmm2\n"
            "movdqa 48(%0), %%xmm3\n"
            "movntps %%xmm0, (%1)\n"        // MOVNTPS 目标操作数 源操作数  使用非时间提示存储打包的单精度浮点值
            "movntps %%xmm1, 16(%1)\n"        // 目标操作数是 128 位或 256 位内存位置。内存操作数必须在16字节(128位版本)或32字节(VEX.256编码版本)边界上对齐,否则将生成一般保护异常(#GP)。
            "movntps %%xmm2, 32(%1)\n"
            "movntps %%xmm3, 48(%1)\n"
            ::"r"(src),"r"(dest):"memory");        //  : 输出运算符列表 : 输入运算符列表 : 被更改资源列表  
            dest += 64;
            src += 64;
        }
    }
 
    // byte-to-byte slow copy
    if(len) slow_memcpy(dest, src, len);
    return dest;
}
 
int main(void){
 
    /*    int setvbuf(FILE * stream, char * buf, int type, unsigned size);
    **    stream为文件流指针,buf为缓冲区首地址,type为缓冲区类型,size为缓冲区内字节的数量。
    **        type: _IOFBF (满缓冲):当缓冲区为空时,从流读入数据。或当缓冲区满时,向流写入数据。
    **              _IOLBF (行缓冲):每次从流中读入一行数据或向流中写入—行数据。
    **              _IONBF (无缓冲):直接从流中读入数据或直接向流中写入数据,而没有缓冲区。
    **    成功返回0,失败返回非0
    */
    setvbuf(stdout, 0, _IONBF, 0);
    setvbuf(stdin, 0, _IOLBF, 0);
 
    printf("Hey, I have a boring assignment for CS class.. :(\n");
    printf("The assignment is simple.\n");
 
    printf("-----------------------------------------------------\n");
    printf("- What is the best implementation of memcpy?        -\n");
    printf("- 1. implement your own slow/fast version of memcpy -\n");
    printf("- 2. compare them with various size of data         -\n");
    printf("- 3. conclude your experiment and submit report     -\n");
    printf("-----------------------------------------------------\n");
 
    printf("This time, just help me out with my experiment and get flag\n");
    printf("No fancy hacking, I promise :D\n");
 
    unsigned long long t1, t2;
    int e;
    char* src;
    char* dest;
    unsigned int low, high;
    unsigned int size;
 
    /*    void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);
    **    mmap()用来将某个文件内容映射到内存中,对该内存区域的存取即是直接对该文件内容的读写。

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//