汇编语言实现一个简单的十六进制转储使用工具
一个简单的十六进制转储使用工具,演示了汇编语言过程的使用。
; 可执行程序名 : hexdump2 ; 版本 : 1.0 ; 创建日期 : 7/9/2016 ; 最后修改 : 7/9/2016 ; 作者 : Moonlight Poet ; 描述 : 一个简单的十六进制转储使用工具,演示了汇编语言过程的使用。 ; ; 使用以下命令生成该程序 : ; nasm -f elf64 -g -F stabs hexdump2.asm ; ld -o hexdump2 hexdump2.o ; SECTION .bss ; 包含未初始化数据的段 BUFFLEN equ 10 Buff resb BUFFLEN SECTION .data ; 包含已初始化数据的段 ; 这里,我们使用一个由两个部分组成的简单数据结构, ; 实现一个十六进制转储使用用具的文本行。 ; 第一个部分显示了16个字节的、中间用空格隔开的十六进制数。(也就是Dumplin) ; 紧跟在后面的是一个由16个字符组成的行,二者之间通过竖线(|)字符分隔。 ; 因为这两个部分是相邻的, ; 所以可以被单独引用或者作为一个连续的单元来引用。 ; 记住,如果要将DumpLin分开使用,在将其发送到Linux控制台之前, ; 必须追加一个EOF。 DumpLin: db " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " DUMPLEN equ $-DumpLin ASCLin: db "|................|",10 ASCLINE equ $-ASCLin FULLLEN equ $-DumpLin ; HexDigits表用来把数字值转换为它们的十六进制等值。 ; 通过一个没有缩放的半字节来进行索引:[HexDigits + eax] HexDigits: db "0123456789ABCDEF" ; 此表用于ASCII字符翻译,实现将其翻译成该十六进制转储行的ASCII部分 ; 通过使用XLAT或者普通的内存查找。 ; 所有的可打印的字符被翻译为它们本身。 ; 高128个字符被转换为ASCII的句号(2Eh) ; 低128个字符中的不可打印字符也被转换为ASCII句号,例如:字符127。 DotXlat: db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh db 20h,21h,22h,23h,24h,25h,26h,27h,28h,29h,2Ah,2Bh,2Ch,2Dh,2Eh,2Fh db 30h,31h,32h,33h,34h,35h,36h,37h,38h,39h,3Ah,3Bh,3Ch,3Dh,3Eh,3Fh db 40h,41h,42h,43h,44h,45h,46h,47h,48h,49h,4Ah,4Bh,4Ch,4Dh,4Eh,4Fh db 50h,51h,52h,53h,54h,55h,56h,57h,58h,59h,5Ah,5Bh,5Ch,5Dh,5Eh,5Fh db 60h,61h,62h,63h,64h,65h,66h,67h,68h,69h,6Ah,6Bh,6Ch,6Dh,6Eh,6Fh db 70h,71h,72h,73h,74h,75h,76h,77h,78h,79h,7Ah,7Bh,7Ch,7Dh,7Eh,2Eh db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh SECTION .text ; 包含代码段 ;---------------------------------------------------------------------------------------------------- ; ClearLine: 将一个十六进制转储行字符串清零,即将其变为16个0。 ; 更新日期: 7/9/2016 ; 输入参数: 没有。 ; 返回值: 没有。 ; 修改: 没有。 ; 调用: DumpChar ; 描述: 通过16次调用DumpChar过程,每次传递给它参数0, ; 这个十六进制转储行字符串被清除为二进制0。 ClearLine: ;pushad ; 保存主调程序的所有通用寄存器 push rax push rdx mov edx,15 ; 我们将进行16次,从0开始计数 .poke: mov eax,0 ; 告诉DumpChar插入一个'0' call DumpChar ; 将'0'插入到十六进制转储字符串中 sub edx,1 ; DEC并不影响CF! jae .poke ; 如果EDX >= 0 ,回去继续循环。 ;popad ; 恢复所有主调程序的通用寄存器。 pop rdx pop rax ret ; 返回 ;---------------------------------------------------------------------------------------------------- ; DumpChar: 插入一个值到十六进制转储字符串中。 ; 更新日期: 7/9/2016 ; 输入参数: 传递一个即将被插入的值到EAX寄存器中。 ; 传递这个值在改行中的位置(0-15)到EDX寄存器中。 ; 返回值: 没有。 ; 修改: EAX, ASCLin, DumpLin ; 调用: 没有。 ; 描述: 传递到EAX寄存器中的值将其放在十六进制传出部分, ; 又放在ASCII部分, ; 其位置为传递给EDX的值 ; 当它不是一个可打印字符时,用一个空格来表示。 DumpChar: push rbx ; 保存主调程序的EBX寄存器 push rdi ; 保存主调程序的EDI寄存器 ; 首先,我们将输入的字符串插入到转储行的ASCII部分 mov bl,byte [DotXlat+eax] ; 将不可打印字符翻译成'.'(句号2Eh)(如果可打印就打印了,具体见DotXlat) mov byte [ASCLin+edx+1],bl ; 写入ASCII码部分 ; 接下来,我们把与插入字符等值的十六进制值 ; 插入到转储行的十六进制部分: mov ebx,eax ; 保存输入字符的第二个副本 lea edi,[edx*2+edx] ; 计算字符串行中的偏移量(EDX*3) ; 查找低半字节的字符,并将其插入到字符串: and eax,0000000Fh ; 屏蔽掉除低半字节以外的所有位 mov al,byte [HexDigits+eax] ; 查找该半字节的等值字符。 mov byte [DumpLin+edi+2],al ; 将这个等值字符写入字符串行。 ; 查找高半字节的字符,并将其插入到字符串: and ebx,000000F0h ; 屏蔽掉除第二低的半字节之外的所有位 shr ebx,4 ; 将字节的高四位移到低四位 mov bl,byte [HexDigits+ebx] ; 查找与该半字节的等值字符。 mov byte [DumpLin+edi+1],bl ; 将这个等值字符写入字符串行。 ; 任务完成!让我们“回家”: pop rdi ; 恢复主调程序的EDI寄存器的值 pop rbx ; 恢复主调程序的EBX寄存器的值 ret ; 返回主调程序 ;---------------------------------------------------------------------------------------------------- ; PrintLine: 将DumpLin现实到标准输出 ; 更新日期: 7/9/2016 ; 输入参数: 没有。 ; 返回值: 没有。 ; 修改: 没有。 ; 调用: 内核sys_write ; 描述: 将十六进制转储行DumpLin显示到标准输出。 ; 使用 INT 80h sys_write. 所有寄存器都被保存起来。 PrintLine: ;pushad ; 保存主调程序的所有通用寄存器 push rax push rbx push rcx push rdx mov eax,4 ; 指定sys_write调用 mov ebx,1 ; 指定文件描述符1;标准输出 mov ecx,DumpLin ; 传递字符串行的偏移量 mov edx,FULLLEN ; 传递字符串行的大小 int 80h ; 进行内核调用显示字符串行 ;popad ; 恢复主调程序的所有通用寄存器 pop rdx pop rcx pop rbx pop rax ret ; 返回主调程序 ;---------------------------------------------------------------------------------------------------- ; LoadBuff: 通过 INT 80h sys_read 将一个缓冲区从标准输入装满数据。 ; 更新日期: 7/9/2016 ; 输入参数: 没有。 ; 返回值: 从EBP中读入的字节数 ; 修改: ECX, EBP, Buff ; 调用: 内核 sys_write ; 描述: 使用 INT 80h sys_read 从标准输入中加载慢慢一缓冲区数据 ; 并将其放入Buff。 ; 因为我们开始了一个新的装满数据的缓冲区,所以缓冲区偏移量计数器ECX被设置为零。 ; 主调程序必须测试EBP中的值: ; 如果在返回时EBP中的值为零,表明在标准输入中遇到了EOF(文件结尾) ; 如果在返回时EBP中的值比零小,表明发生了某种类型的错误。 LoadBuff: push rax ; 保存主调程序的EAX寄存器 push rbx ; 保存主调程序的EBX寄存器 push rdx ; 保存主调程序的EDX寄存器 mov eax,3 ; 指定 sys_read call mov ebx,0 ; 指定文件描述符 0:标准输入。 mov ecx,Buff ; 指定要被读入数据的缓冲区的偏移地址 mov edx,BUFFLEN ; 传递一次要读入的字节数 int 80h ; 调用 sys_read 来填满缓冲区 mov ebp,eax ; 保存从文件中读入的字节数,以备后用。 xor ecx,ecx ; 清除缓冲区指针ECX,将其设为0 pop rdx ; 恢复主调用程序的EDX寄存器 pop rbx ; 恢复主调用程序的EBX寄存器 pop rax ; 恢复主调用程序的EAX寄存器 ret ; 返回主调用程序 GLOBAL _start ;---------------------------------------------------------------------------------------------------- ; 主程序从这里开始 ;---------------------------------------------------------------------------------------------------- _start: nop ; 为GDB输入无操作指令 nop ; 在循环开始前需要做的所有初始化工作都从这里开始: xor esi,esi ; 将整个字节计数器清零 call LoadBuff ; 从缓冲区读入第一个缓冲区的数据 cmp ebp,0 ; 如果ebp=0,sys_read到达了标准输入的EOF(文件结尾) jbe Exit ; 审查该缓冲区,并将这些二进制字节转换为十六进制数字值: Scan: xor eax,eax ; 将EAX寄存器清零 mov al,byte [Buff+ecx] ; 将一个字节从缓冲区读出到AL中 mov edx,esi ; 复制整个计数器的值到EDX寄存器中 and edx,000000Fh ; 屏蔽掉除字符计数器的最低四位以外的其他位 call DumpChar ; 调用字符插入过程 ; 将缓冲区的指针指向下一个字符,并查看以下缓冲区的工作是否已经完成: inc esi ; 递增整个已处理的字符的计数值 inc ecx ; 递增缓冲区的指针值 cmp ecx,ebp ; 与缓冲区中的字符数相比较 jb .modTest ; 如果我们已经处理完了缓冲区中的所有字符…… call LoadBuff ; ……再次充满缓冲区 cmp ebp,0 ; 如果ebp=0,sys_read到达了标准输入的结尾 jbe Done ; 如果到达了EOF,我们的任务就完成了 ; 判断一下我们是否到达了一个包含16个值的块的结尾,并且需要显示一行: .modTest: test esi,0000000Fh ; 测试计数器中最低的四位是否为零。 jnz Scan ; 如果计数器不是16的倍数,继续循环(计数器用来一次扫描16个字节的数,没到的话就继续循环) call PrintLine ; ……否则打印那一行 call ClearLine ; 清除十六进制转储行,将其全部设置为0 jmp Scan ; 继续扫描缓冲区 ; 全部完成,让我们结束这个“晚会” Done: call PrintLine ; 打印“剩余”行 Exit: mov eax,1 ; 用于退出Syscall的代码 mov ebx,0 ; 将零作为返回码 int 80h ; 进行内核调用
样例输入:
hello,world! I am moonlightpoet. How old are you? Hoe are you? I'm fine,thank you!And you?
样例输出:
68 65 6C 6C 6F 2C 77 6F 72 6C 64 21 0A 49 20 61 |hello,world!.I a| 6D 20 6D 6F 6F 6E 6C 69 67 68 74 70 6F 65 74 2E |m moonlightpoet.| 0A 48 6F 77 20 6F 6C 64 20 61 72 65 20 79 6F 75 |.How old are you| 3F 0A 48 6F 65 20 61 72 65 20 79 6F 75 3F 0A 49 |?.Hoe are you?.I| 27 6D 20 66 69 6E 65 2C 74 68 61 6E 6B 20 79 6F |'m fine,thank yo| 75 21 41 6E 64 20 79 6F 75 3F 0A 0A 00 00 00 00 |u!And you?......|