Real World CTF 2018 ccls-fringe命题报告

上周日给Real World CTF 2018出了一道forensics题ccls-fringe,向解出此题的31支队伍表达祝贺。

上一次出题已是2016年,一直没有人教我pwn、reverse、web所以只能从平常接触的东西里勉强抽出要素弄成题目。

kelwya找我来出题,他说forensics也可以,阻止了我说另请高明。另外也想给自己的项目打广告,萌生了从ccls的cache弄一道forensics的想法,因为惰性拖了两个星期没有动手。之前一次LeetCode Weekly前在和一个学弟聊天,就想把他的id嵌入了flag。LeetCode第一题Leaf-Similar Trees没有叫成same-fringe,所以我的题目就带上fringe字样“科普”一下吧。

下载ccls-fringe.tar.xz后解压得到.ccls-cache/@home@flag@/fringe.cc.blob。这是存储的cache文件。

这里体现了ccls和cquery的一个不同点。ccls默认使用"cacheFormat":"binary"了,这个自定义的简单序列化格式,cquery仍在使用JSON。

写段程序读入文件,用ccls::Derialize反序列化成IndexFile,用ToString可以转成JSON字串,阅读可以发现其中一个变量int b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"usr": 7704954053858737267,
"detailed_name": "int b",
"qual_name_offset": 4,
"short_name_offset": 4,
"short_name_size": 1,
"hover": "",
"comments": "flag is here",
"declarations": [],
"spell": "16:80-16:81|1935187987660993811|3|2",
"extent": "16:76-16:81|1935187987660993811|3|0",
"type": 52,
"uses": [],
"kind": 13,
"storage": 0
},

clang -fparse-all-comments会把非Doxygen注释也嵌入AST,注释暗示了我们应该找和它类似的变量。spell差不多表示spelling SourceRange,第80列很奇怪。写一段程序收集位于80列的其他单字符变量,按行号排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "indexer.h"

const char blob[] = ".ccls-cache/@home@flag@/fringe.cc.blob";

__attribute__((constructor)) void solve() {
std::string content = *ReadContent(blob);
auto file = ccls::Deserialize(SerializeFormat::Binary, blob, content, "", {});
std::vector<std::pair<int, char>> wod;
for (auto &[usr, v] : file->usr2var) {
Maybe<Use> spell = v.def.spell;
if (spell) {
Position start = spell->range.start;
if (start.column == 79)
wod.emplace_back(start.line, v.def.detailed_name[4]);
}
}
sort(wod.begin(), wod.end());
for (auto t: wod)
putchar(t.second);
puts("");
exit(0);
}

Makefile指定编译选项和链接选项,除了LLVM只有rapidjson一个依赖了。

1
2
3
4
5
6
7
8
9
10
11
CCLS := $(HOME)/Dev/Util/ccls
LLVM := $(HOME)/Dev/llvm

LDFLAGS := -shared
CXXFLAGS := -std=c++17 -fPIC -g
CXXFLAGS += -I$(CCLS)/src -I$(CCLS)/third_party -I$(CCLS)/third_party/rapidjson/include
CXXFLAGS += -I$(LLVM)/include -I$(LLVM)/Release/include
CXXFLAGS += -I$(LLVM)/tools/clang/include -I$(LLVM)/Release/tools/clang/include

solve.so: solve.cc
$(LINK.cc) $^ -o $@

编译成ccls.so,让ccls吐出flag:

1
2
% LD_PRELOAD=./ccls.so ~/Dev/Util/ccls/Debug/ccls
blesswodwhoisinhk

这只是这人用的id的一个子串,他教育我要多做Codeforces,是很好玩的(可惜我依然这么菜,呜呜😭😭😿😿)。

把same-fringe problem写成coroutine形式也是为了纪念一个去New York City的同学,他在Stanford读研期间,给一门课当助教时曾让我校对一个课程实验Cooperative User-Level Threads。

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
#include <iostream>
#include <vector>
#include <ucontext.h>
using namespace std;

struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
};

struct Co {
ucontext_t c;
char stack[8192];
TreeNode *ret;
Co(ucontext_t *link, void (*f)(Co *, TreeNode *), TreeNode *root) { {int b; /* flag is here */}
getcontext(&c); {int l;}
c.uc_stack.ss_sp = stack; {int e;}
c.uc_stack.ss_size = sizeof stack; {int s;}
c.uc_link = link; {int s;}
makecontext(&c, (void(*)())f, 2, this, root);
}
void yield(TreeNode *x) { {int w;}
ret = x; {int o;}
swapcontext(&c, c.uc_link); {int d;}
}
};

void dfs(Co *c, TreeNode *x) { {int w;}
if (!x) return; {int h;}
if (!x->left && !x->right) c->yield(x); {int o;}
dfs(c, x->left); {int i;}
dfs(c, x->right); {int s;}
}

class Solution {
public:
bool leafSimilar(TreeNode *root1, TreeNode *root2) { {int i;}
ucontext_t c; {int n;}
Co c2(&c, dfs, root2), c1(&c2.c, dfs, root1); {int h;}
do { {int k;}
c1.ret = c2.ret = nullptr;
swapcontext(&c, &c1.c);
} while (c1.ret && c2.ret && c1.ret->val == c2.ret->val);
return !c1.ret && !c2.ret;
}
};

void insert(TreeNode **x, TreeNode &y) {
while (*x)
x = y.val < (*x)->val ? &(*x)->left : &(*x)->right;
*x = &y;
}

int main() {
TreeNode xs[] = {{3},{1},{5},{0},{2},{4},{6}};
TreeNode ys[] = {{5},{3},{6},{1},{4},{0},{2}};
TreeNode zs[] = {{3},{1},{5},{0},{2},{6}};
TreeNode *tx = nullptr, *ty = nullptr, *tz = nullptr;
for (auto &x: xs) insert(&tx, x);
for (auto &y: ys) insert(&ty, y);
for (auto &z: zs) insert(&tz, z);
Solution s;
cout << s.leafSimilar(tx, ty) << endl;
cout << s.leafSimilar(tx, tz) << endl;
}

假如你用clang trunk (>=7),ccls可以检索macro replacement-list中的引用。某些限定条件下template instantiation得到的引用也能索引。

代码

https://github.com/MaskRay/RealWorldCTF-2018-ccls-fringe

ccls的xref功能可以看https://github.com/MaskRay/ccls/wiki/Emacs里的截图。Vim/NeoVim用户也强烈建议看看Emacs能达到什么效果。最近也加了一个vscode-ccls,但我不用VSCode因此没有精力维护。