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

[分享] pwnable.kr memcpy

2021-1-21 20:04
9455

memcpy

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

题目

解题过程

1. 查看文件列表,读取 readme


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

2. 阅读源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// 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;
}

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

3. 调试程序

首先利用源码编译二进制文件
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 字节对齐的数字

1
2
3
4
5
6
while True:
    a = int(raw_input())
    if (a + 4) % 16 > 9 or (a + 4) % 16 ==0:
        print("Right")
    else:
        print("Wrong") # 如果输出 Wrong,当前值 +8 即可

我们所得的一串符合预期的值为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


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回