Aggregator
dotnet反序列化之并不安全的SerializationBinder
.NET反序列化漏洞之绕过 SerializationBinder 不安全的类型绑定
BBScan2.0 : 在大量目标中快速发现潜在漏洞目标
Laravel反序列化 POP Chain3分析
Maintaining a sustainable strengthened cyber security posture
数据访问控制的未来
CVE-2022-30333 UnRAR 一个有趣的解压缩路径穿越漏洞
安全运营中的遗珠
Google CTF 2022 d8: From V8 Bytecode to Code Execution
This weekend I have played Google CTF with r3kapig. On the first day I tried the OCR challenge but failed to solve it, and on the second day I spent the whole day working on the d8 that I am more familiar with. Finally I managed to solve it at midnight as the second blood. This challenge is quite interesting so it is worth to do a write-up.
0x00 OverviewIn this challenge, we need to exploit a runner.cc that takes binary input and passes it to v8::ScriptCompiler::CachedData, which is to be executed. After some investigation, we found that we can use such primitive to execute arbitrary V8 bytecode. It turns out that V8 bytecode execution has many out-of-bound primitives that can be exploited because they are deemed as trusted input by V8. The final solution utilizes an out-of-bound read in CreateArrayLiteral to fetch a faked ArrayBoilerplateDescription, leading to an object faking primitive and thus code execution with regular exploitation technique.
0x01 V8 Code CachingMore information about this can be found here and here. To make it simple, it is a technique that allows V8 to avoid having to parse a same script for many times. When a JavaScript script is compiled, a cache that stores the compilation result can be generated, and when such script is encountered again, such cache can be used instead of re-compiling the same script again.
Generating Code CacheAccording to the documentation in the link above, we need to use v8::ScriptCompiler::kProduceCodeCache or v8::ScriptCompiler::GetCodeCache to generate cache for a script being compiled. However, non of these is found in the given V8 version. After checking test-api.cc, we found that we need to use v8::ScriptCompiler::CreateCodeCache to generate the code cache for a script that was just compiled and executed. Yes, it turns out the documentation makes the mistake. Note that we must call CreateCodeCache after script->Run(context), otherwise some lazily compiled functions are not cached.
In addition, v8::V8::SetFlagsFromCommandLine can be used to allow the script to use native syntaxes such as %DebugPrint. This can make debugging much more convenient. Note that the %DebugPrint is compiled into code cache in the V8 bytecode, so when such cache is executed in the runner.cc, the %DebugPrint output can still be shown.
Another thing to note is that when runner.cc loads the cache, an empty script string is also provided. The cache loader would check if the hash inside the binary cache is identical to the hash of the script, if not the cache will be rejected. After some debugging, we found that the hash of empty script is 0 and the hash of binary cache is the 4 bytes at offset +8. Therefore to allow the cache to be executed such field is set to 0.
Also, the cache generated by debug/release version can not be shared among each other, otherwise the cache will be rejected. In debug version, a flag FLAG_verify_snapshot_checksum is set to perform some additional checksum checking, to disable this, this flag is manually set to false at function SerializedCodeData::SanityCheckWithoutSource.
At this point we can generate the cache for our JavaScript code that can be run by runner.cc. The full code about this is here. We can use ./gen exp.js --allow-natives-syntax --print-bytecode to compile the JavaScript into cache and store the binary cache into ./blob.bin. We use --print-bytecode to see V8 bytecode being generated, and such bytecode can also be found in generated cache.
0x02 Exploiting V8 BytecodeInitially we are thinking if the raw machine code generated by JIT is also stored into cache, if so we can directly execute the shellcode by modifying them in the cache. However, after some trials, it turns out that thing cannot be easy like this. Therefore, it seems that we should use V8 bytecode to achieve the exploit.
By arbitrarily modifying the bytecode, the V8 easily crashes. I have come up with some exploitation ideas but only the last one works for me finally.
- Use bytecode to leak the hole into JavaScript, which is an exploitation primitive that has been previously used. However, it seems that this primitive is already mitigated in current version according to my friend sakura, so I have not spent much time on it, although it can potentially work.
- When V8 bytecode accesses the argument register, there is an index value byte in the instruction byte sequence. By modifying such byte, OOB can be caused. However, after further investigation, the OOB occurs on stack, and the data behind is not easily controllable. In addition, the argument array is not stored in compressed pointer form but 64-bit pointers, so it is hard to exploit by simply writing Smi numbers to stack. Therefore, this idea does not work.
- We found that CreateArrayLiteral also has an index value that is used to access a FixedArray in OldSpace. The value being fetched is a pointer to ArrayBoilerplateDescription, which describes how array is initialized. By controlling the content after the FixedArray, we can fake such ArrayBoilerplateDescription instance, so we can also obtain an object faking primitive. After then the exploitation is regular.
This is a bytecode that is used to create JavaScript array. Let’s write some test code to see how it works.
function foo() { const o = [[], 1.1, 0x123]; return o[0]; } foo(); readline(); // ./d8 test.js --print-bytecodeHere is the bytecode generated for this foo function:
79 00 00 04 CreateArrayLiteral [0], [0], #4 c4 Star0 0c LdaZero 2f fa 01 GetKeyedProperty r0, [1] a9 ReturnIt turns out that an instruction CreateArrayLiteral [0], [0], #4 is generated for the array creation. The question is how does interpreter know what elements to put into the array? The answer lies at [0] of CreateArrayLiteral. Such value is used as index to access an FixedArray, which is printed below the bytecode.
0x1b1f00253b31: [FixedArray] in OldSpace - map: 0x1b1f00002239 <Map> - length: 1 0: 0x1b1f00253b25 <ArrayBoilerplateDescription PACKED_ELEMENTS, 0x1b1f00253af9 <FixedArray[3]>>The index 0 accesses an ArrayBoilerplateDescription instance. This is an instance used to describe how the array elements are initialized. Let see what information it contains.
pwndbg> job 0x1b1f00253b25 0x1b1f00253b25: [ArrayBoilerplateDescription] in OldSpace - map: 0x1b1f000033f5 <Map[12]> - elements kind: PACKED_ELEMENTS - constant elements: 0x1b1f00253af9 <FixedArray[3]> pwndbg> job 0x1b1f00253af9 0x1b1f00253af9: [FixedArray] in OldSpace - map: 0x1b1f00002239 <Map> - length: 3 0: 0x1b1f00253b0d <ArrayBoilerplateDescription PACKED_SMI_ELEMENTS, 0x1b1f00002261 <FixedArray[0]>> 1: 0x1b1f00253b19 <HeapNumber 1.1> 2: 291 pwndbg> p/x 291 $1 = 0x123The ArrayBoilerplateDescription instance contains a constant elements field which points to another FixedArray. Such FixedArray contains the elements to be initialized for the newly created array. One interesting point to note is that the first element is another ArrayBoilerplateDescription pointer instead of a JSArray pointer, and this makes sense: each time when we create an array, we want a new JSArray instance to be created instead of using the reference to the old JSArray.
An important question to ask is that if we can fake such ArrayBoilerplateDescription instance used for CreateArrayLiteral, can we have object faking primitive? After manually modifying pointers inside the constant elements to an existing JavaScript object pointer, the answer turns out to be yes. Therefore, the next question is how to fake the ArrayBoilerplateDescription via OOB read of CreateArrayLiteral instruction.
Controlling Memory after FixedArraySince the FixedArray is in the OldSpace, it is quite intuitive to try to create an Array with double elements and calls garbage collection to put it into OldSpace, and to see if the array elements can locate at memory after the FixedArray(e.i. the OOB read victim). However, it seems that the element content is too far away from the FixedArray, so this approach does not work.
Then we found that the content inside constant elements is very close to FixedArray, but it is before instead of after the FixedArray. However, if we create another function that also contains a CreateArrayLiteral instruction, its constant elements can reside after the target victim FixedArray, as long as this function declaration is located after the victim function. In addition, if the array created contains only double elements, its constant elements is a FixedDoubleArray, which means unboxed double is stored in the memory, so we can fully control the memory content after the victim FixedArray!
The specific OOB index is found by some debugging and trials. Initially we set the unboxed double to As, and then we inspect the memory after the target victim FixedArray in the gen.cc process (debug version). Note that we cannot do so in runner.cc because we cannot print the bytecode there. Nonetheless, fortunately the memory layout is very similar among them. With this we calculate an index value and set it as index used by instruction CreateArrayLiteral, and then we can run the challenge binary with the modified cache. If a crash occurs with address 0x????41414141, the index is valid for OOB access.
Final ExploitAt this point the exploitation steps should be clear:
- Prepare a large double array, and the low 32 bits of its element address are fixed (V8 pointer compression). Such array is used to prepare the faked instances such as ArrayBoilerplateDescription, FixedArray, Uint32Array, etc.
- Spray the low 32 bits of the address of the elements of the large array as FixedDoubleArray after the FixedArray used as OOB read victim. At the large array, the ArrayBoilerplateDescription should be faked (Note that in pointer compression mode low 32 bits of pointers to built-in instances such as Map are fixed).
- Call the victim function, whose CreateArrayLiteral instruction should be modified beforehand to cause the OOB read, and we return the element as the faked object.
- As long as we have the faked object, the exploitation is very regular, which I will not discuss here.
The full exploit is here. We firstly need to compile this exploit to a cache binary, and then use the Python script to locate and modify the CreateArrayLiteral instruction, and then use the modified cache as the final exploit to be used for the challenge binary.
关于我大学这四年的碎碎念
文中所有提到的人名均使用代称或 ID。不过我想你应该都知道他们是谁。
去年六月份看到 @Li4n0 毕业时在博客写了篇同名文章,终于,我也到这个时候了。当时我对一年后的自己会身在何处还抱有疑问,而现在我正坐在跟同学合租的公寓房间里,面前是巨大的落地窗,窗外一片漆黑的夜幕。两周前我参加完学校的毕业典礼后,收拾好东西便匆匆忙忙地搬过来了,期间没有什么太多的仪式感,照片也只是三三两两地拍了几张。 但静下来想想才意识到,我已经毕业了。四年前刚到杭电的第一天晚上,我坐在宿舍的书桌前写下了《你好,四年。》,字里行间充满了我对这四年的幻想与展望。四年后回过头来看,当时立下的目标,有的落空了,有的结果出乎意外,也有的被忘在脑后不了了之了。
我打算写篇文章来记录一下这四年来发生的一些难以忘怀的事情,也记录下自己的感悟。可能内容有些流水账,不过问题不大,反正这篇文章最重要的读者也只是我自己。
大一 · 新奇与随心所欲其实从 6 岁接受义务教育开始到高考结束为止,我们每个阶段的目标都很明确。读小学就是为了能上好初中,读初中是为了中考,读高中是为了高考…… 每进入到一个新的环境,其实我们的长期目标就已经确定好了,身边的人也都是奔着同一个目标去努力。 因此那个时候对与错其实很简单,能让你学会知识的就是好方法,让你疲惫懈怠的就是坏东西。但是上了大学后,这一层约束突然消失了——四年后我可以选择考研,可以选择就业,可以选择考公…… 因为最终的目标不明确,所以就想先尝试自己喜欢的事情。 刚读大一的时候,我突然有了相比高中多好几倍的时间,在那段时间里我的进度是飞速的,2018 年 10 月的时候我专门记录了下自己当时学了什么,除了精进高中时学的 PHP 老本外,还接触了 Docker、Android 开发等。当时真的就是积压了好多年的兴趣欲望,一下子喷发出来了。 同时在室友的推荐下加了 Vidar 的招新群,自己也在一次晚自习时得知了杭电助手,两边都报了名。现在想想这真的是个绝佳的选择。 因为花了过多的时间在整自己的这些东西,我开始逃一些不想上的水课,一些不喜欢的课程作业也是到了快交的时候才匆匆忙忙地补上。所以成绩一直不大好,甚至期中考试过后还被班主任约着谈了话。(对!是班主任,不是辅导员!没想到真的有班主任,这也是我第一次以及最后一次见到她)但其实我一直都不怎么放在心上,反倒是两个社团那边混得风生水起,又是写项目又是学 CTF。
刚进入大学的学生,其实对于学校,自己所在的学院,或多或少地都有一种崇拜感与归属感。但之后在 Vidar 招新群以及身边同学言论的影响下,逐渐产生了一种“学校真垃圾,学院老师专业课讲得一塌糊涂,教不了你真东西”的看法。这个看法现在看来其实是有些偏激的,网安学院的老师确实大部分水平都不大行,这个是事实。但这并不代表他所教授的这门课没有用! 这是我当时不自觉掉入的一个陷阱。讲数据结构的老师可能很垃圾,只会对着 PPT 照本宣科地念,但这不代表数据结构与算法这门课本身在计算机科学中不重要。你可以贬低老师,逃课不听他讲,作业可以不交,但是你得从其他渠道去认真学习这门课程,要不自己看书,要不看额外的网课。不能因为老师垃圾,就把这门课也放弃了。
整个大一上学期其实就是按班就部地上着课,平时自己看看书,参加下杭电助手的部门例会和 Vidar 的新生培训。自己也会整些花活,国庆放假的时候用以前学到的 Web 知识整了个解谜游戏。偶尔也会拍点视频剪剪片子。寒假坐高铁回家的时候,还自己拍了个 vlog,自己在高铁上把片子剪完的。 大一的体育课是打太极,可四肢不协调的我根本没好好练,期末考试打太极,记成绩的老师直接跟我说准备补考吧,我那时瞬间就慌了,不过好在最后被老师 60 分给捞过了。这学期我也迎来了我第的一次挂科。因为是第一次,所以自己格外紧张。寒假的时候狂看考研的网课狂补线性代数,开学补考居然还考了 80 多顺利通过~ 寒假期间也没闲着,Vidar 的 HGAME 新生赛贯穿了我的整个寒假,这也是我 Web 安全的启蒙了。
大一下学期,因为在 HGAME 排名靠前,我成功地加入了 Vidar。那个时候的 CTF 比赛还没如今这么卷,Web 单凭自己一个人还能抢个二血三血,也不像现在这样什么牛鬼蛇神都挑出来,这个师傅那个师傅的膜,出的题也不是无脑套娃的体力活。那个学期参加了 Vidar 的 AWD 比赛,也跟着协会的小伙伴一起去天津线下度假一周打比赛,那场旅行是真的印象深刻。 大一下学期的课程也是我整个大学里最多的了,又多又难,最后也还是可惜挂了科。放暑假前我也是挺惆怅的,想着又得准备补考了 QAQ。传送门:暑假开始了啊……
大二 · 光辉与百念皆灰大一下学期的暑假其实我过得很安逸自由,在家代码写累了就一个人坐车去深圳湾看海,从深圳湾徒步走到高中的学校,再坐地铁去书城看有无新的技术书籍。 那个暑假我也是用自己三脚猫的 Go 语言水平,硬生生地用 Beego 框架把 Apicon 给写出来部署上线了,还熬夜画了很酷的架构图。传送门:Apicon 背后都用到的哪些技术? 现在回过头看那三年前的代码,感叹我这三年来确实成长了不少哈哈哈。 暑假快结束的时候,有幸跟着协会的学长去某省公安局护网,当时大家其实也都是第一次护网,没什么经验。只能靠着弱口令瞎试,最后主办方看不下去了还偷偷塞给我们新的目标,可惜最后成绩还是不怎么好。不过倒是一次很新奇的体验,那个省份因为靠近西北,所以烧烤外卖的羊肉串牛肉串是真的又大又好吃。
大二开学后,我便忙开始于社团招新。有什么是比欺负刚来的大一新生更有意思的呢? 大家多多少少都有些好为人师,总是喜欢言传身教,我对大一的新生就经常这样哈哈哈。大二上学期也跟着协会的小伙伴们去了天津的第五空间线下赛以及首届字节跳动 ByteCTF,前者保底拿了个 5000 的奖金,后者拿了个第六名的不错成绩。全靠 @Li4n0 Web 带我飞了,当时第一天发现靶机 SSH 要密钥登录,之前写的脚本全都用不了,人直接蒙了。 这学期协会也举办了第一届 D^3CTF,当时是第一次去日租房参加运维。(虽然自己线上没出题,只是去骗吃骗喝的。)线下赛可谓惊心动魄,当时的比赛平台其实很不稳定,我们有一大半的时间是在修平台,自己也是两天没睡觉。直到比赛的第一天下午,在旁边沙发上一倒直接睡到傍晚。 这学期也是我第一次没有挂科的学期。早在开学的时候,隔壁宿舍的同学就跟我说这学期学的计算机组成原理会很难,挂科率很高。当时我那个慌的,想着绝对不能挂了,不然补考就糟了。计组的课是安排在每周三周四的早上八点,当时我每天早早买好早餐,都提前 15 分钟到教室,坐第一排认真听讲。这可能是我大学为数不多的认真从头到尾听讲的课。授课的老师是当时的学院副院长,也是一个很有趣的人,我很喜欢。期末考试的卷子只有两道大题,一道 40 分,一道 60 分,难度确实大,考察也很全面。不过最后我以八十多的高分通过了,可喜可贺可喜可贺。 这个学期结束的时候,我也因为给 bilibili 交了两个高危安全漏洞,而赚到了 8000 元,美滋滋地回家过年。 可以说,我的大二上学期,是我大学四年的光辉时刻。
而大二上学期的寒假,也就是 2020 年初,很遗憾,新冠疫情来了。 2020 年的疫情深深地改变了这个世界原本的运作方式。原本 2 月就要返校的寒假,被疫情硬生生地拖到了 5 月,我在家里被迫上着网课,作息极度不规律。因为久久没出门,再加上看到电视上的种种负面新闻,整个人的心理也是很难受的。也是在当时入坑了 Vtuber,开始推 Overidea,感谢 Overidea 陪伴我度过了疫情期间一个又一个夜晚。 但疫情导致的这三四个月的寒假,其实也是一种机遇。我在这段时间里,将之前 D^3CTF 的平台进行重构 —— Cardinal 诞生了。可以说她贯穿了我整个 2020 年,从第一次开源,到补开发文档,到建立用户交流群,再到开源 3D 大屏…… 期间我认识了不少人,也积攒了很多宝贵的经验。我开始认识到做开源的我并不能满足所有人的需求,也不是所有人都对我抱有善意。我应该选择性地去对待他们。
2020 年 3 月的时候,我收到了某大厂的面试邀约,因此我也就投了他们的实习岗。那是我人生中参加的第一次面试,可惜结果并不理想,最终没能通过。那段时间我对自己也陷入了深深地质疑,怀疑自己是不是不适合学计算机。一直以来以兴趣为导向的我却不得不被逼着去学去做一些我不喜欢的东西,这让我很难以接受。就这样消沉了一段时间后,有天下午我翻邮箱的时候看到了一封邮件,这便是我加入 ForkAI 的开始。何老师通过 Vidar 的官网找到了我的博客,然后给我发了邮件,问我有没有兴趣来做逆向相关的事情。我的方向其实是 Web 而非逆向,但我还是回复说可以试试。自己调研了下之后回复说可能需要一台 iPad 真机进行调试分析,何老师问我要了家庭地址后,没几天 iPad 就寄到了。当时我其实挺吃惊的,我和他素不相识,但他却能如此地信任我。之后的事情,很多人其实也都知道了,我大学的后半段时间几乎都投入在了公司这边,这两年多来,我遇到了形形色色的人与事情,数不胜数。
2020 年 5 月初,学校开始安排学生陆续返校,当时急不可耐地我赶紧买了最早的机票回了杭州。现在想想,还是自己家里最舒服。回到学校后,因为没有血清报告,我被强制带去空宿舍楼隔离。隔离的第一天我还很不情愿,但后来慢慢爱上了这种一个人住在大宿舍里,每天睡到自然醒,每天有人送饭,床下就是电脑还有网络的生活了。隔离结束后我还有点念念不舍。
隔离结束后,我像平时一样回到了平淡的日常校园生活中。当时的我以为日子将会这么无忧无虑地过下去,但在五月底发生的一件事,给我的之后的大学生活蒙上了一层厚厚地阴影。这件事我不想再去回顾,当时它给我的打击是巨大的,甚至让我产生了要轻生的想法。它阴差阳错的发生了,但凡其中任何一个步骤变动下,都不至于是当时那个结果。虽然这件事最后如愿以偿地顺利解决了,但它已经给我留下了无法抹去的痕迹,可能在五年后十年后的某个夜晚,我会在睡梦中再次忆起此事,然后惊醒。大二下学期剩下的时间,我也在极力调整着自己的心理状况,让室友带我出校吃些好吃的,晚上买几瓶酒回宿舍麻痹自己。那对我来说真的是特别阴暗的一段时光,我冤屈而又无助,我努力安慰着欺骗着自己,我看到了人性的懒惰与官僚主义的尸位素餐,我站在原地又无可奈何。
大三 · 无功与阴差阳错时间到了大二的暑假,这个暑假我除了忙于公司的事情之外,我还在跟着协会打比赛。最终是拿到了 CyBRICS CTF 2020 全球第七名,GACTF 2020 第一名的好成绩。当时暑假还有个很令我记忆犹新的事情,是 Maro 因为没有买到回家的高铁票而在我家住了一晚,这也是我人生第一次有同学到家里来过夜的,让同学看到自己脏乱的房间真的很不好意思 QwQ。
大三上学期开学后,那个学期我一连参加了好几个比赛,凡是有的线下赛我几乎都报名了。可惜的是都没能取得啥好的成绩。唯一的收获就是游览了祖国的大好河山。(其实也是假的,天天在酒店里也不出去)国赛第二天改赛制,被 ylb 的平台给恶心到了,改成解题赛后成绩并不理想,12 月末的 XUNCA 决赛是在深大体育场,我也带着大学室友游览了一遍我从小长大的地方。这种体验真的很梦幻,小时候的我一定不会想到,十多年后我会带着大学同学再次回到这些熟悉的地方。 大三的课其实也不少,令我印象很深刻的是高老师。我那个学期有两门是她的课,她应该也是当老师不久,比我们大不了多少。所以她很明白学生们的小心思,也很为学生着想。看到我作业晚交了,验收次数不够,一直会催着我去做。她是那种我愿意敞开心扉跟她聊的老师,当时自己随手写了个提醒我按时交作业的 bot,我第一时间就想与她分享。最后期末验收也是很戏剧性,我一学期的课几乎没有听,可验收的时候高老师问的每个问题我都能答上来不少,甚至还是对的。她都开始怀疑我是不是假装自己没听过课了哈哈哈。可能真的是凭直觉的运气好吧。 大三的寒假特别短,因为疫情影响也是没有回湖南。很久没有在深圳过年了,那段时间还是属于沉《魔女之旅》的时候,每天晚上代码写累了就躺床上补小说。深圳的冬天确实不冷,白天甚至可以只穿一件长袖,在床边坐上一天。
大三下学期回到杭州后,公司那边开始忙起来了。我将更多的时间投入到了工作上。4 月份举行了一次到安吉的团建,团建前的几天我的电脑主板还烧了,只能赶紧去西湖苹果店买一台新的。最后还好也是赶上了那次的客户交付。大三下学期这一年公司人员流动以及动荡挺大的,期间我也多次感觉十分疲倦有些撑不下去了。甚至精神恍惚到从出口进学校图书馆,被门口保安叫住后我还一脸疑惑地想他为啥不让我进去。比较可惜的是大三后半年下来,在公司的一些事情开了又停,这么断断续续的导致最后没有几个事情是能完整做完的,可时间和金钱确实已经被浪费了。 我其实除了大四之外,每年都会去一次上海。大一的时候是去看 Mili 的演唱会和 Vueconf 2019,大二是去看 Bilibili Macro Link 2020,大三是去找队长吃饭顺便去拜访了无闻老师的家。算是年度任务了。杭州到上海确实很方便,一般来说清晨七八点钟高铁过去,晚上六七点钟的高铁回来。
大四 · 坚韧与得偿所愿时间来到了大四上学期,因为大三的时候经常在公司,翘了不少课,导致一门很蠢的选修课居然被老师给挂了。无奈只能大四再选两门课把学分给补上。可能因为是大学的最后一年了,这两门课我都按时到教室听课了,作业也都完成了。期末靠前老师划重点的时候全程记录下来,两门课都自己整理了相关的资料进行复习。当然,最后当然也都顺利通过了。还记得期末考试时,我一个大四的学生在考场门口遇到大二学弟的场景。😅
大四上学期还有个很重要的事,那就是毕业设计了。因为之前发生的事情,我想把毕业这些事尽早做完,尽早毕业。因此在大四上学期就选上了第一批的毕业设计。那一排选题自己其实都不怎么感兴趣,最后挑了个 XSS 平台的开发。找到导师说我对这个选题是多么多么地感兴趣,自己也有很多想法。导师回复我课题已经被选了,后来又反转成之前那个学生退了选题。(后来得知我的导师这次毕设只带了我一个学生,其他要考研的学生都被他劝退了) 毕设刚开始的时候我根本没咋当回事,就突击了一下做了 20%。后面导师催着我要开题的时候,才提前一星期写了开题报告和 PPT,当时我表现的挺慌的,身边的人还以为我参加的是最终的论文答辩,听闻居然只是开题后都纷纷表示不屑。开题报告上,某个大一时单独找我聊过天,后来风评逐渐转差的老师开始问及我的一些工作就业相关的问题,我其实挺明白的他就是想显摆刷存在感;但是我也不至于模仿什么爽文男主,把实情透露给他打他的脸。最后只是微笑着搪塞过去了。 进入冬季后,论文查重与答辩也快来了。当时我的进度其实是慢了一截的。甚至离查重还有一个半星期了,我论文还没开始写!所以我当时给自己定下的目标就是每天必须得写满 2000 字才能睡觉。所以那段时间每晚几乎都是两三点才睡。印象很深刻的是有次在公司加班,太晚了便在公司附近找了个公寓酒店住,那个老板跟我说帮我换了个大房间,结果一进房间味道挺大的,也就将就着住了。那天晚上肝到三四点钟,一看手机没电了,但是又没有充电线,电脑端饿了么的 H5 页面也用不了,我直接人傻了。最后是跑到了楼下大堂,看到有接充电宝的,找楼下保安帮我接了个才解决。后来论文查重前也是挺惊险的,那天晚上十点收到导师的微信,说第二天九点前要提交论文查重,我一看还剩两三千字,还要改格式,人直接傻了。赶紧收拾好包背上电脑冲向了离学校最近的酒店。那天是一直肝到了第二天六点才写完,写完后在网上找了两家论文查重的网站,把查重结果和论文微信发给导师后,他居然秒回收到,看来都没有睡呢。发完后我便去补觉了,睡到中午一觉醒来导师微信告诉我查重通过了,可喜可贺。 最后的论文答辩,其实还蛮顺利的。台下老师问的问题也都是项目功能、代码量之类的,随便三两下轻松应对。答辩结束后,晚上出校吃了烤肉,回去的路上还买了一直很喜欢喝得椰椰奶冻。 不过答辩结束并不意味着毕业设计就结束了,关于论文还有一些内容和格式上需要修改的。这也是我导师唯一一次给我的指导 —— 一个 15 分钟的微信电话。他按点给我列出了要改的内容和要修正的字体格式。我后面按照他说的改好后,最终版直接拿去楼下电脑城装订了。所以我的论文几乎是没什么大的修改,也没被导师打回去过。我的导师全程也和我一样很摆,这使得他在下学期的毕设中也这么摆,结果不对劲了。(可能并不是所有的学生都像我一样优秀能够让老师省心吧哈哈哈)
大四下学期,因为疫情,寒假回到深圳后一直回不去杭州,每次都是见着好转准备买票的时候,突然新增了几例阳性。陆陆续续我退了有三四次票了。过年的时候回了趟湖南,期间住在奶奶的新房子里。湖南的冬天是湿冷的,这点跟杭州很不一样。杭州的冬天虽然了冷,但晚上我盖上被子,也就暖和了。湖南因为相对潮湿,被子上沾上了水汽在冬天是冷的。🥶 晚上睡觉的时候盖上被子反倒更冷了。 过年那会我本来是想在奶奶家静下心来写点开源项目的,可是却跑去挖洞了,期间确实也小有成果,自己也学到了不少。后来因为这些洞也赚了笔小钱。传送门:聊聊最近挖 Security Bounty 的感受 我也没想到两年后我居然又能在家里过生日,不过 22 岁的生日没给我有太大的实感,越长大越对过生日没兴趣了。小的时候会给自己整很多有仪式感的东西,现在就吃个蛋糕,这一天也就过了。
四月中旬,我回到了学校。在不到半个月后,“以为死去的回忆突然开始攻击我”,我才知道大二下学期的那件事居然还没完,并且也做好了是时候该做个了断的觉悟。之后的一个多月来我一直郁郁寡欢,不断地尝试去补救,世界也一次又一次地给我希望,然后重重地把我摔到地上。最后,就当我尝尽一切办法,自认为已无力回天时,这件事情最后却又再次出乎意料地完美收场了。当压抑了很久的悲痛突然被释放时,我反倒没觉得有多轻松,心里还是提高着戒备,我想接下来就自己慢慢调理,慢慢走出来吧。
最后快要毕业的一个星期,我穿上学士服,拍了毕业照。同时开始收拾我在宿舍的东西,曾经我收集的各种包装盒和小玩意,到做取舍的时候,全被我丢弃了。以前自己总想着这些东西今后保不齐突然要用上,一直舍不得丢,现在却是被暴力的拆开来检查一遍,然后直接丢掉。我没有做过多的停留,分两次打包好了宿舍的东西,搬到了我现在住的公寓里。甚至直到现在我还认为,只要我想,我便能再次走进学校。
Unlasting最后这一个段落用一首歌名来结尾。
以上就是我顺着回忆记录下来的我的大学四年。期间还有很多精彩的瞬间,遇到了很多有趣的人,抱歉由于篇幅原因以及我现在实在是太困了所以没有写。这并不代表他们不重要,相反他们可能重要到我会时常想起,甚至已经是我生活日常中的一部分了。 以上所有提到了和未提到的,都被我一一记录在了手机相册中。闲的时候我会翻着相册,将自己代入当时的心情。
我的大学四年生活已经结束了。这其中我遇到了种种出乎意料的事情。这也让我不敢做太长远的规划,因为它往往都不按我设想的方式进行,最终都是以以其它的结果呈现给我,我很少能遇到完美的如愿以偿。 我在这四年里表现的可能不是那么出彩,但是这四年真真切切地流过了。我迷茫地站在这个新的起点,义无反顾地向前走。