2022-12-25 WP
从放假到现在做了一车水题,主要是Buu的,攻防世界近几天才开始做,稍微总结一下思路方法
0xFF 前言 真的是完全从0开始做了一堆re
的水题,基本了解了IDA的使用方式,甚至惊喜发现IDA能够使用wine完美运行,遂完全切换至Linux下做题,包括伪代码应该怎么看,伪代码的生成机制,动态调试,远程调试和patch。也收集了一些IDA之外的reverse工具,比如jadx
和.Net Reflector
分别对应java和dotnet的逆向,也很好用
这里放三个略有代表性的题,都很简单
0x00 [ACTF新生赛2020]easyre ExeinfoPe读入直接报错,无所谓,我会IDA
扔到IDA里面惊喜发现有符号表,感觉应该不难
直接F5一把梭
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 int __cdecl main (int argc, const char **argv, const char **envp) { _BYTE v4[12 ]; _DWORD v5[3 ]; _BYTE v6[5 ]; int v7; int v8; int v9; char v10; int i; __main(); qmemcpy(v4, "*F'\"N,\"(I?+@" , sizeof (v4)); printf ("Please input:" ); scanf ("%s" , v6); if ( v6[0 ] != 65 || v6[1 ] != 67 || v6[2 ] != 84 || v6[3 ] != 70 || v6[4 ] != 123 || v10 != 125 ) return 0 ; v5[0 ] = v7; v5[1 ] = v8; v5[2 ] = v9; for ( i = 0 ; i <= 11 ; ++i ) { if ( v4[i] != _data_start__[*((char *)v5 + i) - 1 ] ) return 0 ; } printf ("You are correct!" ); return 0 ; }
容易看出v7
,v8
,v9
,v10
其实都是v6
的一部分
看起来是将输入的flag用_data_start__
这个数组做了个映射,看下这个数组的内容
果然是简单的映射,直接写脚本,把v4
数组的东西映射回去就好了
此处注意,IDA会在字符串中自动加入转义字符,比如v4
中的\"
就是转义后的"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> using namespace std;const char c[] = "~}|{zyxwvutsrqponmlkjihgfedcba`_^]\\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$# !\"" ;const char payload[] = "*F'\"N,\"(I?+@" ;int main () { for (int i=0 ;i<=11 ;i++){ for (int j=1 ;j<96 ;j++){ if (payload[i]==c[j-1 ]){ cout<<char (j)<<"" ; break ; } } } cout<<endl; return 0 ; }
输出U9X_1S_W6@T?
即为flag
0x01 [GWCTF 2019]pyre 下载附件得到一个.pyc
文件,为Python字节码,使用uncompyle6
反编译得到
1 2 3 4 5 6 7 8 9 10 11 print "Welcome to Re World!" print "Your input1 is your flag~" l = len (input1) for i in range (l): num = ((input1[i] + i) % 128 + 128 ) % 128 code += num for i in range (l - 1 ): code[i] = code[i] ^ code[i + 1 ] print codecode = ['\x1f' , '\x12' , '\x1d' , '(' , '0' , '4' , '\x01' , '\x06' , '\x14' , '4' , ',' , '\x1b' , 'U' , '?' , 'o' , '6' , '*' , ':' , '\x01' , 'D' , ';' , '%' , '\x13' ]
它甚至是Python2的语法
看起来只需要把这个code
数组倒着做一次相同的操作,先异或回去再把取模减掉的部分加回来就可以了
1 2 3 4 5 6 7 8 9 10 code = ['\x1f' , '\x12' , '\x1d' , '(' , '0' , '4' , '\x01' , '\x06' , '\x14' , '4' , ',' , '\x1b' , 'U' , '?' , 'o' , '6' , '*' , ':' ,'\x01' , 'D' , ';' , '%' , '\x13' ]集训 for i in range (len (code) - 2 , -1 , -1 ): code[i] = chr (ord (code[i]) ^ ord (code[(i + 1 )])) c = -1 for i in code: c = c + 1 if ord (i) - c < 0 : print (chr (ord (i) + 128 - c), end="" ) continue print (chr (ord (i) - c), end="" )
得到GWHT{Just_Re_1s_Ha66y!}
即为flag
0x02 简单注册器 下载附件得到一个.apk
文件,先放到jadx
里面看看
很容易就找到了主要函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public void onClick (View v) { int flag = 1 ; String xx = editview.getText().toString(); flag = (xx.length() == 32 && xx.charAt(31 ) == 'a' && xx.charAt(1 ) == 'b' && (xx.charAt(0 ) + xx.charAt(2 )) + (-48 ) == 56 ) ? 0 : 0 ; if (flag == 1 ) { char [] x = "dd2940c04462b4dd7c450528835cca15" .toCharArray(); x[2 ] = (char ) ((x[2 ] + x[3 ]) - 50 ); x[4 ] = (char ) ((x[2 ] + x[5 ]) - 48 ); x[30 ] = (char ) ((x[31 ] + x[9 ]) - 48 ); x[14 ] = (char ) ((x[27 ] + x[28 ]) - 97 ); for (int i = 0 ; i < 16 ; i++) { char a = x[31 - i]; x[31 - i] = x[i]; x[i] = a; } String bbb = String.valueOf(x); textview.setText("flag{" + bbb + "}" ); return ; } textview.setText("输入注册码错误" ); }
看来只需要搞到x
数组即可,直接运行这段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class solve { public static void main (String[] args) { char [] x = "dd2940c04462b4dd7c450528835cca15" .toCharArray(); x[2 ] = (char ) ((x[2 ] + x[3 ]) - 50 ); x[4 ] = (char ) ((x[2 ] + x[5 ]) - 48 ); x[30 ] = (char ) ((x[31 ] + x[9 ]) - 48 ); x[14 ] = (char ) ((x[27 ] + x[28 ]) - 97 ); for (int i = 0 ; i < 16 ; i++) { char a = x[31 - i]; x[31 - i] = x[i]; x[i] = a; } String bbb = String.valueOf(x); System.out.println(bbb); return ; } }
得到59acc538825054c7de4b26440c0999dd
即为flag
2022-12-31 WP
本周转战攻防世界,也逐渐做了一些有意思的题
0x00 eazy_go 1 2 file easyGo easyGo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=AMKR9SkEqpLX9a66kfz-MTfh_nlJaiowVRmWgzjZ/KLNtSXuNfFoiFw0kSD4f/2IbhaQN8zH0h9MRASllS, stripped
一个golang
的可执行文件,直接扔到IDA里面,找到主要逻辑main_main
函数
直接看逻辑好复杂,开调!
这个函数看起来是比较某个和输入有关的东西和某个和flag有关的东西,打个断点
直接看v0的值试试?
?
好家伙,明文flag,轻松秒杀
0x01 APK-逆向2 题目名字叫APK,但是我寻思这也没APK啊
1 2 file 4122e391e1574335907f8e2c4f438d0e.exe 4122e391e1574335907f8e2c4f438d0e.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows, 3 sections
看起来dotNet
可执行文件,扔dnSpy
看看咯
反编译后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 private static void Main (string [] args ){ string hostname = "127.0.0.1" ; int port = 31337 ; TcpClient tcpClient = new TcpClient(); try { Console.WriteLine("Connecting..." ); tcpClient.Connect(hostname, port); } catch (Exception) { Console.WriteLine("Cannot connect!\nFail!" ); return ; } Socket client = tcpClient.Client; string text = "Super Secret Key" ; string text2 = Program.read(); client.Send(Encoding.ASCII.GetBytes("CTF{" )); foreach (char x in text) { client.Send(Encoding.ASCII.GetBytes(Program.search(x, text2))); } client.Send(Encoding.ASCII.GetBytes("}" )); client.Close(); tcpClient.Close(); Console.WriteLine("Success!" ); }
直接开一个127.0.0.1:31337
的TCP服务器(嵌入式没白搞
得到flag这算不算一种偷袭?
0x02 babymips 1 2 file 2104878f0fb046a0a1766a2e80e538c2 2104878f0fb046a0a1766a2e80e538c2: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-mipsel.so.1, stripped
看起来是mips
架构的可执行文件,虽然有设备但是懒得放上去跑了,直接IDA
两个主要函数是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int __fastcall main (int a1, char **a2, char **a3) { int i; char v5[36 ]; setbuf((FILE *)stdout , 0 ); setbuf((FILE *)stdin , 0 ); printf ("Give me your flag:" ); scanf ("%32s" , v5); for ( i = 0 ; i < 32 ; ++i ) v5[i] ^= 32 - (_BYTE)i; if ( !strncmp (v5, fdata, 5u ) ) return sub_4007F0(v5); else return puts ("Wrong" ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int __fastcall sub_4007F0 (const char *a1) { char v1; size_t i; for ( i = 5 ; i < strlen (a1); ++i ) { if ( (i & 1 ) != 0 ) v1 = (a1[i] >> 2 ) | (a1[i] << 6 ); else v1 = (4 * a1[i]) | (a1[i] >> 6 ); a1[i] = v1; } if ( !strncmp (a1 + 5 , (const char *)off_410D04, 0x1B u) ) return puts ("Right!" ); else return puts ("Wrong!" ); }
明确了逻辑即可写出脚本
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 #include <cstdio> #include <cstring> #include <algorithm> #include <iostream> #include <string> using namespace std;unsigned char enflag2[] = {'Q' ,'|' ,'j' ,'{' ,'g' ,0x52 , 0xFD , 0x16 , 0xA4 , 0x89 , 0xBD , 0x92 , 0x80 , 0x13 , 0x41 , 0x54 , 0xA0 , 0x8D , 0x45 , 0x18 , 0x81 , 0xDE , 0xFC , 0x95 , 0xF0 , 0x16 , 0x79 , 0x1A , 0x15 , 0x5B , 0x75 , 0x1F , 0 };int main () { for (int i = 5 ; i < 32 ; i++) { if (i & 1 !=0 ) { enflag2[i] = (enflag2[i] << 2 ) | (enflag2[i] >> 6 ); } else { enflag2[i] = (enflag2[i] >> 2 ) | (enflag2[i] << 6 ); } } for (int i=0 ;i<32 ;i++){ enflag2[i]^=32 -i; cout<<char (enflag2[i]); } cout<<endl; return 0 ; }
得到flag
0x03 debug 看这个题目名字就感觉是动态调试秒杀(?
1 2 file 3d43134e9941483e970e936e88c245f2.exe 3d43134e9941483e970e936e88c245f2.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows, 3 sections
依然dotNet
可执行文件,直接dnSpy
花费一些时间找到主要逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private static void ᜀ(string [] A_0){ string b = null ; string value = string .Format("{0}" , DateTime.Now.Hour + 1 ); string a_ = "CreateByTenshine" ; ᜅ.ᜀ(a_, Convert.ToInt32(value ), ref b); string a = Console.ReadLine(); if (a == b) { Console.WriteLine("u got it!" ); Console.ReadKey(true ); } else { Console.Write("wrong" ); } Console.ReadKey(true ); }
果然是debug就解决了……
0x04 crackme 1 2 file 3fd532458bd248349f3bdba2ccb1c5e8.exe 3fd532458bd248349f3bdba2ccb1c5e8.exe: PE32 executable (console) Intel 80386, for MS Windows, 3 sections
一般的win32
可执行文件
有壳,nsPack
,想办法脱壳
通过搜索找到脱壳工具,直接脱壳
将脱壳后文件扔到IDA
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 int __cdecl main (int argc, const char **argv, const char **envp) { int v4; char Buffer[52 ]; memset (Buffer, 0 , 50 ); printf ("Please Input Flag:" ); gets_s(Buffer, 0x2C u); if ( strlen (Buffer) == 42 ) { v4 = 0 ; while ( (Buffer[v4] ^ byte_402130[v4 % 16 ]) == dword_402150[v4] ) { if ( ++v4 >= 42 ) { printf ("right!\n" ); return 0 ; } } printf ("error!\n" ); return 0 ; } else { printf ("error!\n" ); return -1 ; } }
写出脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <cstdio> #include <cstring> #include <algorithm> #include <iostream> #include <string> using namespace std;char Byte[]="this_is_not_flag" ;char Dword[]={0x12 ,4 ,8 ,0x14 ,0x24 ,0x5C ,0x4A ,0x3D ,0x56 ,0xA ,0x10 ,0x67 ,0 ,0x41 ,0 ,1 ,0x46 ,0x5A ,0x44 ,0x42 ,0x6E ,0xC ,0x44 ,0x72 ,0xC ,0xD ,0x40 ,0x3E ,0x4B ,0x5F ,2 ,1 ,0x4C ,0x5E ,0x5B ,0x17 ,0x6E ,0xC ,0x16 ,0x68 ,0x5B ,0x12 ,2 ,0 };int main () { for (int i=0 ;i<42 ;i++){ Dword[i]^=Byte[i%16 ]; } cout<<Dword<<endl; return 0 ; }
0x05 maze 1 2 file bdb2c015b0fd4f74bc4c3e5a6e54bcf4 bdb2c015b0fd4f74bc4c3e5a6e54bcf4: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=eda1df76eec45447cd0e1ad208a7eff914e86758, stripped
看题目应该是经典的走迷宫题,直接IDA
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 __int64 __fastcall main (int a1, char **a2, char **a3) { __int64 v3; int v4; char v5; char v6; const char *v7; unsigned int v9; int v10[9 ]; v10[0 ] = 0 ; v9 = 0 ; puts ("Input flag:" ); scanf ("%s" , &s1); if ( strlen (&s1) != 24 || strncmp (&s1, "nctf{" , 5uLL ) || *(&byte_6010BF + 24 ) != '}' ) { LABEL_22: puts ("Wrong flag!" ); exit (-1 ); } v3 = 5LL ; if ( strlen (&s1) - 1 > 5 ) { while ( 1 ) { v4 = *(&s1 + v3); v5 = 0 ; if ( v4 > 78 ) { if ( (unsigned __int8)v4 == 'O' ) { v6 = sub_400650(v10); goto LABEL_14; } if ( (unsigned __int8)v4 == 'o' ) { v6 = sub_400660(v10); goto LABEL_14; } } else { if ( (unsigned __int8)v4 == '.' ) { v6 = sub_400670(&v9); goto LABEL_14; } if ( (unsigned __int8)v4 == '0' ) { v6 = sub_400680(&v9); LABEL_14: v5 = v6; } } if ( !(unsigned __int8)sub_400690((__int64)asc_601060, v10[0 ], v9) ) goto LABEL_22; if ( ++v3 >= strlen (&s1) - 1 ) { if ( v5 ) break ; LABEL_20: v7 = "Wrong flag!" ; goto LABEL_21; } } } if ( asc_601060[8 * v9 + v10[0 ]] != 35 ) goto LABEL_20; v7 = "Congratulations!" ; LABEL_21: puts (v7); return 0LL ; }
在string里面找到迷宫,在sub_400690
得到#
是终点,
是路
1 2 3 4 5 6 7 8 ****** * * * *** * ** ** * ** * *# * ** *** * ** * ********
构造出flag:nctf{o0oo00O000oooo..OO}
0x7F 本周总结 有一些reverse题可以用一些小技巧偷袭简单解出,这可能是出题人的疏忽,也可能是有意为之,要保持灵活的思维,不能只考虑静态分析
本周两个比赛涨了一些见识,catctf的misc好有意思但是好像我的思路都有些问题
2023-01-23 WP
注:本时间为此WP开始写作时间,并非最终上传时间,最终上传时间以论坛/博客时间为准
第一次在自己的WP里面写比赛题
T1 CATCTF Cat_Jump 很有意思的一题,赛后只觉得自己蠢
附件给了一个.vmdk
文件
1 2 file cat_jump_clean.vmdk cat_jump_clean.vmdk: VMware4 disk image
一个虚拟机硬盘镜像,直观想法应该是挂载它然后看看有什么
直接启动是一个猫猫游戏
找个livecd挂载一下,发现是个alpine linux
并且用了openrc
,且提取出以下文件
1 2 file termosaur termosaur: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, with debug_info, not stripped
会不会是个逆向呢,放IDA里面看看
发现如下String
1 catctf=`base64 /sys/firmware/efi/efivars/CatCTF-7c436110-ab2a-4bbb-a880-fe41995c9f82`;echo U2FsdGVkX1/d/fu4H2quw6KimHlfde+UjUBw0jcmkGEKaHnBck8CelJ14JV9buQy | openssl aes-128-cbc -d -pbkdf2 -base64 -k $catctf
看起来是flag的解密脚本,但是这个efivar
经过一番努力发现是不可得的,比赛的时候只做到这里
赛后考虑到这是个misc,感觉还是这个vmdk文件有问题,遂拿出原始文件,直接hex editor打开直接搜catctf{
果然搜到了CatCTF{EFI_1sv3ry_funn9}
有种打oi签到题被降智的感觉,我愿称之为CTF的小凯的疑惑
T2 CATCTF CatchCat
仍然是一道misc,GPS数据处理
附件CatchCat.txt
纯文本文件,打开以后的内容大概是这样的
1 2 3 $GPGGA,090000.00,3416.48590278,N,10856.86623887,E,1,05,2.87,160.00,M,-21.3213,M,,*7E ... $GPGGA,090609.00,3416.48362669,N,10856.86416198,E,1,05,2.87,160.00,M,-21.3213,M,,*7D
搜索关键词GPGGA ,得到这是一种GPS数据格式,是NMEA-0183
协议的一种数据格式
求助万能的Python
,搜索Python+NEMA两个关键词
找到一个库pynmea2
可以解析NMEA数据,另一个库folium
,同时发现有轮子可以直接用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import pynmea2import foliumimport osdef draw_gps (locations, output_path, file_name ): m = folium.Map(locations[0 ], zoom_start=15 , attr='default' ) folium.PolyLine(locations, weight=3 , color='orange' , opacity=0.8 ).add_to(m) folium.Marker(locations[0 ], popup='<b>Starting Point</b>' ).add_to(m) folium.Marker(locations[-1 ], popup='<b>End Point</b>' ).add_to(m) m.save(os.path.join(output_path, file_name)) nmea_file=pynmea2.NMEAFile('CatchCat.txt' ) poss = list () for record in nmea_file: single = list () single.append(record.latitude) single.append(record.longitude) poss.append(single) print (poss)draw_gps(poss,"./" ,"map.html" )
得到html
文件如图:
阅读轨迹得到flag:CatCTF{GPS_M1ao}
T3 HWS2023WINTER babyre 稍微有一点点难度的android逆向,附件只有一个apk,直接jadx
有a, b, c, d, MainActivity
五个class,先看MainActivity
搞清楚逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class MainActivity extends AppCompatActivity { private Button bt; private EditText input; @Override public void onCreate (Bundle bundle) { super .onCreate(bundle); setContentView(R.layout.activity_main); this .input = (EditText) findViewById(R.id.input); this .bt = (Button) findViewById(R.id.bt); this .bt.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View view) { b.a(MainActivity.this ); if (MainActivity.this .input.getText().toString() != null && new a ().check(MainActivity.this .input.getText().toString())) { Toast.makeText(MainActivity.this , "success" , 1 ).show(); } else { Toast.makeText(MainActivity.this , "fail" , 1 ).show(); } } }); } }
再看一下a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class a { public boolean check (String str) { byte [] bytes = str.getBytes(); if (bytes.length == 38 ) { return false ; } for (int i = 0 ; i < bytes.length; i++) { bytes[i] = (byte ) (bytes[i] + 10 ); bytes[i] = (byte ) (bytes[i] ^ 102 ); bytes[i] = (byte ) (bytes[i] << 3 ); } return Base64.encodeToString(bytes, 0 ).equals("a214bmVqaXlieHpjaXhuc2p4bm5hc20=" ); } }
这里反编译出的a
做了一步不可逆的操作,还有b, c, d
没看,不急
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 public class d extends Application { @Override protected void attachBaseContext (Context context) { super .attachBaseContext(context); new c (this ); } } public class c { public c (Context context) { try { InputStream open = context.getResources().getAssets().open("enc" ); byte [] bArr = new byte [open.available()]; open.read(bArr); for (int i = 0 ; i < bArr.length; i++) { bArr[i] = (byte ) (bArr[i] ^ 52 ); } File dir = context.getDir("odex" , 0 ); if (!dir.exists()) { dir.mkdirs(); } File file = new File (dir.getAbsolutePath() + File.separator + "classes.dex" ); if (!file.exists()) { file.createNewFile(); } FileOutputStream fileOutputStream = new FileOutputStream (file); fileOutputStream.write(bArr); fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
由于class b
代码过于冗长,在这里不再贴出,其主要逻辑为利用c中解码出的dex文件修补现有的dex文件
目前已知enc
是一个经过编码后的dex
文件,将其导出后解码
1 2 3 4 5 6 from Cryptodome.Util.number import long_to_bytesDec = open ("dec.dex" ,"wb" ) with open ("enc" ,"rb" ) as Enc: for i in Enc.read(): Dec.write(long_to_bytes(i^52 ))
用jadx直接打开解码后的dec.dex
,得到一个新的class a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class a { public static String Encrypt (String sSrc) { try { byte [] raw = "FV8aOaQiak6txP09" .getBytes("utf-8" ); SecretKeySpec skeySpec = new SecretKeySpec (raw, "AES" ); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding" ); IvParameterSpec iv = new IvParameterSpec ("2Aq7SR5268ZzbouE" .getBytes()); cipher.init(1 , skeySpec, iv); byte [] encrypted = cipher.doFinal(sSrc.getBytes()); return Base64.getEncoder().encodeToString(encrypted); } catch (Exception e) { e.printStackTrace(); return null ; } } public boolean check (String input) { return Encrypt(input).equals("9Kz3YlSdD3lB9KoxeKxXQT4YOEqJTVIuNU+IjW4iFQzjpU+NikF/UqCOsL+1g4eA" ); } }
将参数转换为16进制,扔到CyberChef
,得到flag{076a554cef6742b402d74c1013dadde9}
T4 HWS2023WINTER sound from somewhere 一道简单的misc,给了一个wav音频文件,学到了QSSTV的使用方式
扔到Audacity看看
比较标准有规律的波形,结合试听考虑是某种调制过的信号,怀疑是SSTV或者拨号猫
先用SSTV试试,Linux下比较好用的SSTV解析软件大概是QSSTV
,发现AUR就有
打开后界面是这样的,默认是直接使用系统的音频输入
由于我使用PipeWire
作为系统的音频框架,直接使用一个GUI小工具qpwgraph
重定向输入输出即可
将当前使用的输出设备的监听接口WI-1000XM2:monitor
重定向到qsstv的输入
鼠标操作即可完成
在Audacity中播放,得到图片,得到flag{OuTer_Wilds}
T5 XCTF EASYHOOK 经典的reverse题目,win32可执行文件,只看题目以为是我要hook进程序,结果是程序hook了自己
1 2 file 1c40a4a45618413d83d4724b7f1a5d2f.exe 1c40a4a45618413d83d4724b7f1a5d2f.exe: PE32 executable (console) Intel 80386, for MS Windows, 3 sections
阅读代码,重命名变量后的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 34 35 36 37 38 39 int __cdecl main (int argc, const char **argv, const char **envp) { HANDLE FileA; DWORD NumberOfBytesWritten; char inputflag[32 ]; puts (aPleaseInputFla); scanf ("%31s" , inputflag); if ( strlen (inputflag) == 19 ) { sub_401220(); FileA = CreateFileA(FileName, 0x40000000 u, 0 , 0 , 2u , 0x80 u, 0 ); WriteFile(FileA, inputflag, 0x13 u, &NumberOfBytesWritten, 0 ); sub_401240(inputflag, &NumberOfBytesWritten); if ( NumberOfBytesWritten == 1 ) puts (aRightFlagIsYou); else puts (aWrong); system(Command); return 0 ; } else { puts (aWrong); system(Command); return 0 ; } } int __cdecl sub_401240 (const char *a1, _DWORD *a2) { int result; unsigned int v3; char v4[24 ]; result = 0 ; strcpy (v4, "This_is_not_the_flag" ); v3 = strlen (a1) + 1 ; return result; }
注意到main
函数中还调用了sub_401220()
函数,跟踪进去找找线索
发现是对当前进程的Hook,并且对某个变量进行了赋值操作
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 int sub_401220 () { HMODULE LibraryA; DWORD CurrentProcessId; CurrentProcessId = GetCurrentProcessId(); hProcess = OpenProcess(0x1F0FFF u, 0 , CurrentProcessId); LibraryA = LoadLibraryA(LibFileName); WriteFile_0 = (BOOL (__stdcall *)(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED))GetProcAddress(LibraryA, ProcName); lpAddress = WriteFile_0; if ( !WriteFile_0 ) return puts (&unk_40A044); unk_40C9B4 = *(_DWORD *)lpAddress; *((_BYTE *)&unk_40C9B4 + 4 ) = *((_BYTE *)lpAddress + 4 ); byte_40C9BC = -23 ; dword_40C9BD = (char *)sub_401080 - (char *)lpAddress - 5 ; return sub_4010D0(); } int __stdcall sub_401080 (HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped) { int v5; v5 = sub_401000((int )lpBuffer, nNumberOfBytesToWrite); sub_401140(); WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped); if ( v5 ) *lpNumberOfBytesWritten = 1 ; return 0 ; } int __cdecl sub_401000 (int a1, int a2) { char i; char v3; char v4; int v5; for ( i = 0 ; i < a2; ++i ) { if ( i == 18 ) { *(_BYTE *)(a1 + 18 ) ^= 0x13 u; } else { if ( i % 2 ) v3 = *(_BYTE *)(i + a1) - i; else v3 = *(_BYTE *)(i + a1 + 2 ); *(_BYTE *)(i + a1) = i ^ v3; } } v4 = 0 ; if ( a2 <= 0 ) return 1 ; v5 = 0 ; while ( byte_40A030[v5] == *(_BYTE *)(v5 + a1) ) { v5 = ++v4; if ( v4 >= a2 ) return 1 ; } return 0 ; }
提取出byte_40A030
的数据,写脚本逆变换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <cstdio> #include <cstring> #include <iostream> using namespace std;char enflag[]={0x61 ,0x6A ,0x79 ,0x67 ,0x6B ,0x46 ,0x6D ,0x2E ,0x7F ,0x5F ,0x7E ,0x2D ,0x53 ,0x56 ,0x7B ,0x38 ,0x6D ,0x4C ,0x6E ,0 };char flag[100 ];int main () { for (int i=0 ;i<strlen (enflag);i++){ if (i%2 ){ flag[i]=char ((enflag[i]^i)+i); } else { flag[i+2 ]=char (enflag[i]^i); } } flag[0 ]='f' ; cout<<flag<<endl; return 0 ; }
得到flag{Ho0k_w1th_Fun}
后记:这题如果用动态调试的话难度大大降低,可以直接进入关键函数
T6 XCTF handcrafted-pyc 一个对pyc
文件的逆向,考察pyc文件结构和Python字节码
附件是一个Python脚本,此处省略括号中的内容
1 2 3 4 5 6 import marshal, zlib, base64exec (marshal.loads(zlib.decompress(base64.b64decode("..." ))))
脚本加载了一个pyc文件,先用zlib和base64把它解码后写入到文件
1 2 3 4 5 6 7 import struct, zlib, base64a = zlib.decompress(base64.b64decode('...' )) tmp = open ("a.pyc" ,"wb" ) for i in a: cache = struct.pack("B" ,i) tmp.write(cache) tmp.close();
写入以后用hex editor打开,发现缺少文件头,猜测为Python版本为2,补充八个字节文件头03 F3 0D 0A C8 B9 59 61
补充后用uncompyle6
反编译,得到一份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 def main --- This code section failed: --- L. 1 0 LOAD_GLOBAL 0 'chr' 3 LOAD_CONST 108 6 CALL_FUNCTION_1 1 None 9 LOAD_GLOBAL 0 'chr' 12 LOAD_CONST 108 15 CALL_FUNCTION_1 1 None 18 LOAD_GLOBAL 0 'chr' 21 LOAD_CONST 97 24 CALL_FUNCTION_1 1 None 27 LOAD_GLOBAL 0 'chr' 30 LOAD_CONST 67 33 CALL_FUNCTION_1 1 None 36 ROT_TWO 37 BINARY_ADD 38 ROT_TWO 39 BINARY_ADD 40 ROT_TWO 41 BINARY_ADD 42 LOAD_GLOBAL 0 'chr' 45 LOAD_CONST 32 48 CALL_FUNCTION_1 1 None 51 LOAD_GLOBAL 0 'chr' 54 LOAD_CONST 101 57 CALL_FUNCTION_1 1 None 60 LOAD_GLOBAL 0 'chr' 63 LOAD_CONST 109 66 CALL_FUNCTION_1 1 None 69 LOAD_GLOBAL 0 'chr' 72 LOAD_CONST 32 75 CALL_FUNCTION_1 1 None
分析字节码得到以下关键指令
1 2 3 4 5 6 7 8 9 LOAD_GLOBAL LOAD_CONST 两条指令结合为向栈中加入某类型的一个元素并赋值 BINARY_ADD 将栈顶两个元素弹出,相加,再压入栈中 ROT_TWO 交换栈顶两个元素(此指令可参考以下代码
此处摘录两个函数的的具体实现,其余指令实现均可在CPython源码(注意不同版本opcode有差异)中找到
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 TARGET_NOARG(BINARY_ADD) { w = POP(); v = TOP(); if (PyInt_CheckExact(v) && PyInt_CheckExact(w)) { register long a, b, i; a = PyInt_AS_LONG(v); b = PyInt_AS_LONG(w); i = (long )((unsigned long )a + b); if ((i^a) < 0 && (i^b) < 0 ) goto slow_add; x = PyInt_FromLong(i); } else if (PyString_CheckExact(v) && PyString_CheckExact(w)) { x = string_concatenate(v, w, f, next_instr); goto skip_decref_vx; } else { slow_add: x = PyNumber_Add(v, w); } Py_DECREF(v); skip_decref_vx: Py_DECREF(w); SET_TOP(x); if (x != NULL ) DISPATCH(); break ; } TARGET(ROT_TWO) { PyObject *top = TOP(); PyObject *second = SECOND(); SET_TOP(second); SET_SECOND(top); FAST_DISPATCH(); }
只需模拟相应操作即可复原栈中数据
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 def binary_add (nums:list ): tmp = nums.pop() nums.append(nums.pop()+tmp) return nums def rot_two (nums:list ): last=nums.pop() prev=nums.pop() nums.append(last) nums.append(prev) return nums with open ("decr.py" ) as decr lines = decr.readlines() nums = list () isdigit = lambda s: all ([data >= '0' and data <= '9' for data in s]) for i in lines: if len (i.split())==3 : i = i.split()[-1 ] if isdigit(i): nums.append(chr (int (i))) else : nums.append(0 ) else : if "BINARY_ADD" in i: nums = binary_add(nums) elif "ROT_TWO" in i: nums = rot_two(nums) print (nums)
得到hitcon{Now you can compile and run Python bytecode in your brain!}
本周总结 本周打了HWS和杭电Hgame,见到了很多新东西
比赛比较长见识,有时间有能力要多参加比赛,也要防止被降智
遇到没见过的题目不要慌,冷静分析+搜索+跳出固有思维往往可以提供新的思路
有字节码的语言可以考虑了解一下它们的字节码结构
2023-01-12 WP
本次WP题目主要来自Hgame