Skip to content

MCP的一些问题1 #97

Description

@sss12365

问题

通过 MCP 调用 execute_command("bof --help")(以及任何 --help/-h/help 形态)始终返回:

Command executed successfully (no output)

而终端里直接敲 --help 是有完整输出的。

原因

两个原因,都在 malice-network-dev/client/core/

  1. execute_command 捕获不到 Cobra 的 help 文本

    • execute_commandexecuteCommandWithTaskWaitutils.go:204)用 client.Stdout.Range(start, now) 截取输出。
    • client.StdoutNewStdoutWrapper(os.Stdout)external/IoM-go/client/log.go:16)——一个环形缓冲 wrapper,只有「写到这个 wrapper」的内容才会被 .Range() 截到
    • Cobra 的 --helpcmd.OutOrStdout(),全仓库没有任何 cmd.SetOut(client.Stdout)(grep 无命中),所以 help 直接打到原始 os.Stdout,绕过了 wrapper 缓冲 → .Range() 截到空串 → MCP 返回 "no output"。
    • 对照:module list / whoami 的输出走 console 日志路径(logs.Log.SetOutput(client.Stdout)console.go:247),所以能被截到。
  2. MCP server 自己埋了个误导 Tip

    • mcp.go:448 在每次 search_commands 返回末尾硬编码:

      sb.WriteString("Tip: Use execute_command(\"<command> --help\") for detailed usage.")

修复思路

既然 Cobra 的 help 走 stdout wrapper 捕获不到,就在 execute_command handler 里短路 help 请求:识别出 --help/-h/help,用 cmd.SetOut(&buf) + cmd.Help() 把 help 文本抓进缓冲再返回,绕开 stdout 捕获通道。

问题二:MCP 执行 session newbindnot found pipeline

现象

通过 MCP 客户端执行 session newbind --pipeline <bind_pipeline> --target <ip:port> 时,报错 rpc error: code = Unknown desc = not found pipeline,bind 会话无法建立;同一操作在 tui/webui 客户端正常。

根因

1. 触发链路与失败点

session newbind 在客户端的执行流程为两步 RPC(client/command/sessions/new.go):

// 第一步:注册会话
con.Rpc.Register(con.Context(), &clientpb.RegisterSession{...})
// 第二步:下发 init spite,驱动 listener 去 dial 目标 bind 马
con.Rpc.InitBindSession(sess.Context(), &implantpb.Init{Data: sess.Raw()})

失败发生在第二步。服务端 InitBindSessionserver/rpc/rpc-implant.go:222)经 GenericHandlerserver/rpc/generic.go:337)调用 loadPipelineStreamForSessionserver/rpc/generic.go:324)查找 listener 为该 pipeline 注册的流:

func loadPipelineStreamForSession(sess *core.Session) (interface{}, bool) {
	if sess == nil || sess.PipelineID == "" {
		return nil, false
	}
	// ① 以 "listenerID/pipelineID" 复合键查找
	if streamVal, ok := pipelinesCh.Load(core.PipelineRuntimeKey(sess.ListenerID, sess.PipelineID)); ok {
		return streamVal, true
	}
	// ② 退化到裸 pipelineID 查找,但前提是 ListenerID 非空
	if sess.ListenerID != "" && runtimePipelineNameCount(sess.PipelineID) <= 1 {
		return pipelinesCh.Load(sess.PipelineID)
	}
	return nil, false   // ← 在这里返回 false,上层转为 ErrNotFoundPipeline
}

pipelinesCh 的写入方(server/rpc/rpc-listener.go:86,listener 的 SpiteStream 建立时)只写复合键

pipelineKey := core.PipelineRuntimeKey(listenerID, pipelineID)  // 例如 "listener/test"
pipelinesCh.Store(pipelineKey, stream)

因此查找命中的唯一现实路径是 ①,它要求 sess.ListenerID 非空且与 listener 注册流时使用的 ID 一致。

2. 为什么 MCP 客户端会失败

会话的 ListenerID 来自客户端注册时提交的 RegisterSessionserver/internal/core/session.go:112ListenerID: req.ListenerId)。而修复前的客户端代码根本没有设置这个字段client/command/sessions/new.go:32-43):

// 修复前:没有 ListenerId 字段
_, err := con.Rpc.Register(con.Context(), &clientpb.RegisterSession{
	PipelineId: PipelineID,
	RawId:      encoders.BytesToUint32(rid),
	SessionId:  sid,
	Target:     target,
	Type:       consts.ImplantMaleficBind,
	...
})

于是 sess.ListenerID == ""

  • 路径 ①:复合键变成 "<空>/test",与 "listener/test" 不匹配;
  • 路径 ②:sess.ListenerID != "" 条件不成立,直接跳过;
  • 结果:loadPipelineStreamForSession 返回 false → GenericHandler 回滚任务并返回 types.ErrNotFoundPipelineserver/rpc/generic.go:346)。

tui/webui 客户端构建的 RegisterSession 携带了 ListenerId(如 "listener"),所以同样的 pipeline 在那些客户端上工作正常。这也是"运行时新建的 bind pipeline 报 not found pipeline,而服务端启动时恢复的 pipeline 正常"这一表象的来源——早期排查时曾误判为"运行时创建的 pipeline 没进注册表",实际是客户端没有提交 listener 归属

修改思路

  1. 修客户端,而不是修服务端
    • RegisterSession.ListenerId 字段在 proto 契约中早已定义(第 3 字段),服务端会原样落库,tui/webui
    一直依赖它正常工作。
    • 是 MCP 客户端没有履行契约,所以只需补齐客户端的字段提交;若改服务端去兼容空 ListenerId,属于放大改动面。
    2. 从本地缓存自动解析,而不是要求用户传参
    • --pipeline 参数本身就是 pipeline 名,客户端缓存(con.ServerState.FindCachedPipeline)中已有完整的 pipeline →
    listener 映射。
    • 新增 resolvePipelineListenerID() 辅助函数,注册时自动查出 ListenerId 填入 RegisterSession,对用户完全透明,CLI
    用法不变。
    3. 查不到时降级返回空串,而不是报错
    • 保持与旧行为一致的降级路径(服务端 validateReRegisterPipeline 对空 ListenerId 有 core.Listeners.Find
    兜底),不引入新的失败模式。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions