逆向工程实验——pre6(汇编、Android逆向、RSA算法破解)

一、 阅读然后回答2个问题

阅读
https://en.wikipedia.org/wiki/X86_calling_conventions
然后回答2个问题:
What does this code do?
The function has 4 arguments and it is compiled by GCC for Linux x64 ABI (i.e., arguments are passed in registers).

<f>:
0:  mov   r8,rdi    //把rdi寄存器中的值送给r8寄存器
3:  push    rbx      //将rbx寄存器中的值压入栈   
4:  mov   rdi,rsi     //把rsi寄存器中的值送给rdi寄存器 
7:  mov   rbx,rdx    //把rdx寄存器中的值送给rbx寄存器
a:  mov   rsi,r8    //把r8寄存器中的值送给rsi寄存器
d:  xor   rdx,rdx    //rdx寄存器异或置0
begin:
10:   lods   rax,QWORD PTR ds:[rsi]    //从串取指令,从DS:SI所指向的空间中取出一个字节/字/双字放入寄存器中AL/AX/EAX,同时把SI+(-)1/2/4.(LODS相当于MOV AL,[SI]和INC SI.)(取出一个值后地址加1,方便下次取出下一个值,rsi存放了源地址)
12:   div   rbx      // 无符号除法DX -AX / rbx,商回送AX,余数回送DX
15:   stos   QWORD PTR es:[rdi],rax    //保存串,是LODS的逆过程,保存在ES:DI中(保存一个值后地址加1,方便下次保存下一个值,rdi存放了目的地址)
17:   loop   begin     //CX不为零时循环
19:   pop   rbx        //rbx寄存器中的值出栈
1a:   mov   rax,rdx    //把rdx寄存器中的值0送给rax寄存器
1d:   ret            //返回  

四个参数,第一个参数是rsi,表示源地址;第二个参数是rdi,表示目的地址;第三个参数是rbx,表示除数;第四个参数是rcx,表示源地址上数据的数目。
原来的C语言代码应该是类似这样的:

void f(DWORD *arr1,DWORD *arr2,DWORD x,DWORD n)
	for(int i=0;i<n;i++)
	{
	arr2[i]=arr1[i]/x;
	}
	return ;

函数功能:就是将[参数2]指向的数组的前[参数4]个数的值都除以[参数3]之后,赋值给[参数1]指向的数组。

<f>:
   0:             push   rbp             //将rbp的值入栈
   1:             mov    rbp,rsp       //将rsp的值送到rbp
   4:             mov    QWORD PTR [rbp-0x8],rdi    //将rdi的值放到ds[rbp-0x8]地址的四个字的位置上
   8:             mov    QWORD PTR [rbp-0x10],rsi   //将rsi的值放到ds[rbp-0x10]地址的四个字的位置上

   c:             mov    rax,QWORD PTR [rbp-0x8]    	//将地址ds[rbp-0x8]上的四个字送到rax
  10:             movzx  eax,BYTE PTR [rax]        //将地址ds[rax]上的一个字节无符号扩展送到eax
  13:             movsx  dx,al               //将al有符号扩展送到dx
//将rdi的低字节有符号扩展后的值放在dx
  17:             mov    rax,QWORD PTR [rbp-0x10]    //将ds[rbp-0x10]的字送到rax
  1b:             mov    WORD PTR [rax],dx            //将dx的值送到ds[rax]地址
//把rdi的最低字节进行符号扩展后的值,给rsi的一个字
  1e:             mov    rax,QWORD PTR [rbp-0x10]   //将ds[rbp-0x10]的数据送到rax
  22:             movzx  eax,WORD PTR [rax]      //将ds[rax]的字无符号扩展送到eax
//把rsi的一个字给eax
  25:             test   ax,ax        //ax与ax进行与操作,不改变ax
  28:             jne    2c           //不为0就跳到2c

  2a:             jmp    38          //跳转到38
  2c:             add    QWORD PTR [rbp-0x8],0x1     //ds[rbp-0x8]地址的值加1
  31:             add    QWORD PTR [rbp-0x10],0x2   //ds[rbp-0x10]地址的值加2
  36:             jmp    c      //跳转到c

  38:             pop    rbp  //rbp出栈
  39:             ret             //返回

函数功能:将一个数组(rdi为首地址)按有符号扩展后存入另一个数组(rsi为首地址)。

二、阅读

https://www.pediy.com/kssd/pediy07/pediy7-94.htm
一个简单的linux crackme的逆向

三、阅读

https://blog.csdn.net/ir0nf1st/article/details/67799899
逆向一个IOS CrackMe
https://www.cnblogs.com/LittleHann/p/3374206.html
android逆向学习小结–CrackMe_1

复现android逆向过程

1、先在android模拟器上安装好CrackMe1.apk文件,

在这里插入图片描述
在这里插入图片描述

1.1再打开运行一下看它是要干什么:
在这里插入图片描述

只有一个输入框,那说明这个验证码的输入来自别的地方,因为我们知道,不管你的加密算法是啥,总是要有一个函数输入源的,我们在UI界面上输入的相当于是结果,而输入源应该来自于别的地方,计算完之后和我们在UI上输入的结果进行对比,大致是这个思路。

2、用apktool将CrackMe1.apk反编译:

在这里插入图片描述
在这里插入图片描述

2.1、查看AndroidManifest.xml文件。了解到主activity为:Main。
在这里插入图片描述

2.2、再到CrackMe1\res\values路径下,查看出现的字符串String之类的资源,出现了之前我们运行app时出现的名字:“lohan’s crackme1”、输入提示信息:“Please enter the registration code:”、无效的序列:“Invalid serial”等:
在这里插入图片描述

2.3、id:
在这里插入图片描述

2.3、String和id对应的编号:
在这里插入图片描述

3、接着我们要从apk中提取.dex文件

将CrackMe1.apk文件的后缀名改为.zip,之后解压缩:
在这里插入图片描述

3.1、dex文件转换为jar

将classes.dex复制到dex2jar的目录下,用dex2jar将classes.dex转换成classes-dex2jar.jar。
在这里插入图片描述

3.2、反编译jar包

用jd-gui反编译jar包,来查看java源代码:
在这里插入图片描述

看到里面很多的类、方法、变量都用a、b、c代替了,基本上可以判定是配ProGuard混淆了,不过问题也不大,虽然显示的是无意义的函数名,但是因为函数比较少,所以不太影响我们分析代码流程。

3.3、代码分析

先从Main.class开始分析:

  1. 先是onCreate()初始化函数,先初始化b和c的类。然后调用b.a()生成并存储"机器码",然后调用c.a(),也就是判断是否已经存储了serial,并判断是否能通过算法校验。如果不能通过,则什么都不做,这就是启动时检测注册状态的做法,即如果你之前已经注册了,那在之后的登录后就会自动识别出来,但是我们如果是第一次启动且没有注册,那这里就什么也不做。
    如果能通过,则调用自身的方法a()。而自身的方法a()又调用了c.b()方法,即检查我们输入的serial和机器码的MD5值是否相同,如果相同则什么也不做,如果不同就把下面的按钮和TextView等UI控件给隐藏了。并启动倒计时类a.start()。即二次验证。
    ps:
    这里要注意的是,由于程序使用了ProGuard来混淆代码,所以用jd-gui翻译出来的代码全都是从a,b,c开始计数,而且经常是变量、类、方法的命名混合了起来。我们在看java代码的时候遇到难懂的地方要结合smali代码一起看,这样才能获取比较准确的对程序代码流的把握。

public void onClick(View paramView)
if (c.a(((EditText)findViewById(2131034114)).getText().toString()) == 0)
{
Toast.makeText(this, 2130968577, 0).show();
return;
}
Toast.makeText(this, 2130968578, 0).show();
onClick()监听函数判断我们通过UI输入的serial是否和"机器码"的MD5值相同,如果不相同则弹出提示Invalid serial!(可以通过ID值反查出对应的字符串),如果相同则弹出Thanks for purchasing!
main的a()函数:
在这里插入图片描述

它的构造函数是负责初始化程序的用户界面,就是开始的输入框、按钮:
通过调用layout目录下的main.xml文件下的id号获取对应的控件,而main.xml文件下的id号又通过values目录下的ids.xml文件来自于publics.xml文件,例如(2131034113):
在这里插入图片描述

在文件中0x7f050001对应的就是2131034113,所以这样就能通过id号来找到相应的控件了。设置控件的可见性为4,意思是不可见的,但还占着原来的空间。
之后就是初始化a类,开始a类的倒计时功能:

3.4、a类的分析:

可以看到,类a是一个CountDownTimer:
在这里插入图片描述

我们看出这个类的功能是倒计时6秒, 从onFinish函数(计时完毕时触发)然后调用c.a(),也就是判断我们输入的serial是否等于"机器码"的MD5值。如果不能通过,就设置TextView内容提示注册无效。

3.5、类b的分析:

类 b 提供了一个公共的构造函数 public b(Context paramContext), 一个私有的成员函数private String b(), 以及一个公有成员函数 public final void a()。
b(): 通过TelephonyManager获取设备相关的一些信息,然后通过PackageManager获取到自身的签名。然后把这些字符串拼接起来返回给调用者:
在这里插入图片描述

a()调用方法b()获取字符串,然后通过SharedPreferences.Editor将这个字符串值存储到键machine_id,可以理解为机器码。也就是说,这个加密函数的输入是本机的机器码:
在这里插入图片描述

经过上面的分析,类b对外提供方法a,功能就是生成"机器码"并存储到系统中,对应的键为machine_id。

3.6、类c的分析:

类c提供的方法较多,我们逐个分析。

  1. 构造函数
    在这里插入图片描述

把参数上下文Context传入给自己的属性,并初始化两个字符串。

  1. public static String b()
    在这里插入图片描述

通过MessageDigest计算传入参数 的MD5值。

  1. public static boolean b()
    在这里插入图片描述

通过 getPackageManager 获取自身的签名,如果签名与构造函数中的两个字符串b(f0d412b5530e1f9841aab434d989cc77)或者c(4ec407446b872351e613111339daae9)任意一个相等,那么返回false,否则返回true。

  1. public static int a(String var0)
    在这里插入图片描述

可以看出这段代码的功能为计算机器码的 MD5,如果与传入的参数var0一致,那么通过SharedPreferences存入到serial(机器码的MD5值var0)字段中。 当然还有调用b方法进行一些判断,自身的签名不能是已知的两个。

  1. public static boolean a()
    在这里插入图片描述

这个其实就是上面的 int a(String var0)的包装函数,通过SharedPreferences获取serial字段(机器码的MD5值),并传给这个方法,返回相应的返回值(判断结果):
在这里插入图片描述

4. 破解方法:

4.1 单纯的破解,用代码注入的方法得到注册码

经过分析,我们知道应该在b.smali的155行:

move-result-object v2 这里代码注入,因为这个b()的作用就是获取当前"机器码"(注意,这里获取的是没有MD5之前的"机器码",因为程序中的MD5都是临时算出来的)。
我们在这里加入:

const-string v3, “SN” invoke-static {v3, v2},
Landroid/util/Log;>v(Ljava/lang/String;Ljava/lang/String;)I

在这里插入图片描述

用apktool将smalli代码重新回编译CrackMe1.apk。
在这里插入图片描述

还需要对CrackMe1.apk进行签名在android上才能运行:
在这里插入图片描述

在命令行中执行 adb logcat -s SN:v ,然后再启动程序,会在命令行中看到一大串字符串,这些字符串就是我们要的机器码。

4.2 读取程序对应的文件得到注册码

我们知道,所谓的SharedPreferences本质上是保存在当前程序空间下的/data/data//shared_prefs/ _preferences.xml 文件中的。
我们在android模拟器中找到这个文件,直接读取这个文件的内容:
在这里插入图片描述

可以看到,和我们通过代码注入的方式得到的机器码是相同的。

4.3 编写注册机得到注册码

这种方法是最好的,编写注册机要求我们对目标程序的代码有全盘的认识,然后模拟原本的算法或者逆向原本的算法写出注册机。
我们用Eclipse重新生成一个新的工程 com.lohan.crackme。注意,工程的报名必须和目标程序的包名一致,这样我们的注册机运行后得到的APK签名才会是一样的:
在这里插入图片描述

通过监听获取注册码:
在这里插入图片描述

四、 (2选1,原理一样的)

MTC3 Broadcasting and low exponent — RSA-Attack
https://www.mysterytwisterc3.org/en/challenges/level-ii/broadcasting-and-low-exponent–rsa-attack
Alice’s Birthday Party (Part 2)
https://www.mysterytwisterc3.org/en/challenges/level-ii/alices-birthday-party-part-2
hint:中国剩余定理 https://www.di-mgt.com.au/bigdigits.html

选的是Alice’s Birthday Party (Part 2):
题目当中给出了

N1=
514745167025222387434132377137056715954750729807151447929894289695587285793889099978536904494455862473045694392353612260528582074521711735864082380505874261026769465596315849668245703081452047808798727647904141791488099702631575692170683102622471798376397440600292225038412176681344166204027842724877162681931
N2=
332459552799915544356022641605448137617079921391832222557892949808060953028449422328281413629912335051440744955455010851012308918294549765005480121061697711447087615327860789708246235156912421474047484838827777697938563515420810650393553528058831317409340577149233554235346445890238642955390137465511286414033
N3=
665701912162243069059653781669230805473457427767514323262762891771122352328706695409103713864384833437438648120217615990765220365745013739246022203593234785338178963805463643869398986119431772931646042972240277833431035018628949924813463553419243108837309078316455504749755062865258063926243606206806549969161

e=3

c1=
159610386572167689266326385036487109027500941380400104125191262882664358398577536610497671009102596940624920315091422093100238619835848693651492344785000232303139338861093680138737091249739575100655219967271819921458016154329847843423233652818852580016834561970850695063090000199448970052668647861992230109134
c2=
80704323590708576386562863656130406931573788060159775931074197125212042930440694778363300836637666152530601069635539711403775897104413839059003511049631024172974390473641408894970527777947213128650545118958630567223577806350516381008539951304600069024003674444114727988917350720932569342357635015732615468372
c3=
290728542387622789691059470283422806073663108257730190721270583629901119139049111765276898786687400514004023098315787810926656039376046957101984075353288285867739293190825676944209163087896697394093577432590616749562076462942759742984949258019827469729922204479107792698042941392668070743176808454529741938138

也就是已知
在这里插入图片描述

根据中国剩余定理,是可以求出me,e=3,继而求出10进制明文。之后我们将它转换成16进制数字,再转换成Base64的字符串,最后解码即可得到明文字符串。
源代码:

# coding=utf-8
#py -2

#求最大公因数
def gcd(a, b):
    if not b:
        return a, 1, 0
    else :
        g, x, y = gcd(b, a % b)
        return g, y, x - y * (a / b)

def solve(n, A = [],B = []):
    M = 1
    ans = 0
    for i in A:
        M *= i
    for i in range(n):
        Mi = M / A[i]
        G, X, Y = gcd(Mi, A[i])
        ans = (ans + Mi * X * B[i]) % M
    return ans

def lf(n) :
   return n*n*n

def pd(n) :
   l=1
   r=n
   while (l<=r) :
      mid=(l+r)/2
      if (lf(mid)==n) :
         return 1
      if (lf(mid)>n) : r=mid-1
      if (lf(mid)<n) : l=mid+1
   return 0

def klf(n) :
   l=1
   r=n
   while (l<=r) :
      mid=(l+r)/2
      if (lf(mid)==n) :
         return mid
      if (lf(mid)>n) : r=mid-1
      if (lf(mid)<n) : l=mid+1

n=3
L1 = []
L2 = []
N1=514745167025222387434132377137056715954750729807151447929894289695587285793889099978536904494455862473045694392353612260528582074521711735864082380505874261026769465596315849668245703081452047808798727647904141791488099702631575692170683102622471798376397440600292225038412176681344166204027842724877162681931
N2=332459552799915544356022641605448137617079921391832222557892949808060953028449422328281413629912335051440744955455010851012308918294549765005480121061697711447087615327860789708246235156912421474047484838827777697938563515420810650393553528058831317409340577149233554235346445890238642955390137465511286414033
N3=665701912162243069059653781669230805473457427767514323262762891771122352328706695409103713864384833437438648120217615990765220365745013739246022203593234785338178963805463643869398986119431772931646042972240277833431035018628949924813463553419243108837309078316455504749755062865258063926243606206806549969161
C1=159610386572167689266326385036487109027500941380400104125191262882664358398577536610497671009102596940624920315091422093100238619835848693651492344785000232303139338861093680138737091249739575100655219967271819921458016154329847843423233652818852580016834561970850695063090000199448970052668647861992230109134
C2=80704323590708576386562863656130406931573788060159775931074197125212042930440694778363300836637666152530601069635539711403775897104413839059003511049631024172974390473641408894970527777947213128650545118958630567223577806350516381008539951304600069024003674444114727988917350720932569342357635015732615468372
C3=290728542387622789691059470283422806073663108257730190721270583629901119139049111765276898786687400514004023098315787810926656039376046957101984075353288285867739293190825676944209163087896697394093577432590616749562076462942759742984949258019827469729922204479107792698042941392668070743176808454529741938138
L1.append(C1)
L1.append(C2)
L1.append(C3)
L2.append(N1)
L2.append(N2)
L2.append(N3)
C=solve(n, L2, L1)
N=N1*N2*N3

ans=0
for k in range(1000010) :
   if pd(k*N+C)==1 :
      ans=klf(C)
      break
# 得到16进制的明文
v=[]
while (ans) :
   v.append(ans%16)
   ans=ans/16
len0=len(v)

for i in range(len(v)/2) :
   tmp=v[i]
   v[i]=v[len0-i-1]
   v[len0-i-1]=tmp
# 输出数字
for i in range(len(v)) :
   print(v[i]),
print

# 将数字转换为对应的Base64字符
string = ""
for i in range(len(v)/2) :
   num=0
   for j in range(2) :
      num=v[2*i+j]+num*16
      # 字符串
        string = string + chr(num)
    # 输出数字对应的Base64字符,且在python 2中输出不换行,不加空格:
   # sys.stdout.write(chr(num))
print string
# Base64解码
import base64
print(base64.b64decode(string.encode()))

实验结果截图:
在这里插入图片描述

解密后的结果是:Einladung zu meiner Geburtstagsparty. Die Party findet am 20.12.2012 in Bletchley statt.
这是德语,意思是“邀请你来参加我的生日派对。2012年12月12日在布莱奇利举行的派对”。
而题目中的问题是派对的地址,所以答案就是Bletchley。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值