这题整体思路其实大家应该都很明白了。这里主要是列举一些优化点。elf header相关的做的比较挫,求其他大神思路。
FPU优化k计算
普通md5一般使用预计算的K实现,而64个uint32_t就导致了,额外256字节的空间。为了节省这些空间,可以采用intel的浮点指令fsin
核心代码如下
代码语言:txt复制 push 32
fild QWORD [rsp] ;push 32 到ST0
push rsi ; push i 到栈上
fld1 ; push 1 进入ST0 这时候ST1是32
fiadd DWORD [rsp] ; ST0=ST0 i
fsin ; ST0=sin(ST0)
fabs ; ST0=abs(ST0)
fscale ; ST0=ST0*2^ST1
fisttp QWORD [rsp] ; pop K[i] to stack
节省系统调用
正常思路中需要readlink
找到自身文件,然后用open
,read
来读取文件内容。
这些完全可以省掉,由于进程执行时,已经完全将自身镜像加载到内存地址。因此可以直接从汇编的基址读取内存,即为文件内容。
避免memcpy
正常md5流程中,需要对message进行padding。因此需要按64字节为一块,一块一块拷贝到临时buffer中单独处理。
这里可以通过将program header中的flags设置为可写,并将memsize设置的比filesize大一些,方便直接在内存中原地做padding。
代码语言:txt复制Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000008048000 0x0000000008048000
0x0000000000000184 0x0000000000000204 RWE 203440c766800880
hexifier优化
最终输出md5的时候需要做bin2hex,最初写的c语言版本很简单
代码语言:txt复制for(int i=0;i<16;i ) {
hex_output[i*2] = hexmap[(output[i] & 0xf0) >> 4];
hex_output[i*2 1] = hexmap[output[i] & 0xf];
}
后来为了汇编优化,改成了一层循环
代码语言:txt复制for(int i=0;i<32;i ) {
hex_output[i] = hexmap[((output[i>>1] >> (i&1?4:0)) & 0xf)];
}
但是无论如何都有一个额外16字节的hexmap
这里汇编也改了很多版,最后使用loop bextr实现了短小精悍的hexifier
loop是非常好用的循环指令,1字节能够替代dec jnz
lodsb,stosb也是很好用的读取存储指令,1字节能够替代mov inc
bextr是二进制扩展中用于提取特定几位的指令用于类似于实现 a = (b >> k) & n
代码语言:txt复制hexifer:
push rsp
pop rsi ;input binary
; 正常是需要32的栈 但是两边长短步调不一致可以只压16字节
push rax
push rax
push rsp
pop rdi ;output hex
mov cl, 32
mov bx, 0x404
hexifer_loop:
lodsb ; al = input[i]
bextr eax, eax, ebx
cmp al, 10
jl number
add al, 0x27
number:
add al, 0x30
stosb ; output[i*2] = al
xor bl, 0x4
jpo hexifer_loop_end
dec rsi
hexifer_loop_end:
loop hexifer_loop
SSE指令实现MD5轮转
MD5算法中,每轮block计算都需要对state做一个轮转功能
代码语言:txt复制A = D;
D = C;
C = B;
B = f;
每个block计算结束之后还有个add操作
代码语言:txt复制state[0] = A;
state[1] = B;
state[2] = C;
state[3] = D;
这些可以用sse指令批量完成
代码语言:txt复制pshufd xmm1, xmm1, 10010011b ; rotate xmm1
paddd xmm0, xmm1 ; add inner state to outer state
寻址优化
一般寻址计算/jmp类指令的时候,如果偏移大于1字节,就会变成4字节寻址,因此尽量控制偏移在1字节以内。比如常量可以放在靠文件头的位置,寻址的时候指令会小很多。
padding预计算
由于文件大小是合代码强相关,因此padding的计算和处理可以完全在编译期做,没有必要放到运行时。
代码语言:txt复制64n: 64n.asm calc.sh
echo 'PADDINGSIZE equ 64' > padding.asm
echo 'LOOPSIZE equ 1' >> padding.asm
nasm $< -o $@
./calc.sh $@ > padding.asm
nasm $< -o $@
chmod x $@
代码语言:txt复制#! /bin/bash
size=$(stat --printf="%s" $1)
left=$(echo $sized| bc)
if [ $left -gt 56 ]; then
PADDINGSIZE=$(echo 64-$left 64 | bc)
else
PADDINGSIZE=$(echo 64-$left | bc)
fi
LOOPSIZE=$(echo "($size $PADDINGSIZE)/64"| bc)
echo "PADDINGSIZE equ $PADDINGSIZE"
echo "LOOPSIZE equ $LOOPSIZE"
可以直接在构建完成后,离线算出padding内容长度等信息,然后生成单独的汇编文件。
在代码中include这个额外生成的汇编文件,最后直接赋值就可以了。
代码语言:txt复制%include "padding.asm"
; buffer rawlen = 0x80
or BYTE [$$ FILESIZE], 0x80
mov WORD [rax PADDINGSIZE - 8], FILESIZE * 8
其他
使用64位寄存器(rax/r8之类的)的时候指令需要有64bit prefix,因此尽量不要使用64位寄存器,会额外多一个字节
mov rax, 1 > mov eax, 1
使用寄存器寻址的时候使用非64位寄存器,也会额外多一个字节
mov rsp, 1 < mov 1, esp
完整源码
代码语言:txt复制BITS 64
%include "padding.asm"
�fine MD5PADDING 128
�fine EXTRA MD5PADDING
org 0x08048000
ehdr: ; Elf64_Ehdr
db 0x7F, "ELF" ; e_ident[EI_MAGO]
db 2 ; e_ident[EI_CLASS] 64bits
db 1 ; e_ident[EI_DATA] little endianness
db 1 ; e_ident[EI_VERSION]
; e_ident[EI_OSABI] System V
; e_ident[EI_ABIVERSION] & padding 7
_start2:
movdqu xmm0, [rbp INITIAL]
push 32
jmp SHORT _start3
dw 2 ; e_type
dw 0x3e ; e_machine
OO equ $-$$
db 0, 1, 5, 0 ; e_version
dq _start ; e_entry
dq phdr - $$ ; e_phoff
_start:
mov ebp, $$ ; use rbp as offset
; preprocess for input buffer
mov eax, $$ FILESIZE
jmp SHORT _start2
dw ehdrsize ; e_ehsize
dw phdrsize ; e_phentsize
dw 1 ; e_phnum
ROTS equ $-$$
db 7,12,17,22,5,9 ; e_shentsize
; e_shnum
; e_shstrndx
ehdrsize equ $ - ehdr
db 14,20,4,11,16,23,6,10,15,21
M equ $-$$
db 1, 5, 3, 7 ; e_flags
INITIAL equ $-$$
dd 0x67452301;
dd 0xEFCDAB89;
dd 0x98BADCFE;
dd 0x10325476;
FUNS equ $-$$
f0:
xor esi,edx
xchg eax,edx
and esi,edi
xor eax,esi
ret
f1:
xor edi,esi
xchg eax,esi
and edi,edx
xor eax,edi
ret
f2:
xor esi,edx
xchg eax,esi
xor eax,edi
ret
dw 0
f3:
not edx
or edx,edi
xchg eax,edx
xor eax,esi
ret
_start3:
jmp SHORT _start4
phdr: ; Elf64_Phdr
dd 1 ; p_type
dd 7 ; p_flags
dq 0 ; p_offset
dq $$ ; p_vaddr
dq $$ ; p_paddr
dq FILESIZE ; p_filesz
dq FILESIZE EXTRA ; p_memsz
; p_align
phdrsize equ $ - phdr 8 ; start的代码没有对齐8字节 这里就直接加8了
_start4:
padding:
; buffer rawlen = 0x80
or BYTE [rax], 0x80
mov WORD [rax PADDINGSIZE - 8], FILESIZE * 8
setup_initial:
; r14 blocK
mov r14d, ebp ; input buffer
mov r13b, LOOPSIZE
; xmm0 is outer state
fild QWORD [rsp]
md5_outer_loop:
; xmm1 is inner state
movdqu xmm1, xmm0
md5_inner_process:
xor esi, esi ; ebx as i inner loop i
md5_inner_loop:
; location rsp use more 1 byte than other
push rsp
pop rax
mov ebx, esi
shr ebx, 4 ; ebx = i / 16
push rsi
movdqu [rax], xmm1 ; copy outer state to stack
mov edi, [rax 4] ; eax = fctn(B,C,D)
mov esi, [rax 8]
mov edx, [rax 12]
lea eax, [rbp FUNS rbx*8] ; fctn = FUNS[p]
call rax
pop rsi
push rax ; push fctn(B,C,D) to stack
push rsi
fld1
fiadd DWORD [rsp]
fsin
fabs
fscale
fisttp QWORD [rsp] ; push K[i] to stack
movzx eax, BYTE [rbx rbp M] ; eax = m = M[p]
mul esi
add al,BYTE [rbx rbp OO] ; o = O[p]
and eax,0xf ; al = g = (m*q o)
mov eax,[r14 rax*4]; eax = X[g]
pop rdx ; edx = K[q 16*p]
add eax, edx
pop rdx ; pop fctn(B,C,D) to rdx
add eax, edx
push rsp
pop rdi
; location rsp use more 1 byte than other
add eax, [rdi]; eax = A fctn(B,C,D) K[q 16*p] X[g]
mov edx, esi ; eax = i / 16 * 4
shr dl, 4
shl dl, 2
mov ecx, esi ; ecx = i % 4
and cl, 0x3
add dl, cl
mov cl,[rbp rdx ROTS]
rol eax, cl
add eax, [rdi 4] ; f = B rol (xxx)
mov [rdi], eax ; A = f
movdqu xmm1, [rdi] ; load new value to xmm1
pshufd xmm1, xmm1, 10010011b ; rotate xmm1
inc esi
cmp esi, 64
jl md5_inner_loop
paddd xmm0, xmm1 ; add inner state to outer state
add r14, 64
dec r13
jnz md5_outer_loop
movdqu [rdi], xmm0 ; mov state back to stack for output
hexifer:
push rsp
pop rsi ;input binary
; 正常是需要32的栈 但是两边长短步调不一致可以只压16字节
push rax
push rax
push rsp
pop rdi ;output hex
mov cl, 32
mov bx, 0x404
hexifer_loop:
lodsb ; al = input[i]
bextr eax, eax, ebx
cmp al, 10
jl number
add al, 0x27
number:
add al, 0x30
stosb ; output[i*2] = al
xor bl, 0x4
jpo hexifer_loop_end
dec rsi
hexifer_loop_end:
loop hexifer_loop
write:
mov al, 1
mov edi, eax
push rsp
pop rsi; hex output buffer
mov dl, 32
syscall
exit:
xor edi, edi ; exit arg 1
mov al, 60 ; sys_exit
syscall
FILESIZE equ $ - $$