首页
论坛
专栏
课程

[原创] memcpy pwnable.kr writeup

2019-8-5 23:04 1669

[原创] memcpy pwnable.kr writeup

2019-8-5 23:04
1669

题目分析

地址: http://pwnable.kr/play.php

 

只要程序能够顺利执行完,flag就会被打印。
题目问题主要出在 fast_memcpy()函数上。
里面的movntps指令的目的地址必须16byte对齐。
而malloc函数返回的地址有可能不满足条件,这样的话就会segfault。
那怎么让malloc分配的内存地址是16byte对齐呢?

 

代码:

// compiled with : gcc -o memcpy memcpy.c -m32 -lm
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include <math.h>

unsigned long long rdtsc(){
        asm("rdtsc");
}

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 32(%0), %%xmm2\n"
            "movdqa 48(%0), %%xmm3\n"
            "movntps %%xmm0, (%1)\n"
            "movntps %%xmm1, 16(%1)\n"
            "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){

    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;
    // allocate memory
    char* cache1 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    char* cache2 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    src = mmap(0, 0x2000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

    size_t sizes[10];
    int i=0;

    // setup experiment parameters
    for(e=4; e<14; e++){    // 2^13 = 8K
        low = pow(2,e-1);
        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 );

        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;
}

解题过程

利用gdb加载程序, 在 main 函数中调用 malloc函数指令位置处下断点。
观察每次分配内存的地址。
内存size = 8:0x08f41010
size = 16:0x08f41020
size = 32: 0x08f41038
size = 64: 0x08f41060
size = 128: 0x08f410a8

 

可以观察到一个规律,那就是每次分配的内存地址减去上次分配的内存地址 都比上次请求的内存空间大小 多8个字节。
这8个字节应该就是堆块管理结构内容。

 

<u>所以:想要程序每次分配的内存地址都是16byte对齐。则每次分配的空间大小都应该是 size % 16 = 8</u>

poc

from pwn import *
context.log_level = 'DEBUG'
context.terminal = ['terminator','-x','sh','-c']
#p = process('./memcpy')
s = ssh(host="pwnable.kr",
        user="memcpy",
        port=2222,
        password="guest")
if s.connected():
    p = s.connect_remote("127.0.0.1",9022)
    p.recvuntil("specify the memcpy amount between 8 ~ 16 :")
    p.sendline('8')
    for i in range(9):
        send_num = pow(2, i+4)
        send_num = str(send_num+8)
        p.sendlineafter(': ', send_num)
    p.interactive()

mmap 函数

  • 用途(内存映射)
  1. 将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用。用内存读写代替I/O读写
  2. 将特殊文件进行匿名内存映射,可以为关联进程提供共享内存空间
  3. 为无关联的进程提供内存共享空间

普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。mmap并不分配空间, 只是将文件映射到调用进程的地址空间里(但是会占掉你的 virutal memory), 然后你就可以用memcpy等操作写文件, 而不用write()了.写完后,内存中的内容并不会立即更新到文件中,而是有一段时间的延迟,你可以调用msync()来显式同步一下, 这样你所写的内容就能立即保存到文件里了.这点应该和驱动相关。 不过通过mmap来写文件这种方式没办法增加文件的长度, 因为要映射的长度在调用mmap()的时候就决定了.如果想取消内存映射,可以调用munmap()来取消内存映射

  • 原型
    void * mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)

start: 指向映射的内存起始地址;NULL表示系统自动选择地址
length: 内存大小。
prot: 映射内存的保护方式

  • PROT_EXEC 映射区域可被执行
  • PROT_READ 映射区域可被读取
  • PROT_WRITE 映射区域可被写入
  • PROT_NONE 映射区域不能存取

flags: 内存区域的各种特性。调用时必须指定 MAP_SHARED / MAP_PRIVATE 之一。

  • MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。
  • MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
  • MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
  • MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
  • MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
  • MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。

fd: 要映射到内存中的文件描述符。 fd为-1表示匿名内存映射。

 

返回值: 映射成功返回内存起始地址。

MOVNTPS指令 (解题关键)

从XMM寄存器复制4个单精度浮点数至128位内存单元
reference
The source operand is an XMM register, YMM register or ZMM register, which is assumed to contain packed single-precision, floating-pointing. The destination operand is a 128-bit, 256-bit or 512-bit memory location. The memory operand must be aligned on a 16-byte (128-bit version), 32-byte (VEX.256 encoded version) or 64-byte (EVEX.512 encoded version) boundary otherwise a general-protection exception (#GP) will be generated.
对应源操作数为XMM,则目的内存为128比特内存位置。内存必须16字节对齐,否则会生成一个 general-protection 异常。

MOVdqa指令

移动对齐的双四字 128bit

XMM寄存器

reference
SSE指令集提供了xmm寄存器,xmm一组8个128位的寄存器,分别名为xmm0-xmm7
对应指令
movaps 把4个对准的单精度值传送到xmm寄存器或者内存
movups 把4个不对准的单精度值传送到xmm寄存器或者内存
movss 把1个单精度值传送到内存或者寄存器的低位双字
movlps 把2个单精度值传送到内存或者寄存器的低四字
movhps 把2个单精度值传送到内存或者寄存器的高四字
movlhps 把2个单精度值从低四字传送到高四字
movhlps 把2个单精度值从高四字传送到低四字

 

movapd 把2个对准的双精度值传送到xmm寄存器或者内存
movupd 把2个不对准的双精度值传送到xmm寄存器或者内存
movdqa 把2个对准的四字节整数传送到xmm寄存器或者内存
movdqu 把2个不对准的四字节整数传送到xmm寄存器或者内存
movsd 把1个双精度值传送到内存或者寄存器的低四字
movhpd 把1个双精度值传送到内存或者寄存器的高四字
movlpd 把1个双精度值传送到内存或者寄存器的低四字

segfault 原因

程序尝试去读或者写一个非法内存地址

 

A segfault occurs when a reference to a variable falls outside the segment where that variable resides, or when a write is attempted to a location that is in a read-only segment.

malloc 分配的内存地址 16byte对齐

chunk_size = 1024; //需要的内存块大小
void *mem = malloc(chunk_size+15);
void *ptr = ((uintptr_t)mem+15) & ~(uintptr_t)0x0F;
//use the ptr
free(mem);

代码参考

Pwnlib 设置

terminal 指定

context.terminal = ['terminator','-x','sh','-c']
第一个参数 terminator 为本地可用的terminal。

输出交互信息

context.log_level = 'DEBUG'
这样可以输入对程序的输入和程序的输出信息



[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最新回复 (2)
gdxzll 2019-8-17 23:19
2
1
看了一下pwnable的writeup,题目不错
j3rry 2019-8-19 19:10
3
0
gdxzll 看了一下pwnable的writeup,题目不错
适合从入门到放弃哈哈哈
游客
登录 | 注册 方可回帖
返回