[NSSCTF 2021]招新赛Reverse出题人笔记

ASTJS

这段json其实是js语言的ast (抽象语法树)

ast 定义

ast js 用法

转换器

简单的解答

直接看json 其实并不长 就300多行 其实认真分析一下可以猜到

"value": "EXXH_MpjxBxYnjggrM~eerv" 是数据

"operator": "^" 是计算符

"value": 11 是数据

尝试一下 可知是简单的异或 flag NSSCTF{astIsReallyFunny}

这里其实出了点问题 原来异或后有一个不可见字符 复制的时候漏掉了

所以计算结果是 NSSCTF{as+IsReallyFunny} 不过如果分析一下题目名 就知道 flag其实是 NSSCTF{astIsReallyFunny}

深入一点

我们知道 ast 其实就是方便解释器执行的一种手段

所以有能转换回JS的程序吗 有的 https://github.com/estools/escodegen/

不过没法直接使用 因为AST的规则不完全一致 需要修改一下配置

https://github.com/estools/escodegen/

源代码

(function () {
function bE(str,key){
     var arr = str.split('');
     return arr.map((i)=>{
       return String.fromCharCode(i.charCodeAt()^key)
    }).join('')
  }
console.log(bE('EXXH_Mpjx•BxYnjggrM~eerv',11))
})();

easyapp

安装应用查看可知是一个输入flag判断对错的简单app

随便选择一个反编译dex的工具 反编译dex 转java可知

有3个关键的类 使用工具转java代码可知

  • Encoder
public class Encoder {
   private int key = 0x075bcd15;

   public Encoder() {
  }

   public String encode(String str) {
       StringBuilder sb = new StringBuilder();
       for (char c : str.toCharArray()) {
           sb.append((char) (c ^ this.key));
      }
       return sb.toString();
  }
}

分析代码 可知这是一个异或函数 把字符串的每一位和 key 异或

  • MainActivity
protected void onCreate(Bundle bundle) {
       MainActivity.super.onCreate(bundle);
       setContentView(0x7f0b001c);
       encoder = new Encoder();
       mainActlvity = new MainActlvity();
      ((Button) findViewById(0x7f080057)).setOnClickListener(new -$.Lambda.MainActivity.i-SDaQT6aGr2btgF05Lf-fvXXSo(this, (EditText) findViewById(0x7f080090)));
  }

   public void lambda$onCreate$0$MainActivity(EditText editText, View view) {
       System.out.println(encoder.encode(editText.getText().toString()));
       if (encoder.encode(editText.getText().toString()).equals("棿棢棢棲棥棷棊棐棁棚棨棨棵棢棌")) {
           Toast.makeText(this, "YES", 0).show();
      } else {
           Toast.makeText(this, "NO", 0).show();
      }
  }

分析代码 可知 用户输入经过 Encode 之后 和 一个静态字符串比较

如果仔细分析一下代码 可知 在创建时 调用了MainActlvity

  • MainActlvity
public class MainActlvity {
   public MainActlvity() {
       try {
           Field declaredField = Encoder.class.getDeclaredField("key");
           declaredField.setAccessible(true);
           declaredField.set(MainActivity.encoder, 0x3ade68b1);
      } catch (IllegalAccessException | NoSuchFieldException e) {
           e.printStackTrace();
      }
  }
}

分析代码可知 这里把 Encode key 的值掉包成了 0x3ade68b1

综合上述本题其实就是一个最简单的异或 把 key 掉包了 如果仔细分析了代码 或者使用动态调试 都能发现

最后异或一下得出flag NSSCTF{apkYYDS}

注意 这里有个小坑

key 和 字符 异或 的结果是大于char的上限的

在强制转换中会丢失数据

写python脚本时请注意这一点

pyre

丢进Exeinfo PE 一看

[ PyInstaller v.3.6  - 2005?019 - support Python 2.7, 3.5?.7 www.pyinstaller.org ] - stub :  x64 Microsoft Visual C++ v14 - 2015 - microsoft.com (exe 4883ec 28-48) , 
Overlay :  - .zlib Package

pyinstaller 使用 pyinstxtractor脚本 还原pyc

https://github.com/extremecoders-re/pyinstxtractor

把 code.pyc 随便扔进一个反编译程序、网站

比如 https://tool.lu/pyc/

#!/usr/bin/env python
# visit https://tool.lu/pyc/ for more information
import hashlib
import base64

def init(OOOOO00O0OO00OO00, OOO00O0000OOO0O00):
   OO00O000OOO0OOOOO = 0
   OOO00O0000OOO0O00 = hashlib.md5(OOO00O0000OOO0O00.encode()).hexdigest()
   OOO00OO0OOO00OOO0 = []


def Encrypt(O0000O0OO00OO00O0, OO0OO00O00O0O00O0):
   OOOO0O00O00OOOOO0 = OO0O000O000O0O0O0 = 0
   O00O00OOOO00O0000 = ''
   O00O00OOOO00O0000 = base64.b64encode(O00O00OOOO00O0000.encode())
   return O00O00OOOO00O0000

input_str = input('input flag pls:')
s = []
init(s, 'bJLVFYw3WI5ncGez')
if Encrypt(s, input_str).decode() == 'w4s1PUYsJ8OYwpRXVjvDkVPCgzIEJ27Dt2I=':
   print('good!')
   continue
print('nonono!')
continue

可以看到代码被混淆 而且反编译的不全

其实我是没想到没法反编译的 当时python版本选错了

本来是想用py3.6这种可以完美反编译的

结果打包的时候用了py3.9

qaq.....

导致这题难度直线上升 qaq

不过如果你对RC4很熟悉 其实可以直接看出来这就是一个RC4

如果不熟悉的话。。。

就只有手搓字节码了QAQ

可以参考

https://www.cnblogs.com/yinguohai/p/11158492.html

import hashlib
import base64
def init (OOOOO00O0OO00OO00 ,OOO00O0000OOO0O00 ):
   OO00O000OOO0OOOOO =0
   OOO00O0000OOO0O00 =hashlib .md5 (OOO00O0000OOO0O00 .encode ()).hexdigest ()
   OOO00OO0OOO00OOO0 =[]
   for O0O00OO00OOO0O000 in range (256 ):
       OOOOO00O0OO00OO00 .append (O0O00OO00OOO0O000 )
       OOO00OO0OOO00OOO0 .append (OOO00O0000OOO0O00 [O0O00OO00OOO0O000 %len (OOO00O0000OOO0O00 )])
   for O0O00OO00OOO0O000 in range (256 ):
       OO00O000OOO0OOOOO =(OO00O000OOO0OOOOO +OOOOO00O0OO00OO00 [O0O00OO00OOO0O000 ]+ord (OOO00OO0OOO00OOO0 [O0O00OO00OOO0O000 ]))%256
       OOOOO00O0OO00OO00 [O0O00OO00OOO0O000 ],OOOOO00O0OO00OO00 [OO00O000OOO0OOOOO ]=OOOOO00O0OO00OO00 [OO00O000OOO0OOOOO ],OOOOO00O0OO00OO00 [O0O00OO00OOO0O000 ]

def Encrypt (O0000O0OO00OO00O0 ,OO0OO00O00O0O00O0 ):
   OOOO0O00O00OOOOO0 =OO0O000O000O0O0O0 =0
   O00O00OOOO00O0000 =''
   for O0OO0O0O0OO0O0OOO in OO0OO00O00O0O00O0 :
       OOOO0O00O00OOOOO0 =(OOOO0O00O00OOOOO0 +1 )%256
       OO0O000O000O0O0O0 =(OO0O000O000O0O0O0 +O0000O0OO00OO00O0 [OOOO0O00O00OOOOO0 ])%256
       O0000O0OO00OO00O0 [OOOO0O00O00OOOOO0 ],O0000O0OO00OO00O0 [OO0O000O000O0O0O0 ]=O0000O0OO00OO00O0 [OO0O000O000O0O0O0 ],O0000O0OO00OO00O0 [OOOO0O00O00OOOOO0 ]
       O0OO0O0OOO00O0O00 =(O0000O0OO00OO00O0 [OOOO0O00O00OOOOO0 ]+O0000O0OO00OO00O0 [OO0O000O000O0O0O0 ])%256
       OOO000O0OOOOO00O0 =chr (ord (O0OO0O0O0OO0O0OOO )^O0000O0OO00OO00O0 [(O0000O0OO00OO00O0 [OOOO0O00O00OOOOO0 ]+O0000O0OO00OO00O0 [OO0O000O000O0O0O0 ])%256 ])
       O00O00OOOO00O0000 +=OOO000O0OOOOO00O0
   O00O00OOOO00O0000 =base64 .b64encode (O00O00OOOO00O0000 .encode ())
   return O00O00OOOO00O0000


while True :#line:35
   input_str =input ("input flag pls:")
   s =[]
   init (s ,'bJLVFYw3WI5ncGez')
   if Encrypt (s ,input_str ).decode () =="w4s1PUYsJ8OYwpRXVjvDkVPCgzIEJ27Dt2I=":
       print ("good!")
   else :
       print ("nonono!")

其实这里已经很明确了 就是RC4

init是初始化 s-box

解密后得到flag NSSCTF{more_qwq_lol}

re1

这道题出的时候出了点问题,有个地方位移12位写成了位移4位,导致解不出来只能爆破,加密后的数据是这样的(如下图)因为flag是NSSCTF{}的格式,所以要爆破十位,又因为flag由大写字母和下划线组成,所以可以减少爆破的范围,这就直接上个爆破的脚本了。

def get_flag(l):
   retlist = [1 for i in range(len(l))]
   for i in range(len(l)):
       if isinstance(l[i],list):
           tmplist = get_flag(l[i])
           retlist[i] = tmplist
       else:
           for n in range(len(l)):
               tmplist = []
               h4 = ord(l[n][-1])>>4
               l4 = int(s[-4*(len(l[n])+1):-4*len(l[n])],2)^h4
               for m in li:
                   maybe = (int(m,2)<<4)+l4
                   if chr(maybe) in box:
                       tmplist.append(l[n]+chr(maybe))
               retlist[n] = tmplist
           return retlist
   return retlist

result = 591620785604527668617886
ret = str(bin(result))[2:].zfill(80)
s = ret[8:52]
li = ['0000','0001','0010','0011','0100','0101','0110','0111']
box = 'QWERTYUIOPASDFGHJKLZXCVBNM_'
h12 = str(bin(ord('{')))[2:5].zfill(4)
r = int(s[-4:],2)^int(h12,2)
maybel = []
for n in li:
   maybe = (int(n,2)<<4)+r
   if chr(maybe) in box:
       maybel.append(chr(maybe))
retli = maybel
for i in range(9):
   retli = get_flag(retli)
print(retli)

re2

简单题直接贴脚本

result='bcfba4d0038d48bd4b00f82796d393dfec'
list = [47, 138, 127, 57, 117, 188, 51, 143, 17, 84, 42, 135, 76, 105, 28, 169, 25]
de = []
for i in range(len(list)):
   key = (list[i]>>4)+((list[i] & 0xf)<<4)
   de.append(key)
flag = ''
for i in range(len(result)//2):
   n = int(result[i*2:(i+1)*2],16)
   flag += chr(n^de[i])
print(flag)

re3

简单题直接贴脚本

import urllib.parse
key = "HereIsFlagggg"
enc = '%C2%A6n%C2%87Y%1Ag%3F%C2%A01.%C2%9C%C3%B7%C3%8A%02%C3%80%C2%92W%C3%8C%C3%BA'
enc = urllib.parse.unquote(enc)
s_box = list(range(256))
j = 0
for i in range(256):
   j = (j + s_box[i] + ord(key[i % len(key)])) % 256
   s_box[i], s_box[j] = s_box[j], s_box[i]
res = []
i = j = 0
for s in enc:
   i = (i + 1) % 256
   j = (j + s_box[i]) % 256
   s_box[i], s_box[j] = s_box[j], s_box[i]
   t = (s_box[i] + s_box[j]) % 256
   k = s_box[t]
   res.append(chr(ord(s) ^ k))
ret = "".join(res)
print(ret)

re4

简单题直接贴脚本

s = 'wesyvbniazxchjko1973652048@$+-&*<>'
result = 'v0b9n1nkajz@j0c4jjo3oi1h1i937b395i5y5e0e$i'
flag=''
for i in range(len(result)//2):
   p = result[i*2:(i+1)*2]
   flag += chr((s.index(p[0])-i)*17+len(s)-s.index(p[1])-i-1)
print(flag)

re5

python反编译,使用pyinstxtractor.py将编译过的exe文件反编译,获得源码的pyc,然后再找个pyc反编译的在线网站解一下就拿到源码了。

import random
import msvcrt

(row, col) = (12, 12)
(i, j) = (0, 0)
maze = [
  [1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
  [1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1],
  [1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,0,1],
  [1,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1],
  [1,0,1,1,1,1,1,0,1,0,1,0,1,0,1,1,1,1,1,0,1,1,1,0,1],
  [1,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0,1,0,0,0,1,0,0,0,1],
  [1,1,1,0,1,1,1,1,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1,0,1],
  [1,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,1,0,1],
  [1,0,1,1,1,0,1,0,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1],
  [1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,1],
  [1,0,1,1,1,1,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,1,1,0,1],
  [1,0,1,0,0,0,0,0,1,0,1,0,1,0,0,0,1,0,0,0,1,0,0,0,1],
  [1,0,1,0,1,1,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,1,1],
  [1,0,0,0,1,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,1,0,1],
  [1,1,1,1,1,0,1,0,1,0,1,1,1,0,1,1,1,0,1,0,1,0,1,0,1],
  [1,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,1,0,1],
  [1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,0,1,1,1,1,1,0,1,0,1],
  [1,0,1,0,1,0,0,0,1,0,1,0,1,0,0,0,1,0,0,0,1,0,0,0,1],
  [1,0,1,0,1,1,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,1,1,0,1],
  [1,0,1,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,1],
  [1,0,1,1,1,0,1,1,1,1,1,0,1,1,1,0,1,0,1,1,1,0,1,0,1],
  [1,0,1,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,1,0,1,0,1],
  [1,0,1,0,1,1,1,0,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,0,1],
  [1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1],
  [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1]]
print("Mice walk in a maze: wasd to move,q to quit")
print(
   "flag is the shortest path's md5,example:if the shortest path is wasdsdw,the flag is md5('wasdsdw')"
)
(i, j) = (0, 1)
n = 0
while i == row * 2 and j == col * 2 - 1:
   print("ohhhh!!!!you did it")
   break
   print("your position:({},{})".format(i, j))
   inp = msvcrt.getch()
   n += 1
   ti = i
   tj = j
   if b"a" == inp and i > 0:
       tj -= 1
   elif b"w" == inp and j > 0:
       ti -= 1
   elif b"s" == inp and j < row * 2:
       ti += 1
   elif b"d" == inp and i < col * 2:
       tj += 1
   elif b"q" == inp:
       exit("bye!!")
   else:
       print("What???")
   if maze[ti][tj] == 1:
       print(random.choice(["no wayy!!", "it's wall", "nop"]))
       continue
   elif maze[ti][tj] == 0:
       print(random.choice(["nice!!", "yeah!!", "Go on"]))
       i = ti
       j = tj
   return None

获得迷宫的矩阵,然后矩阵也不大,手动走一下就能走出来了,写脚本也行,但是写脚本要动脑子我就没写,得到路径后根据提示将路径md5一下就得flag了

re6

简单题直接贴脚本

s_box = 'qwertyuiopasdfghjkzxcvb123456#$'
s = 'u#k4ggia61egegzjuqz12jhfspfkay'
s = s[::-1]
ret = 3
for i in range(len(s)):
   m = s_box.index(s[i])
   ret = ret*31+m
ret = str(bin(ret))[2:]
if len(ret)%8 != 0:
   ret = ret.zfill(len(ret)+8-len(ret)%8)
result = ''
for i in range(0,len(ret)//8):
   result += chr(int(ret[8*i:8*(i+1)],2))

print(result)

re7

简单题直接贴脚本

import random
result = [201, 8, 198, 68, 131, 152, 186, 136, 13, 130, 190, 112, 251, 93, 212, 1, 31, 214, 116, 244]
random.seed(1)
l = []
for i in range(4):
    l.append(random.getrandbits(8))
flag = ''
for i in range(len(l)):
    random.seed(l[i])
    for n in range(5):
        flag += chr(result[i*5+n]^random.getrandbits(8))
print(flag)

re8

打开之后如上图,拖进exeinfope查壳

无壳,拖进ida64

直接f5后再将ascii码转换为字符可得下图

读代码可知将{34sy_r3v3rs3}中的3换成e,4换成a,加上NSSCTF提交即可,flag为NSSCTF{easy_reverse}.

re2

打开如上图,拖进exeinfope查壳

无壳,拖进ida64

直接f5后再将ascii码转换为字符

然后读代码,可以发现就是用了一个凯撒加密,为24位,手动将ylqq]aycqyp{解码再加上NSSCTF提交即可,flag为NSSCTF{nss_caesar}.

感谢观看我的博客~
[NSSCTF 2021]招新赛Reverse出题人笔记
https://www.wd-ljt.com/post/1016/609.html
来源于问谛居,转载记得联系作者哟~
THE END
分享
二维码
海报
[NSSCTF 2021]招新赛Reverse出题人笔记
ASTJS 这段json其实是js语言的ast (抽象语法树) ast 定义 ast js 用法 转换器 简单的解答 直接看json 其实并不长 就300多行 其实认真分析……
<<上一篇
下一篇>>