-
-
[分享] 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()用来将某个文件内容映射到内存中,对该内存区域的存取即是直接对该文件内容的读写。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
- [原创]pwnable.kr horcruxes 10755
- [分享] pwnable.kr blukat 9974
- [分享] pwnable.kr unlink 9473
- [分享] pwnable.kr asm 10671
- [分享] pwnable.kr memcpy 10451