程序破解
NOP、JNE、JE、JMP、CMP汇编指令的机器码
NOP:NOP指令即“空指令”。执行到NOP指令时,CPU什么也不做,仅仅当做一个指令执行过去并继续执行NOP后面的一条指令。(机器码:90)
JNE:条件转移指令,如果不相等则跳转。(机器码:75)
JE:条件转移指令,如果相等则跳转。(机器码:74)
JMP:无条件转移指令。段内直接短转Jmp short(机器码:EB)段内直接近转移Jmp near(机器码:E9)段内间接转移Jmp word(机器码:FF)段间直接(远)转移Jmp far(机器码:EA)
CMP:比较指令,功能相当于减法指令,只是对操作数之间运算比较,不保存结果。cmp指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
反汇编与十六进制编程器
实验源码:
login.c
#include<stdio.h>
void main()
{
int pass=123;
int enter;
printf("please enter the password:\n");
scanf("%d",&enter);
if(enter==pass)
printf("right\n");
else
printf("wrong\n");
}
运行效果如下:
输入命令:
objdump -d login
查看main函数
现在的需求是:修改可执行文件另其无论输入什么密码的是正确的。
修改思路:将jne 跳转偏移量改为0,这样40063a位置的指令就相当于继续执行输出right。
实现方法:
输入指令:
vim login
打开可执行文件
输入:
%!xxd
进入十六进制编辑模式,如下图所示:
然后输入
/750c
找到jne指令的位置,如下图所示:
将750c改成7500表示跳转到下一条指令继续执行,相当于NOP。
输入
%!xxd -r
切换回原模式
输入
wq
保存后退出。
运行修改后的文件效果如下:
由图可见无论输入什么密码都是正确的,修改成功。
如果想输入什么密码都是错误的,只需要将750c改为eb0c即可,eb为JMP,0c为输出"wrong"的代码相对偏移量。
效果如下:
如果想输入原先正确的密码为"wrong",输出其他密码为"right",只需把750c改为740c即可,74相当于JE,相等则跳转。
效果如下:
可执行文件的基本格式
Linux可执行文件格式为ELF即Executable and Linkable Format。
格式:
ELF header :ELF文件头,在文件的开始,保存了路线图,描述了该文件的组织情况。
program header table :程序文件头表,告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表。
.text段:存储只读程序
.data段:存储已经初始化的全局变量和静态变量
.bss段:存储未初始化的全局变量和静态变量,因为这些变量的值为0,所以这个段在文件当中不占据空间
.rodata段:存储只读数据,比如字符串常量
...(各种(节))
Section header table:节头部表,包含了描述文件节区的信息,每个节区在表中都有一项,每一项给出诸如节区名称、节区大小这类信息。用于链接的目标文件必须包含节区头部表,其他目标文件可以有,也可以没有这个表。
具体分析见ELF文件格式分析。
ELF文件格式分析
ELF全称Executable and Linkable Format,可执行连接格式,ELF格式的文件用于存储Linux程序。ELF文件(目标文件)格式主要三种:
1)可重定向文件:文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件。(目标文件或者静态库文件,即linux通常后缀为.a和.o的文件)
2)可执行文件:文件保存着一个用来执行的程序。(例如bash,gcc等)
3)共享目标文件:共享库。文件保存着代码和合适的数据,用来被下连接编辑器和动态链接器链接。(linux下后缀为.so的文件。)
ELF文件头
查看/usr/include/elf.h中的ELF头文件数据结构。
#define EI_NIDENT (16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;
取一段简单的代码进行分析:
hello.c
#include<stdio.h>
void main()
{
printf("hello");
}
输入指令
readelf -h hello
得到ELF文件头信息:
由图可看出ELF文件头大小为64字节。
输入命令:
hexdump -x hello -n 64
对ELF头的16进制表进行分析:
第一行对应e_ident[EI_NIDENT],实际内容为:7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 ,前四个字节7f454c46(0x45,0x4c,0x46是'e','l','f'对应的ascii编码)是一个魔数,表示这是一个ELF对象。接下来02字节表示是一个64位对象,接下来01字节表示是小端法表示,再接下来的01字节表示文件头的版本。剩下的默认设置为0。
第二行,e_type值为0x0002,表示是一个可执行文件。e_machine的值为0x003e表示目标文件所期待的系统架构为Advanced Micro Devices X86-64。e_version值为0x00000001,表示是当前版本。e_entry的值为0x00400430表示程序入口地址。
第三行,e_phoff的值为0x0040,表示程序头部表的起始位置在磁盘文件中的偏移量为64字节。e_shoff的值为0x19d8,表示节头部表(Section Header Table)的起始位置在磁盘文件中的偏移量为6616字节。
第四行,e_flags的值为e_flags值为0x00000000,表示未知处理器特定标志。e_ehsize值为0x0040表示ELF文件头部的大小为64字节。e_phentsize的值为0x0038,表示程序头部表(Program Header Table)中每一个表项的大小56字节。e_shnum值为0x0009,表示程序头部表(Program Header Table)中总共有9个表项。e_shentsize的值为0x0040,表示节头部表(Section Header Table)中每一个表项的大小为64字节。e_shnum值为0x001f,表示节头部表(Section Header Table)中总共有31个表项。e_shstrndx的值为:0x001c表示节头部表(Section Header Table)中与节名字表相对应的表项的索引。
通过文件头找到section header table,理解其内容
在/usr/include/elf.h中section header table的数据结构定义如下:
typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;
typedef struct
{
Elf64_Word sh_name; /* Section name (string tbl index) */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
由上述分析可知节头部表(Section Header Table)的起始位置文件头的偏移量为0x19d8字节。
输入命令:
xxd hello
找到0x19d8即Section Header Table的起始点如下图所示:
从起始点开始的Section Header Table的数据结构如Elf64_Shdr结构体所示。
通过输入命令
readelf -S hello
可以查看Section Header Table,如下图所示:
通过section header table找到各section
由前面的分析可知节头部表(Section Header Table)中每一个表项的大小为64字节。
在第一节中,内容全部为0不表示任何段。
在第二节中,为.interp段,段偏移sh_offset为0X238(红线),段大小sh_size为0X1c(蓝线)。
在第三节中,为.note.ABI-tag节,节偏移sh_offset为0X 254(红线),节大小sh_size为0X 20(蓝线)。
第四个节,为.note.gnu.build-i段,节偏移sh_offset为0X 274(红线), 节大小sh_size为0X 24(蓝线)。
第五个节,为.gnu.hash节,节偏移sh_offset为0X 298(红线), 节大小sh_size为0X 1c(蓝线)。
............中间节省略...........
第14节.text节的表项起始地址=0x19d8+14*64=0x1d58
第14节.text节的节偏移为0x430字节,节大小为0x182字节。
其他节同理可推出,其他节起始地址的表项起始地址=0x19d8+节序号*64。
然后通过相对文件头的偏移地址和节大小可以找到各section。
理解常见.text .strtab .symtab .rodata的section。
1.text section是可执行指令的集合,.data和.text都是属于PROGBITS类型的section,这是将来要运行的程序与代码。
2.strtab section是属于STRTAB类型的section,可以在文件中看到,它存着字符串,储存着符号的名字。
3.symtab section存放所有section中定义的符号名字,比如“data_items”,“start_loop”。 .symtab section是属于SYMTAB类型的section,它描述了.strtab中的符号在“内存”中对应的“内存地址”。
4.rodata section,ro代表read only,即只读数据(const)。