狗儿

热爱的话就坚持吧~

0%

矿大 CUMTCTF 逆向 RE(完结撒花)

矿大CTF逆向区通关啦!

本文不再更新,完结撒花~

2020年6月14日 00:19

0x01 入门1

img

双击str2,直接看到flag

img

注意:flag形式为xman{可见字符}

0x02 入门2(第一次用python解题)

加密算法为将输入的字符串(从v5开始)逐位与v3(v3=0,每次自增一)异或,加密后的字符串(从v12开始)与从unk_403010开始的字符串比较

img

从unk_403010开始的字符串的值为

(注意,h指十六进制)

img

python跑一波

1
2
3
4
5
6
7
8
a = ['58','31','70','5C','35','76','59','69','38','7D','55','63','38','7F','6A']
b = []
for i in a:
b.append(int(i,16)) #先前误写成了int(a,16),编译不通过
v3=0
for i in b:
print(chr(i^v3),end='')
v3=v3+1

答案为

img

学长的WP中给出的python代码:

1
2
3
4
5
6
#coding=utf-8 
a=[0x58,0x31,0x70,0x5C,0x35,0x76,0x59,0x69,0x38,0x7D,0x55,0x63,0x38,0x7F,0x6A]
flag=''
for i in range(len(a)):
flag+=chr(a[i]^i)
print flag

0x03 入门3

刚打开,还没转C语言,就看到了

img

base64走一波

拿到flag

B4se64_i5_c0mmon

0x04 入门4

没做出来,主要是只知道是MD5,不知道怎么解题,特别是数据是如何在内存中存储的不清楚

看了学长的WP才知道就是对 7FEF6171469E80D32C0559F88B377245 进⾏还原

img

img

PS:

1、MD5中用的是十六进制

2、IDA中,右键数字选择 Hexadcecimal 把数字转成16进制显示(快捷键H。同样的R键能让数据以字符char的形式 显示)

一个对MD5算法讲解的笔记

https://www.cnblogs.com/arsense/p/6485073.html

0x05 入门7

不解释

img

0x06 软件逆向教学

img

easy

由下面的语句可知,输入的字符串长度为21

img

选中数据后按R键,将十进制数转换成字符显示

img

转换后:

img

将v5和v6反着读(大头端和小头端的区别,具体的不清楚,只知道是存储和显示上的差异)

flag{eas

1est_cra

ckme

}

合起来就是flag{eas1est_crackme}

0x07 找到字符串就赢了

img

主函数中有:

img

进入check函数

img

a1是check的形参,实际为输入的字符串

我走入的误区:

1.好久没做这个类型的题了,把(i+a1)^6理解成了i+a1的六次方,^在c++中是异或,在python中才是n次方,搞混了

2、以为是i+a1的六次方是否和i+134520928相等,实际上,选中134520928后,按h键转换为十六进制后就很清晰了,实际上就是个地址,加上个i后表示偏移量

img

构造已经很清晰了,输入的字符串与6异或后和0x804A00处开始的字符串比较是否相同。

界面内按g键

img

img

我一看,尾端一个等号,这不就是类似base64吗(但base64没有|~>等符号),我以为异或解出字符串后还有base64一次

python:

1
2
3
4
5
base64='bcg|nd4q6>cp1o7i0c|w4h|~6>?weios'
flag=''
for i in range(32):
flag+=chr(ord(base64[i])^6)
print(flag)

输出 deazhb2w08ev7i1o6ezq2nzx089qcoiu

base64不行,因为base64解码后,有的字符甚至都超出了127。我看着想了好几个小时,一直以为这串乱七八糟的东西一定跟base64有关,因为逆向后出现了base64的字样。

img

最终没办法,只能把这串“乱码”加上flag{}直接提交,通过了。。。。

我该说什么好呢??

0x08 据说数学不好的不会做

第一次接触动态调试。

用ida32打开后,f5转c,再按f9显示如下界面,选Local Windows debugger,点OK

img

点Yes

img

若路径含中文,则提示

img

img

img

关闭,将文件移动到英文路径,并修改文件名不含中文,重新打开。

(学长wp的文件名似乎是中文的,不太清楚这一块,先改成英文名,将就着用吧)

img

v20往后的17位为输入的字符串

分析可知

string[i]=v15[i]/v13[i]

所以要知道v15和v13的值

由于代码繁琐,不易得知,所以通过动调直接获得二者的值。

f9进入动调界面,

把光标移到要下断点的地⽅,按 F2 下断点,那条语句就会变成红⾊。(也可以通过点击该语句前面的圆圈下断点)

(程序会我们下断点的地⽅中断运行)

这里我们在第66行,while处下断点(此时v15和v13的具体值已经计算出并存储到相关位置)

img

在弹出的程序运行界面中输入一个任意的长度为17的字符串,回车(因为前面的if语句判断是否长度为17,若不是17,并不计算v15和v13)

此时,已经计算出具体值,找到v15的地址

双击程序里的v15,

img

img

则00D3FD48就是v15的地址,选中,复制。

鼠标点击Hex View-1的框,按g,弹出一个搜索框,将v15的地址粘贴进去(地址中的字母可以是小写的)

img

会跳转至v15的起始位置(下图中数值为66的地方)

img

从起始位置数17个单元(一个单元有几个字节跟具体的数据类型有关),

img

DWORD全称Double Word,是指注册表的键值,每个word为2个字节的长度,DWORD 双字即为4个字节

故有:

img

由上可知

v15=[0x66,0xd8,0x123,0x19c,0x267,0x2b2,0x309,0x370,0x39f,0x3b6,0x4ba,0x4ec,0x4d3,0x594,0x5eb,0x6e0,0x84d]

注意读取方式,如第三个黄框内的23 01 00 00 ,读取为00 00 01 23

同理可以读出v13的值

img

img

img

明显看出,v13的值是从1到17

知道了v15和v13的值,就可以写py了

1
2
3
4
5
6
v15=[0x66,0xd8,0x123,0x19c,0x267,0x2b2,0x309,
0x370,0x39f,0x3b6,0x4ba,0x4ec,0x4d3,0x594,0x5eb,0x6e0,0x84d]
flag=''
for i in range(17):
flag+=chr(v15[i]//(i+1)) #不是整除的话会报错
print(flag)

得到flag{song_ni_fen}

0x09 简单逆向

img

直接打开题目链接,(pdf文件在我电脑上默认用chrome打开),弹出

img

试一下,这并不是flag(于子洋发现事情并没有那么简单)

下载下来,用binwalk看了下,并不是复合文件。

可是pdf的文件也没法用ida呀

用winhex看了下,文件首位都没有明显的flag提示,但抱着试试看的态度从头开始往后翻,翻了一定的长度后

img

base64走一遍

flag{easy_J5_c0de_in_PDf}

但是,这也隐藏的太深了吧,谁会往后翻这么多?

而且,这是逆向吗??hhh

0x0A Easy_CrackMe

img

img

用ida打开,alt+T打开搜索字符串界面,输入Incorrect Password查找

img

跳转到含有该字符串的地方

img

此处可以看到‘Incorrect Password’与‘Congratulation!!’均被引用,Ctrl+x跟踪上去。

img

img

f5转c

img

可以看出当v3=97,v4=a5y,v5=aR3versing,String=69时输出Congratulation

v3=a,v4=5y,v5=R3versing,String=E

img

String存储在v3前面,所以为Ea5yR3versing

根据格式,得到cutmctf{Ea5yR3versing}

[原创]Easy_CrackMe小练习

https://bbs.pediy.com/thread-216972.htm

0x0B 入门6

迷宫题,在攻防世界的新手区做过。可参考:http://blog.iyzyi.com/index.php/archives/308/#toc-0x12maze

1572270207634

1572270238937

1
2
3
4
5
6
s = '********* *    ** * ** ** * ** ** * #* ** **** **      *********'
for i in range(len(s)):
if i % 8 == 7:
print(s[i],'\n',end='')
else:
print(s[i],end='')

1572270284770

起始位置在光标处,走到#

xman{dddddrrrrruuuuullldddr}

0x0C 普通的逆向

解题时用的IDA7.0,但是写题解时发现卡在加载界面,故使用的6.8。

1572270888819

很容易确定flag{****_***_*******_666}

(第23行开始)第一段的四个字符分别为ord('f') ^ 5 ord('l') ^ 0x19 ord('a') ^ ord('\f') ord('g') ^ 0x13cumt,注意第三个字符是换页\f,而不是字母f

(第45行开始)第二段的三个字符。已知v12 = ord('f'),求v11 = 0x12 ^ v12 = t , v10 = 0x17 ^ v11 = c ,即ctf

关键在于剩下的那段字符的解密。sub_4011C0将要求的字符加密后的密文为iueuihu。

掏出我的OD,在偏移首行0x1C0处下断点,f9直达。在程序中输入flag{cumt_ctf_iueuihu_666},其中iueuihu的目的是占位。

此时:

1572272893361

运行到偏移0x235处,之后就是加密。运行到偏移0x293时,可以发现ECX中已经出现了iueuihu加密后的qfufqrf。我们要找的就是加密后为iueuihu的字符串

1572273053366

进度回到运行到偏移0x235处。

1572273297647

来到0x3B3028处,

1572273474386

看来就是从这里取字符。

继续运行:

1572273629662

再结合IDA中的伪C代码,易于发现0x3B2FBF + ord('u') = 0x3B3034

所以取字符的原理已经知道了:对于字符x,其加密后的字符为地址0x3B2FBF + ord('x') 处的字符。

密文i地址为0x3B30310x3b3031 - 0x3B2FBF = 114 = r

之后的6个字符的解密类似。最终为reverse。

flag{cumt_ctf_reverse_666}

0x0D Register

究极easy的android逆向。

apktool反编译:

1
2
3
4
5
D:\计算机\CTF\工具\apktool_2.4.0.jar d -s D:\桌面\Register.apk -o D:\桌面\out

d <file.apk>:执行decode,<file.apk>表示被反编译的apk文件的绝对路径。
-s:指定不反编译源码,即保留classes.dex文件。
-o <outdir>:指定反编译之后生成文件的路径。

然后打开D:\桌面\out\res\values\strings.xml,发现flag.

0x0E brokenApk_00

一个损坏的apk,无法安装,也无法使用apktool反编译。解压后使用dex2jar将dex转换成jar。

1574867947153

关键代码整理为:ctext[count % textLen] = ctext[count % textLen] ^ key[count % keyLen]

但是只知道count的终值是50000,但是不知道count改动的过程是怎样的。

brokenApk_00\res\layout\main.xml是一个二进制文件,强制打开后发现

1574868102205

那就是点击按钮50000次喽,count从1到50000,但是写出的脚本跑出的flag为'lag{apk_1s_Zip_Fi1e>,尝试修改count从0到49999,跑出正确的flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ctext = ['=', '6', '(', '-', '0', '5', '\'', '!', '\020', '}', 
'"', '\013', '\r', '9', '!', '\r', '\027', '9', 'd', '7',
'a' ]
ctext = [ord(i) for i in ctext]

key = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z' ]
key = [ord(i) for i in key]

keyLen, textLen = len(key), len(ctext)

for count in range(0, 50000):
ctext[count % textLen] = ctext[count % textLen] ^ key[count % keyLen]
print(''.join(list(map(chr, ctext))))

0x10 linux

参考:

ida反编译后没发现什么有用的信息。放到linux下打开提示缺少libpython2.7

1574904858551

所以推测是python的打包后的可执行文件。

使用pyinstxtractor.py将exe转换为pyc文件。python pyinstxtractor.py [filename]

pyinstxtractor.py下载地址:https://github.com/countercept/python-exe-unpacker/blob/master/pyinstxtractor.py

1574905202218

1574905236178

将re04添加后缀名.py,打开,提示二进制文件,忽略并打开,看到源代码:

1574905305650

1574905323994

就是base64.

顺便说一下uncompyle的使用,虽然这题没用到。

1
2
3
4
5
6
7
8
#安装
pip install uncompyle

#反编译单个文件
uncompyle6 foo.pyc > foo.py

#反编译多个文件
uncompyle6 -o . *.pyc

反编译多个文件实测在windows下无效,linux可以。

0x11 Hide&Seek

安卓逆向,做之前有50+个同学做出来了,以为不会很难,但是浪费了数十个小时。我吐了,这根本不是安卓逆向好吗?分明就是Misc.

拿到app后,模拟器走一波:

1575045712566

1575045726751

apktool提取资源,dex2jar反编译dex,jd-gui查看代码。

ctrl+shift+s全局搜索2131296272。实名吐槽jd-gui的全局搜索极其不好用,经常搜不到存在的字符串。

1575045842809

1575045865534

来到res\drawable-hdpi-v4,里面一堆图片:

1575045929758

有两张明显的解码错误。

logi_.png末尾有一串疑似字符。

1575045994758

my_personal_not_login_bg.png左下角有flag字样:

my_personal_not_login_bg

所以就是flag{0HYOVFlNDM3}

0x12 KeygenMe

1575108477425

逻辑很简单,就一次异或而已。

1575108510324

对name字符串三个字符一组,分别与v6,v7,v8异或,得到的数据以2位16进制形式写入字符串v13。

aS02x:

1575108608389

%02x 格式控制: 以十六进制输出,2为指定的输出字段的宽度.如果位数小于2,则左端补0

1
2
3
4
5
6
7
8
9
10
s = '5B134977135E7D13'
serial = []
for i in range(len(s)//2):
serial.append(int(s[i*2:i*2+2], 16))
print(serial)
v6 = [16, 32, 48]
for i in range(len(serial)):
serial[i] ^= v6[i % 3]
print(serial)
print(''.join(list(map(chr, serial))))

有人可能会问为什么上上图中第40行是!strcmp(&v9, &v13)。我说过很多次了,不要过分依赖IDA的F5.

我们来看一下它的汇编:

1575112918799

①输入serial
②判断两字符是否相同
③判断字符是否为\0
④判断两字符串的下一个字符是否相同
⑤下标加2
⑥eax清零
⑦没搞懂。只知道sbb是借位减法
⑧eax为0则ZF赋1,则correct

0x13 珍贵资料

拿到一个ANDROID BACKUP文件和一个apk。

对于ANDROID BACKUP的利用可以参考https://github.com/wnagzihxa1n/CTF-Mobile/blob/master/2015XCTF-RCTF/flag-system-100/WriteUp.md

1575120914966

1
2
3
dd if=unknown bs=1 skip=24 of=compressed
printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" | cat - compressed | gunzip -c > decompressed.tar
tar xf decompressed.tar

1575120983320

但是输入账号和密码,登录失败。

所以我们把apk逆向一波。

LogoActivity.class中:

1575121118237

WelcomeAvtivity.class中:

1575121168555

所以我们把字符串i异或0xB,得到:flag is password。

所以把密码dudqlvqrero1提交,不对,尝试添上flag{},cumtctf{},都不对。

那就再看看反编译的代码吧。

1575121333042

选中的那一行的上一行,是将密码加密。

然后加密后的字符串与dudqlvqrero1比较。

(我开始被坑了,以为dudqlvqrero1就是密码,没想到是加密后的)

encode方法调用了Encryption,Encryption:

1575121510014

其实就是凯撒密码,key值为3

写出解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SOURCE = "ijklmstuvwxyz0123abcdenopqrfgh456789"
LEN = len(SOURCE)
s = list('dudqlvqrero1')
flag = []
for i in range(len(s)):
k = SOURCE.index(s[i])
j = k
if k == LEN - 1:
j = 1
k = j
if j == LEN - 2:
k = 2
j = k
if k == LEN -3:
j = 3
flag.append(SOURCE[j-3])
print(''.join(flag))

输出amanisnobody,添上flag{}提交。

0x14 入门8

1575127543995

主函数,输入,经过一系列处理得到v15(即v4),然后已知的数据byte_403024与v4异或。

由于v15是char,所以最多128种可能的取值,直接爆破就可以。不需要搞明白v15是怎么处理得到的。

35~39行的处理算法极其恶心,放个部分截图:

1575127796002

分析是极其困难的。所以还是爆破吧。

一开始我没注意到那一大串的处理其实并不是加密字符串,而是仅仅得到一个char类型的数据而已。

所以说不要程序走到一处后直到完全看明白再继续往下看呀!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
string = '!"#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\''
string_list = list(map(ord,string))
byte_403024 = [ 0x64, 0x00, 0x47, 0x47, 0x43, 0x04, 0x46, 0x50, 0x6B, 0x05, 0x47, 0x6B, 0x40, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x6B, 0x58, 0x04, 0x04, 0x53, 0x15]
for i in range(128):
flag = []
for j in range(63):
if byte_403024[j] ^ i not in string_list:
break
else:
flag.append(chr(byte_403024[j] ^ i))
if len(flag) == 63:
print(''.join(flag))

1575128079404

加上xman{}提交。

另外在这里写一个将十六进制从winhex导出的小技巧:

1575128162967

0x15 普普通通的逆向

1575160273931

upx壳,我使用命令行脱掉后程序无法运行。

1575160327024

看了看网上的学长的题解,他们都是使用upxshell脱壳,都可以正常运行。

但是我使用upxshell脱壳后,依然程序崩溃。

原因是选择的upx的版本不对。

1575160446344

选择UPX 2.03脱壳,就可以了。

1575160539415

1575160771728

这题输入用户名,然后对用户名加密,再与输入的密码比较。所以可以动态调试拿到加密后的用户名。

但是本题反调试比较多。

GetTickCount

第51行和第64行的GetTickCount(),作用是计算运算这段代码的时间,如果大于1秒,v7的值就会和正常运行时的值不同,然后v7代入到65行的sub_401240中,影响用户名的加密。

sub_401240:

1575161025950

上图的v3就是之前的v7。flag的字符和v7是相关的。

但是我们如果不去逆算法的话,根本没必要在第51行和第64行中间下断点,正常跑就行。所以不用刻意绕过这个反调试的机制,程序正常跑就可以。

CreateToolhelp32Snapshot

列出所有进程名称,与吾爱破解[LCG].exe 吾爱破解.exe idaq.exe idaq64.exe比较,如果存在同名进程,则影响v1,v2的取值,从而影响flag的计算。

1575161862935

IDA对中文不友好,OD很容易看出吾爱破解的字样。

v1,v2最初为1,若存在相应进程,则赋值为0。v1负责吾爱破解[LCG].exe 吾爱破解.exe,v2负责idaq.exe idaq64.exe

我们有两个思路,最简单的是把我们使用的吾爱破解[LCG].exe改名。还有就是判断完所有进程后,我们手动修改v1,v2的值。

IDA中找到开始计算flag的地址4013b3,基址401000,偏移3b3,巧的是OD的基址也是401000,所以OD中来到4013b3的地方下断点。

1575162220431

F9运行到此处。

v1和v2分别存储在bl,bh.

此时的ebx是00000100,bx是后四位,bh是01,bl是00.

因为此时我没开IDA,所以对应IDA的v2的值是01。如果你同时开了IDA和OD,ebx应该是00000000.

这些不重要,我们直接把ebx改为00000101.然后继续运行。

1575162420030

IsDebuggerPresent

该函数是被Kernel32.dll导出的,该函数没有参数,如果当前程序正在被调试的话,返回值为1,没有被调试的话,返回值为0

1575162495420

所以直接将v3(eax)改为0即可。

找到flag开始处理的地址40145b,这里我的OD没分析处理来这是代码。右键->分析->分析代码。就可以把十六进制转换成代码了。

在此处下断点,F9,修改eax为0,有的OD自带绕过IsDebuggerPresent()这个函数,就不用你去修改了。

在IDA中观察到4014f2开始strcmp

1575163323042

所有我们在此处下断点。

1575163349034

拿到flag.

学长的题解

由于我个人水平所限,这里粘贴一下学长写的WP.

来源:http://ronpa.top/2018/09/19/cumt%E5%AE%9E%E8%AE%AD%E5%B9%B3%E5%8F%B0writeup/

考各种常见反调试的绕过 挺好的一题 去年写的很吃力还没写出来 今年调的很吃力好歹写出来了23333

这题其实完全不需要去逆向算法 因为题目只是要求得到给定username的密码 程序最后直接是把输入的密码和生成的密码进行比较
所有只需要绕过所有反调试 最后把比较的密码给提取出来即可得到flag

一上来发现文件加了壳 最简单的upx压缩壳 选择用upxshell脱壳

img

简单看一下主函数

  1. 开头有个输入检测 username是否是数字

  2. 具体把生成密码的分成了四段

  3. 第一个函数 0x4011E0 前有两个调用GetTickCount()来检测时间间隔 并且中间有遍历文件入口检测int3断点的语句 一旦在两次调用间下断点或者有int3断点 就会得到错误的参数v7 然后进函数则会生成错误的密码的6-9位

  4. 第二个函数 0x401240 内有一个获取进程的函数CreateToolhelp32Snapshot() 假如检测到常见调试器的进程 则会得到错误的中间第10-13位密码

  5. 第三个函数 0x401420 内有IsDebuggerPresent()函数检测调试状态 得到第14-17位密码

  6. 第四个函数 0x4014b0 里直接给了flag的开头和结尾flag{} 最后把生成的密码和输入的密码进行对比

  7. 所以最快速的方法就是绕过上面三个检测 在最后的比较里提取生成的密码

    img

接下来就是绕过

GetTickCount() & int3检测

直接不用管,两次中间的夹的代码其实是一个int3断点的检测 例如OD,其是将代码设置为0xcc来实现普通断点 所以从头到尾遍历文件 假如有0xcc则存在调试.这个和GetTickCount()组合防止我们在while里更改 但可以在循环结束后把v7和v11改为0
我用的加了插件的OD把这个0xcc检测绕过去了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
v9 = GetTickCount();
v5 = &loc_401052;
v6 = 64;
do
{
if ( !v6 )
break;
v4 = *v5++ == -52;
--v6;
}
while ( !v4 );
if ( v4 )
v11 = 1;
v7 = (GetTickCount() - v9) / 0x3E8 > 1;
sub_4011E0(v11, (int)&v19, v7);

img

CreateToolhelp32Snapshot()

查找进程信息,并和字符吾爱破解[LCG].exe 吾爱破解.exe idaq.exe idaq64.exe 假如进程中有同名进程 则代表检测到调试器 生成错误字符.

img

就比如下图,是我们第一个system进程和他要检测的调试器进程名吾爱破解[LCG].exe进行比较

wcscmp()(它用来比较两个Unicode字符串是否相等 和strcmp相同 相等返回0)

img

这里绕过的方法很多 最搞笑的方法就是你直接把调试器的名字给改了就行,比如我用的是吾爱破解[LCG].exe改成OD.exe就不会被检测了233333

或者可以在调试的时候最后直接把v1=1 v2=1改成正确的值 1

IsDebuggerPresent()

该函数是被Kernel32.dll导出的,该函数没有参数,如果当前程序正在被调试的话,返回值为1,没有被调试的话,返回值为0
这个直接改V3值为0就行 我的OD直接绕过了

都绕过之后提取正确密码

img

041751300132
flag{antidebugabc}

img

总结

这题我的可能算是取巧的做法 完全没去关注算法 但要去逆向这题代码也需要知道反调试函数的返回值.

  1. GetTickCount() 获取当前时间 通过两次调用相减可以得到时间差 防止中间下断点 绕过方法 最后修改值
  2. 0xcc int3断点检测 有些调试器下断会改变文件值 遍历全局去检测是否有字节为0xcc来检测是否被调试 绕过方法 用有插件的OD
  3. CreateToolhelp32Snapshot() 看是那种 检测父进程还是调试器还是权限 绕过方法 更改进程名称
  4. IsDebuggerPresent() 如果当前程序正在被调试的话,返回值为1,没有被调试的话,返回值为0 绕过方法 用有插件的OD/最后修改值

img

看雪反调试介绍

0x16 什么是密钥?

先用upx2.03脱壳:

1592063141978

1592064427221

1592064775547

前半部分是极其复杂的算法,有md5和sha1,将输入的字符串加密后放入source(最后一步变换是取sha1,结果是长度为20的一个字符串),然后Dest等于source的前20个字符加上v4,v5两个随机数,共22个字符。

虽然很复杂,但是完全不需要看懂前半部分。

后半部分总共进行了三次异或:

byte_405450 = byte_405450 ^ Dest ^ time_curent ^ Dest = byte_405450 ^ time_current

考虑到byte_405450[]的初值为table[time_current],上式可化为:

byte_405450 = table[time_current] ^ time_current

这样可以惊奇的发现,最终的结果与输入无关!只与已知的table数据表和time_current有关。那我们爆破time_current即可。

idc脚本提取table的数据,输出到D:/1.txt,然后写出下面的脚本:

1
2
3
4
5
6
7
8
9
#with open('D:\\1.txt', 'rb')as f:
# table = f.read()
table = b'N\x0b\x0c!8{7\x17no{ku\x1f-V*\x0cZYe+n\x16\x0b\x17-DZ\x17Zys~xdVqojk@vl@q/k@tzfb\x0c\x15\x16,BXd\x15tzfb\x0c\x15\x16,BXd\x15u\x1f-V*\x0cZYe+n\x16qojk@vl@q/k@qojk@vl7\x17no{kZ\x17ys~x\x1f-V*\x00\x00\x00\x00\x00\x00\x00\x00'
for time in range(60):
flag = ''
for j in range(22):
flag += chr(table[time + j] ^ time)
if 'flag' in flag:
print(flag)

0x17 Hook&Crack

本题同时单独发表在安卓逆向之log插桩

1592053619784

java

c.c.a中找到tu.class:

1592053539056

红框处是反调试的代码,如果修改了apk重新打包,再次运行会终止进程。

进入sd.dfh中看一下:

1592053729337

进入jh.gfds:

1592053815578

最内侧的df.gr是一个很长的二进制字符串,外侧的函数都是解密操作。

由于变量名被混淆了,解密代码很难读懂,而且flag在程序运行中明文出现过,所以可以考虑插桩,当将二进制字符串解密出flag时,可以使用log输出flag。

删掉反调试代码

我们来到smali层:

打开tu.smali,删掉下面的代码:

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
.line 35
invoke-virtual {p0}, Lc/c/a/tu;->getApplicationInfo()Landroid/content/pm/ApplicationInfo;

move-result-object v1

iget v2, v1, Landroid/content/pm/ApplicationInfo;->flags:I

.line 36
and-int/lit8 v2, v2, 0x2

.line 35
iput v2, v1, Landroid/content/pm/ApplicationInfo;->flags:I

if-eqz v2, :cond_0

.line 37
invoke-static {}, Landroid/os/Process;->myPid()I

move-result v1

invoke-static {v1}, Landroid/os/Process;->killProcess(I)V

.line 39
:cond_0
invoke-static {}, Landroid/os/Debug;->isDebuggerConnected()Z

move-result v1

if-eqz v1, :cond_1

.line 40
invoke-static {}, Landroid/os/Process;->myPid()I

move-result v1

invoke-static {v1}, Landroid/os/Process;->killProcess(I)V

.line 42
:cond_1
invoke-virtual {p0}, Lc/c/a/tu;->isRunningInEmualtor()Z

move-result v1

if-eqz v1, :cond_2

.line 43
invoke-static {}, Landroid/os/Process;->myPid()I

move-result v1

invoke-static {v1}, Landroid/os/Process;->killProcess(I)V

.line 45
:cond_2
const-string v1, "com.example.secret"

invoke-virtual {p0, v1}, Lc/c/a/tu;->getSignature(Ljava/lang/String;)I

move-result v0

.line 49
.local v0, "sig":I
invoke-direct {p0}, Lc/c/a/tu;->checkCRC()Z

move-result v1

if-eqz v1, :cond_3

.line 50
invoke-static {}, Landroid/os/Process;->myPid()I

move-result v1

invoke-static {v1}, Landroid/os/Process;->killProcess(I)V

1592054095920

log插桩

来到sd.smali:

1592054494071

把上图的第27行删掉,不要销毁flag。并在此处添加

1
2
3
const-string v1, "iyzyi-flag"

invoke-static {v1, v0}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I

同时将第18行的.locals 1改为.locals 2,以增加一个临时变量。

1592059539665

编译成新的apk后,再次反编译,可以看到java代码变成了这样:

tu.class:

1592059721721

sd.class:

1592059681816

adb查看log

夜神模拟器

安装apk后,在夜神模拟器的安装目录下打开powershell,输入:

连接设备:

./adb.exe devices

输出log:

./adb.exe logcat -s iyzyi-flag:v

iyzyi-flag是log的标签。

1592060153384

base64解码后即得flag.

手机

以Honor v10为例。

多次点击版本号,进入开发者模式:

TIM图片20200613230529

进入开发人员选项,启用USB调试“仅充电”模式下允许ADB调试

tm

通过USB线和电脑连接,允许USB调试:

sdfsd

然后手机安装刚刚插桩的apk。

从网上下载adb,并在其目录下打开powershell,剩下的adb操作和夜神模拟器的操作是一样的:

1592061105576

./adb.exe logcat -c的作用是清空log缓存区。