写了逆向方向3个题,队伍第12
ezDos
好像只能汇编分析
1 | seg002:0000 seg002 segment byte public 'CODE' use16 |
这部分是将输入放在168的位置,并且168里放的是大小,要比对大小是0x26
1 | seg002:0032 ; --------------------------------------------------------------------------- |
本题第一个坑,rc4的init阶段,刚开始有一个push 0x100次压栈,但乍一看后面好像并没有用到但是我们去关注sub_10670函数本身
1 | seg005:0000 sub_10670 proc far ; CODE XREF: seg002:0040↑P |
ret=pop jmp,所以最后push进去的就会是跳转,刚开始pop dx,返回地址被取出,add dl,al,这里对返回地址改动,这个数字是固定的,经过计算这里的al是2,所以call sub_10670后紧跟的两字节是被越过的
所以再次观察就会发现,bl里的值就是先前压栈的值,并且栈是先进后出,所以这里rc4init是反着来的盒,所以第一部分造盒
1 | void rc4init1() { |
紧接着往后看
1 | seg002:0054 loc_10504: ; CODE XREF: seg002:0050↑j |
于是就该换盒了,si=i,di=j,慢慢看,134里面存储的是0xc,后面紧跟着是’NCTf2024nctF’,刚好12字节大小,并且0xc给了cl,[+0]代表的是前面造的盒
一开始是
dl=S[i];
j=j+dl;
al=key[i%0xc];
后面紧跟着的push ax是为了把前面左移和后面右移区分,同时这两个函数也和前面一样是用来改变返回地址的,所以这里实现的是:
push ax;
shl ax,3;
mov dx,ax;
pop ax;
push dx;
shr ax,5;
pop dx;
or al,dl:
add di,ax;
也就相当于(a<<3)|(a>>5),最后j+=a;
j%256;
change(S[i],S[j]);
1 | seg002:00BB xor cx, cx |
最后一段,怎么说呢,复杂归复杂,慢慢捋出来把,其实就是rc4稍微变了一下。。。服了卡了一天
1 |
|
SafeProgram
拖进去
1 | int __fastcall main(int argc, const char **argv, const char **envp) |
题目提示说调试器在监视你,果然调试的时候闪退了,往start方向找下断点发现毫无作用,那看来是init或者tls的原因,看导出表发现果然有两个tls,第一个里面看不出是做什么的
1 | void sub_140001520() |
顺着找很快就看到了,这里把逻辑改一下然后保存一下,就可以调试了,整体逻辑不难,就是sm4加密,当然如果没见过还是很难的,不过直接解密发现有问题,应该是sm4有改动,加密逻辑不好改的话那就只能是1.盒2.轮遍数,后面发现是Sbox变动了,是因为我发现key变了,看交叉引用发现是tls0里面的东西。。。前面说的话打脸了
1 | __int64 sub_7FF677251480() |
后面的就是盒,前面的是key,再次解密就出flag了
1 |
|
所以flag是NCTF{58cb925e0cd823c0d0b54fd06b820b7e}
x1Login
这是我第一次尝试用frida做安卓
1 | private final void checkSecutity() { |
有反调试,我选择frida到checkSecutity方法内,然后直接无操作返回(官方wp中显示用apktool解包,改,打包,签名,但是我一直报4字节的错,根本没办法解决,所以只得放弃)
然后就可以hook进去了
1 | public void onCreate(Bundle bundle) { |
主函数静态分析,前面是界面不必理会,一直到DecStr.get开始有东西,双击DecStr
1 | public class DecStr { |
是.so层的调用,如果.so分析的话
1 | sub_7C0( |
逻辑其实就是这样,base解码+异或长度Exv3nhr5BNW0axn3aNz/DNv9C3q0wxj/Exe=->com.nctf.simplelogin.Check
当然也可以frida
var str = DecStr.get(“Exv3nhr5BNW0axn3aNz/DNv9C3q0wxj/Exe=”);
console.log(‘[+] DecStr 解密结果: ‘ + str);
总之这里是获取了方法名,后面final Class<?> cls = getClass(str);+MainActivity.onCreate$lambda$0(editText, editText2, this, cls, view);
也就是加载方法,点击调用,跟着进到getClass里面
1 | private final Class<?> getClass(String str) { |
这里实现的是加载dex文件,frida getApplicationContext()得到android.app.Application@da4db40
ygvUF2vHFgbPiN9J对应的是libsimple.so
所以这里是加载方法,关键方法是loadDex,所以可以进到这个函数中加载dex,然后用frida保存下来,整体dex如下
1 | Java.perform(function() { |
得到dex.dex
1 | package com.nctf.simplelogin; |
简单解密,this.username.equals(DecStr.get(“uZPOs29goMu6l38=”));也就是账号=X1c@dM1n1$t
tMC2=Md5
逻辑就是加载Md5,然后对账号名Md5,调用Secure.doCheck
1 | public class Secure { |
从这里可以看到doCheck也是.so层的,并且这个Secure调用的.so层是libnative.so(解密agDYB3bJ得到native),那么就找doCheck,但是发现.so文件中并没有标识,ida调试也会跑飞
不过好在函数也不多,找找也找到了
1 | __int64 __fastcall sub_1E30(__int64 a1, __int64 a2, __int64 a3, __int64 a4) |
(可以看s盒来判断加密类型)逻辑就是3des加密,加密结果和xmmword_1804验证,大概是24字节,a1,a2参数是固定的,a3,a4就是传入的两个参数,所以不难猜出key就是账户的Md5,值得注意的是v11类型是__int64,8字节存储,所以会大小端序
1 |
|
en_key=d2d3436ad3ec537d
de_key=9784cf3d63dde737
密文:0x8BA584B886EC9E40 0xBBB8D31AE2648A7E 0x523E454612FA4BDF
直接CyberChef选择Triple DES Decrypt
获得~DWPefaS+MY?x$y5=6mG50U5,这个也要大小端序转换因为输入的时候Safe->
S
a
f
e
0xefaS相当于是这样,所以解密出来的就是上面这样,转换后SafePWD~5y$x?YM+5U05Gm6=
NCTF{X1c@dM1n1$t_SafePWD~5y$x?YM+5U05Gm6=}
gogogo(复现)
第一次做go题,很遗憾,就差一点就有了,拖进ida
1 | // main.main |
看着很长,实际也不短,不过go语言废话多,动调分析一下
1 | fmt_Fscanf( |
输入
1 | if ( len_1 >= 40 ) |
runtime_gcWriteBarrier1感觉是在加载函数,也就是main_main_gowrap1,main_main_gowrap2
加载之后后面runtime_newproc就是在调用了函数总之逻辑在上面这两个里(这里可以关注一下mem和mem赋值,这个是opcode)
1 | // main.(*coroutVM).run |
这个就是主要逻辑了,runtime_chanrecv2是在解析instr然后给v18,给了v18之后v18给v17再次解析(这里的instr就是opcode的当前部分?或者是总体指针没太关注)
最后解析结果给v13,后面v13给了v2,v2动调发现是一个叫main_mov的函数(并且会变)跟踪者往上寻找
1 | .rdata:0000000000299F60 add dq offset main_ADD ; DATA XREF: main_map_init_0:loc_267F5B↑o |
有点像vm的操作码了,并且在动调找操作码对应hex的时候,也就是runtime_mapaccess2解析后返回函数指针
1 | v41 = &v48[size]; |
这里有可能返回v41,所以动调去看v41里的值
1 | debug056:000000C000090000 qword_C000090000 dq 808041457215144Eh ; DATA XREF: debug056:000000C000081F68↑o |
这里一个hex紧跟着一个操作函数,并且0x2A对应mov在之前,v17中经常出现,本来我以为这个v17是输入,但是多次改变输入发现仍然会是0x2A, 0x00, 0x37, 0x9E,后面看到这里再加上之前在v2函数跟踪的时候点到过mem,然后看到了0x2A, 0x00, 0x37, 0x9E发现十分熟悉,所以猜测出mem是opcode(后面我才知道可以看结构体的定义来猜测)
于是有了操作函数对应字节码,有opcode,就可以解密了,因为这个调用的函数相同并且都是opcode[i+0]做字节码,opcode[i+1,2,3]参与运算,并且函数固定,所以这个vm不是很难写
1 |
|
但是这玩意太太太长了。。。懒得分析了,到此结束
。。。是xxtea。。。不做了