前言:一个萌新re的wp
PixelShooter 这个题最简单了,GG修改分数死了就直接出flag
flag:MRCTF{Unity_1S_Fun_233}
因为没有接触过安卓逆向,大佬都是直接把APK丢进反编译软件里的,只能用这种费力的方式了
该题官方下载链接
lualu 这个题先翻了一下脚本发现看不懂,所以在线重写一遍
直接丢进IDA,提示用IDA64,那就是64位程序了
丢进IDA64加载完后翻了一下,卧槽居然没有main
不过不急shift+F12,顺便打开程序看一下
ctrl+F搜到了那个字符串
双击点进去,并按x查看引用
看到了这些汇编,不过咱也看不懂还是直接F5
一顿操作以后
这咋办呐,不急我们走动态看看
一样的做法先搜索字符串,定位关键代码
本来想找他的scanf,然后跟自己输入的假码,在他上面说的字符串之后下了断点,结果就发现了这个,分析如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 cmps={83 ,80 ,73 ,80 ,76 ,125 ,61 ,96 ,107 ,85 ,62 ,63 ,121 ,122 ,101 ,33 ,123 ,82 ,101 ,114 ,54 ,100 ,101 ,97 ,85 ,111 ,39 ,97 } print ("Give Me Your Flag LOL!:" )flag=io .read () if string .len (flag)~=28 then [[~是异或]] print ("Wrong flag!" ) os .exit () end for i=1 ,string .len (flag) do local x=string .byte (flag,i) if i%2 ==0 then [[第偶数个字符a[i]^=i 这里直接再^一次i就可以了]] x=x~i else x=x+6 [[不是偶数个则是奇数个a[i]+=6 逆向就是a[i]-=6]] end if x~=cmps[i] then [[处理完之后与上面给的字符串相比较]] print ("Wrong flag!" ) os .exit () end end print ("Right flag!" )os .exit ()
exp如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> int main () { char a[]={0 ,83 ,80 ,73 ,80 ,76 ,125 ,61 ,96 ,107 ,85 ,62 ,63 ,121 , 122 ,101 ,33 ,123 ,82 ,101 ,114 ,54 ,100 ,101 ,97 ,85 ,111 ,39 ,97 ,0 }; for (int i=1 ;a[i];i++) { if (i%2 ==0 ) { a[i]^=i; } else { a[i]-=6 ; } } printf ("%s" ,(char *)a+1 ); }
flag:MRCTF{7he_83st_1u@_f0r_yOu!}
之后我尝试在内存里找到运行逻辑,失败告终,然后跑去问出题人,出题人说这个题目就是lua lu,内嵌了一个lua解释器(那么我可以改内存来达到盗用他的lua解释器!),大概就是不让那些喜欢静态调试的人好过吧
该题官方下载链接
好了这题咱不吃上一题的亏 直接丢进x64dbg(x32dbg提示我的)
丢进去以后老规矩搜索字符串,定位关键点,在Give me your code下面发现strlen函数并且cmp 0x21
过了len的判断 再往下跟就到把input一顿xx的地方
怎么xx的先不管 再往下看看
一个一个cmp 不是就exit 很好这就是被xx过后的input 那么我们拿它来反xx就可以得到flag了
好了我摊牌了,听说re爷爷看汇编跟看小说一样,我…我是re孙子,咱们上神器IDA_F5
这次居然有main,要是没有main的话直接取后面的动态调试的地址ctrl+g过去找近的函数F5就OK了,要是被aslr了那就还是shift+f12找字符串过去吧,最后直接上exp,话说前面那个算法我分析了好久,以为我没弄错,是f5的错(f5经常抽风),害,我好菜,我菜死了
分析如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 for ( i = 0 ; i <= 32 ; ++i ){ input[i] = input_[key_1[i]]; v4 = i; input[i] ^= LOBYTE(key_1[i]); } for ( j = 0 ; j <= 32 ; ++j ){ v4 = j; if ( key_2[j] != input[j] ) { sub_40E640(argc, (__int64)argv, j, (__int64)"Wrong!\n" ); system(*(const char **)&argc); exit (argc); } }
exp如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> int main () { char key_1[]={9 ,10 ,15 ,23 ,7 ,24 ,12 ,6 ,1 ,16 ,3 ,17 ,32 ,29 ,11 ,30 , 27 ,22 ,4 ,13 ,19 ,20 ,21 ,2 ,25 ,5 ,31 ,8 ,18 ,26 ,28 ,14 ,0 }; char key_2[]="gy{\x7Fu+<RSyW^]B{-*fB~LWyAk~e<\\EobM" ; char flag[34 ]={0 }; for (int i=32 ;i>=0 ;i--) { flag[key_1[i]] = key_2[i]; flag[key_1[i]] ^= key_1[i]; } printf ("%s" ,flag); }
flag:MRCTF{Tr4nsp0sltiON_Clph3r_1s_3z}
该题官方下载链接
Junk 拿到题目先丢进ida,shift+f12看到了 “give me your flag! “ ,然后交叉引用,然后f5,然后欸?怎么f5不了?f5报错了居然!不急不急,按空格切换视图,记下左边的地址,401390
然后咱把它丢进od,ctrl+g跳到这个地方,再一顿改名分析,发现了关键call
enter进去看看
好了len出来了,往下继续看
这个call,噫,有点意思,call指令分为两步,先push下一条指令的地址入栈,然后jmp去一个地方,这里过来直接把入栈的地址加了一,后面retn就是到了之前入栈的下一个地址的+1的地方,意思就是说下面那里根本不是一个call,前面那一位E8是根本不会走的,那么我们把它nop掉,这样就能看见了
这里说一下怎么nop他,在下面的数据窗口ctrl+g输入401256 即那个call的地址,再双击第一个E8改成90就是nop了
这样逻辑很清晰了,这里才几句用不着看F5也可以知道这里的算法是把每一位都^3
异或完了可以看见他直接跳过了这里,那么我们就可以料想这两个call一点P用都没有,再往下看看
什么?je跳过去的线断了?其实并不是,只是od把这里以为是个call其实他是个屁call,他直接je到了401280 ,而这个call的地址是40127F ,也就是说这里是一个假call,那咱再把他nop掉
因为上面那个xor,zf标志寄存器一定会为1,je一定会跳过,那么我们直接把je也一块nop了,下面也有一个一毛一样的结构,于是我顺手就nop完了,好了我们现在下面就是剩下的程序了,但是菜鸡看不懂汇编呢,现在能f5了吗?试试看?右键->复制到可执行文件->所有修改,文件->右键->保存文件
我们来对比一下
之前是这个样子的,点进去并不能F5
之后是这个样子的,点进去可以F5
分析一波如下
game3分析如下
逆向算法思路:从key反推是table几,保存起来,在反base64算法,再反game2,最后反game1,完事
exp如下
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 #include <stdio.h> int main () { char table[]={0x41 ,0x42 ,0x43 ,0x44 ,0x45 ,0x46 ,0x47 ,0x48 ,0x49 ,0x4A , 0x4B ,0x4C ,0x4D ,0x4E ,0x4F ,0x50 ,0x51 ,0x52 ,0x53 ,0x54 ,0x55 , 0x56 ,0x57 ,0x58 ,0x59 ,0x5A ,0x61 ,0x62 ,0x63 ,0x64 ,0x65 ,0x66 , 0x67 ,0x68 ,0x69 ,0x6A ,0x6B ,0x6C ,0x6D ,0x6E ,0x6F ,0x70 ,0x71 , 0x72 ,0x73 ,0x74 ,0x75 ,0x76 ,0x77 ,0x78 ,0x79 ,0x7A ,0x29 ,0x21 , 0x40 ,0x23 ,0x24 ,0x25 ,0x5E ,0x26 ,0x2A ,0x28 ,0x2B ,0x2F }; char key[]={0x25 ,0x42 ,0x55 ,0x45 ,0x64 ,0x56 ,0x53 ,0x48 ,0x6C ,0x6D , 0x66 ,0x57 ,0x68 ,0x70 ,0x5A ,0x6E ,0x21 ,0x6F ,0x61 ,0x57 ,0x5A , 0x28 ,0x61 ,0x47 ,0x42 ,0x73 ,0x5A ,0x40 ,0x5A ,0x70 ,0x5A ,0x6E , 0x21 ,0x6F ,0x61 ,0x57 ,0x5A ,0x28 ,0x61 ,0x47 ,0x42 ,0x73 ,0x5A , 0x40 ,0x5A ,0x70 ,0x5A ,0x6E ,0x21 ,0x6F ,0x59 ,0x47 ,0x78 ,0x6E , 0x5A ,0x6D ,0x25 ,0x77 ,0x2E ,0x2E }; unsigned char flag[60 ]={0 }; int temp[60 ]={0 }; for (int i=0 ;key[i];i++) { for (int j=0 ;table[j];j++) if (key[i]==table[j]) { temp[i]=j; break ; } } for (int i=0 ,j=0 ;i<43 ;i+=3 ,j+=4 ) { flag[i] = ((temp[j] & 0x3F ) << 2 ) + ((temp[j+1 ] & 0x30 ) >> 4 ); flag[i+1 ] = ((temp[j+1 ] & 0xF ) << 4 ) + ((temp[j+2 ] & 0x3C ) >> 2 ); flag[i+2 ] = ((temp[j+2 ] & 3 ) << 6 ) + (temp[j+3 ] & 0x3F ); } for (int i=0 ;i<43 ;i++) { flag[i] = flag[i] % 16 * 16 + flag[i] / 16 ; flag[i] ^= 3 ; } printf ("%s" ,flag); }
flag:MRCTF{junkjunkjunkcodejunkjunkcodejunkcode}
该题官方下载链接
shit 丢进IDA里容易发现这个,不过呢这里点过去不能F5,因为他做了混淆,我们动态跑进去把他混淆的地方nop掉
然后右键补丁,效果如下
反汇编分析如下
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 char __cdecl sub_4012F0 (const char *input) { unsigned int v1; int v2; int v4; signed int i; int v6; v4 = 0 ; for ( i = 0 ; i < strlen (input); i += 4 ) { v1 = input[i + 3 ] | (input[i + 2 ] << 8 ) | (input[i + 1 ] << 16 ) | (input[i] << 24 ); v2 = (v1 << (32 - *(&key_1 + 4 * (i / 4 )))) | (v1 >> *(&key_1 + i / 4 )); sub_4013CA(); v6 = ((v2 << 16 ) | ~HIWORD(v2)) ^ (1 << *(&key_1 + i / 4 )); sub_40140F(); if ( i > 0 ) v6 ^= v4; v4 = v6; if ( v6 != key_2[i / 4 ] ) return 0 ; } return 1 ; }
逆向思路:
key_2 ^=上一个key_2( 如果有的话),
再拿结果 ^=1<<key_1[i/4] 的后四位取反 ,再把前后4位换位置 ,
再循环左移key_1[i/4]位 ,这个时候的结果转换成char输出就是flag了
完整exp如下
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 #include <stdio.h> #define rol( a , o ) \ ((a<<(o%0x20 )) | (a>>(0x20 - (o%0x20 )))) #define ror( a , o ) \ ((a>>(o%0x20 )) | (a<<(0x20 - (o%0x20 )))) int main () { int key1[]={0x3 ,0x10 ,0xD ,0x4 ,0x13 ,0xB }; int key2[]={0x8C2C133A ,0xF74CB3F6 ,0xFEDFA6F2 ,0xAB293E3B ,0x26CF8A2A ,0x88A1F279 }; unsigned int v1,v2,v3,v4; char input[]={0x66 ,0x6C ,0x61 ,0x67 ,0x7B ,0x61 ,0x5F ,0x33 ,0x61 ,0x32 ,0x79 , 0x5F ,0x72 ,0x65 ,0x5F ,0x66 ,0x6F ,0x72 ,0x5F ,0x74 ,0x65 ,0x73 ,0x74 ,0x7D ,0 }; for (int i=0 ;i<6 ;i++) { v3 = key2[i]; if (i) v3 ^= key2[i-1 ]; v3 ^= (1 << key1[i]); v2 = ((v3& 0xFFFF0000 ) >> 16 ) + (((~v3) & 0xFFFF ) << 16 ); v1 = (v2 >> (32 - key1[i])) | (v2 << key1[i]); printf ("%c%c%c%c" ,*((char *)&v1+3 ),*((char *)&v1+2 ),*((char *)&v1+1 ),*(char *)&v1); } }
flag:flag{a_3a2y_re_for_test}
官方下载链接
撒花完结~这次就写了这么几个题,主要是因为IDA里的反汇编太不熟悉了,以及对基本算法逆向的不熟悉,需要多多做题,多多写wp
virtual_tree (再补一道题)
这个题动态静态双管齐下
大体结构是这样的
第一个函数进去乍一看以为是个算法,仔细一看发现根本不会走,判断直接跳过了,然后len=16
这个树着实麻烦,不过关键代码只有一句,在这里下断点跑一次就可以拿到所有的数据
这个game其实挺简单的,不过这是经过处理之后的如果是直接静态来看,会直接懵B
作者坏坏的把名字改了,而且还有一点混淆(作者说这是编译器的问题)
具体处理方法是这样的,不过其实没有必要,动态走一遍记录一下跑的地方和参数也是一样的,主要是迷惑性很强
exp及部分解析 唠叨
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 #include <stdio.h> char key1[]={0x17 ,0x63 ,0x77 ,0x3 ,0x52 ,0x2E ,0x4A ,0x28 ,0x52 ,0x1B ,0x17 ,0x12 ,0x3A ,0x0A ,0x6C ,0x62 };void uadd (char a,char b) { key1[b]-=a; } void uxor (char a,char b) { key1[b]^=key1[a]; } void usub (char a,char b) { key1[b]=-key1[b]+key1[a]; } int main () { char input[17 ]={0 }; uadd(2 ,0xf ); uxor(0xf ,0xe ); usub(2 ,0xc ); uxor(0xc ,0xb ); usub(7 ,0xa ); usub(8 ,9 ); uxor(7 ,8 ); uadd(3 ,7 ); usub(1 ,6 ); uxor(5 ,4 ); usub(7 ,3 ); uadd(7 ,2 ); uxor(2 ,1 ); uadd(0xa ,0 ); char key2[]={0x4D ,0x4C ,0x47 ,0x50 ,0x4F ,0x4B ,0x46 ,0x43 ,0x4A ,0x45 ,0x4E ,0x49 ,0x48 ,0x44 ,0x42 ,0x41 }; for (int i=0 ;i<16 ;i++) input[i]=key1[i]^key2[i]; printf ("%s" ,input); }
flag:flag{@_7r3e_f0r_fuNN!}