Reverse(七)
Java逆向解密
https://buuoj.cn/challenges#Java%E9%80%86%E5%90%91%E8%A7%A3%E5%AF%86
jd-gui逆向class文件为java
import java.util.ArrayList;
import java.util.Scanner;
public class Reverse {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
System.out.println("Please input the flag );
String str = s.next();
System.out.println("Your input is );
System.out.println(str);
char[] stringArr = str.toCharArray();
Encrypt(stringArr);
}
public static void Encrypt(char[] arr) {
ArrayList<Integer> Resultlist = new ArrayList<>();
for (int i = 0; i < arr.length; i++) {
int result = arr[i] + 64 ^ 0x20; // 转为数字格式
Resultlist.add(Integer.valueOf(result));
}
int[] KEY = {
180, 136, 137, 147, 191, 137, 147, 191, 148, 136,
133, 191, 134, 140, 129, 135, 191, 65 };
ArrayList<Integer> KEYList = new ArrayList<>();
for (int j = 0; j < KEY.length; j++)
KEYList.add(Integer.valueOf(KEY[j]));
System.out.println("Result:");
if (Resultlist.equals(KEYList)) { // 逐一匹配
System.out.println("Congratulations);
} else {
System.err.println("Error);
}
}
}
python解密即可
luck_guy
https://buuoj.cn/challenges#[GXYCTF2019]luck_guy
exeinfo确定为64位程序,找到main的关键函数
unsigned __int64 get_flag()
{
unsigned int v0; // eax
int i; // [rsp+4h] [rbp-3Ch]
int j; // [rsp+8h] [rbp-38h]
__int64 s; // [rsp+10h] [rbp-30h] BYREF
char v5; // [rsp+18h] [rbp-28h]
unsigned __int64 v6; // [rsp+38h] [rbp-8h]
v6 = __readfsqword(0x28u);
v0 = time(0LL); // 设置时间种子
srand(v0);
for ( i = 0; i <= 4; ++i )
{
switch ( rand() % 200 ) // 伪随机数(生成数固定,猜测个顺序)
{
case 1: // 最后,因为进行了打印变量
puts("OK, it's flag:");
memset(&s, 0, 0x28uLL);
strcat((char *)&s, f1); // f1='GXY{do_not_'
strcat((char *)&s, &f2);
printf("%s", (const char *)&s);
break;
case 2: // 没用
printf("Solar not like you");
break;
case 3: // 没用
printf("Solar want a girlfriend");
break;
case 4: // 给f2做了个初始赋值,是第一步
s = 0x7F666F6067756369LL;
v5 = 0;
strcat(&f2, (const char *)&s);
break;
case 5: // 对f2的个别位做了修改,应该是第二步
for ( j = 0; j <= 7; ++j )
{
if ( j % 2 == 1 )
*(&f2 + j) -= 2;
else
--*(&f2 + j);
}
break;
default:
puts("emmm,you can't find flag 23333");
break;
}
}
return __readfsqword(0x28u) ^ v6;
}
python解密f2:得到后半部分为hate_me},合并即为 GXY{do_not_hate_me}
s = '7F666F6067756369'
f2 = bytes.fromhex(s).decode()
f2 = f2[::-1] # 逆转
for i in range(8):
if i % 2 == 1:
print(chr(ord(f2[i])-2), end="")
else:
print(chr(ord(f2[i])-1), end="")
print()
刮开有奖1
https://buuoj.cn/challenges#%E5%88%AE%E5%BC%80%E6%9C%89%E5%A5%96
ida64+查找字符串定位核心加密函数如下,需求解String
INT_PTR __stdcall DialogFunc(HWND hDlg, UINT a2, WPARAM a3, LPARAM a4)
{
const char *v4; // esi
const char *v5; // edi
int v7[2]; // [esp+8h] [ebp-20030h] BYREF // 这里很关键,后面的v8-v16其实都是v7数组
int v8; // [esp+10h] [ebp-20028h]
int v9; // [esp+14h] [ebp-20024h]
int v10; // [esp+18h] [ebp-20020h]
int v11; // [esp+1Ch] [ebp-2001Ch]
int v12; // [esp+20h] [ebp-20018h]
int v13; // [esp+24h] [ebp-20014h]
int v14; // [esp+28h] [ebp-20010h]
int v15; // [esp+2Ch] [ebp-2000Ch]
int v16; // [esp+30h] [ebp-20008h]
CHAR String[65536]; // [esp+34h] [ebp-20004h] BYREF
char v18[65536]; // [esp+10034h] [ebp-10004h] BYREF
if ( a2 == 272 )
return 1;
if ( a2 != 273 )
return 0;
if ( (_WORD)a3 == 1001 )
{
memset(String, 0, 0xFFFFu);
GetDlgItemTextA(hDlg, 1000, String, 0xFFFF);
if ( strlen(String) == 8 )
{
v7[0] = 90;
v7[1] = 74; //
v8 = 83;
v9 = 69;
v10 = 67;
v11 = 97;
v12 = 78;
v13 = 72;
v14 = 51;
v15 = 110;
v16 = 103;
sub_4010F0(v7, 0, 10);
memset(v18, 0, 0xFFFFu);
v18[0] = String[5];
v18[2] = String[7];
v18[1] = String[6];
v4 = (const char *)sub_401000(v18, strlen(v18));
memset(v18, 0, 0xFFFFu);
v18[1] = String[3];
v18[0] = String[2];
v18[2] = String[4];
v5 = (const char *)sub_401000(v18, strlen(v18));
if ( String[0] == v7[0] + 34
&& String[1] == v10
&& 4 * String[2] - 141 == 3 * v8
&& String[3] / 4 == 2 * (v13 / 9)
&& !strcmp(v4, "ak1w")
&& !strcmp(v5, "V1Ax") )
{
MessageBoxA(hDlg, "U g3t 1T!", "@_@", 0);
}
}
return 0;
}
if ( (_WORD)a3 != 1 && (_WORD)a3 != 2 )
return 0;
EndDialog(hDlg, (unsigned __int16)a3);
return 1;
}
sub_4010F0函数:a1是数组(长为11)的话,可以看出来函数对a1里的值做了修改,a2=0,a3=10
int __cdecl sub_4010F0(int a1, int a2, int a3)
{
int result; // eax
int i; // esi
int v5; // ecx
int v6; // edx
result = a3; // 初始为10
for ( i = a2; i <= a3; a2 = i ) // 从0到10
{
v5 = 4 * i;
v6 = *(_DWORD *)(4 * i + a1); // 之所以乘四是因为int长4个字节,每次往后取一个数组int值
if ( a2 < result && i < result )
{
do
{
if ( v6 > *(_DWORD *)(a1 + 4 * result) )
{
if ( i >= result )
break;
++i;
*(_DWORD *)(v5 + a1) = *(_DWORD *)(a1 + 4 * result);
if ( i >= result )
break;
while ( *(_DWORD *)(a1 + 4 * i) <= v6 )
{
if ( ++i >= result )
goto LABEL_13;
}
if ( i >= result )
break;
v5 = 4 * i;
*(_DWORD *)(a1 + 4 * result) = *(_DWORD *)(4 * i + a1);
}
--result;
}
while ( i < result );
}
LABEL_13:
*(_DWORD *)(a1 + 4 * result) = v6;
sub_4010F0(a1, a2, i - 1);
result = a3;
++i;
}
return result;
}
还原后的c代码,难点主要是数组的还原,其他都不用改(代码蒟蒻,只能勉强理清逻辑,大概是交换排序算法,反汇编混淆的有点厉害)
#include<stdio.h>
int __cdecl sub_4010F0(int *a1, int a2, int a3)
{
int result; // eax
int i; // esi
int v5; // ecx
int v6; // edx
result = a3; // 初始为10
for ( i = a2; i <= a3; a2 = i ) // 从0到10
{
v5 = i;
v6 = a1[i]; // 之所以乘四是因为int长4个字节,每次往后取一个数组int值
if ( a2 < result && i < result )
{
do
{
if ( v6 > a1[result] )
{
if ( i >= result )
break;
++i;
a1[v5] = a1[result];
if ( i >= result )
break;
while ( a1[i] <= v6 )
{
if ( ++i >= result )
goto LABEL_13;
}
if ( i >= result )
break;
v5 = i;
a1[result] = a1[i];
}
--result;
}
while ( i < result );
}
LABEL_13:
a1[result] = v6;
sub_4010F0(a1, a2, i - 1); // 又调用了
result = a3;
++i;
}
return result;
}
int main() {
int str[] = {90, 74, 83, 69, 67, 97, 78, 72, 51, 110, 103};
sub_4010F0(str, 0, 10);
for (int i = 0; i <= 10; i++) {
printf("%d ", str[i]);
}
}
得到结果为51 67 69 72 74 78 83 90 97 103 110,可以看出是按照ASCII码大小从小到大排序
sub_401000函数进入后可以看到base64的码表(0-9a-zA-Z这样),因此判断为base64加密只需逆向解密即可
ak1w——jMp,V1Ax——WP1,
求解:String[0] = v7[0] + 34 = U(85) String[1] = J(74) String[2] = (3 69 + 141) / 4 = W(87) String[3] = 2 (90 / 9) * 4 = P(80) String[4] = v18[2] (第二串) = 1 String[5] = v18[0] (第一串) = j String[6] = v18[1] (第一串) = M String[7] = v18[2] (第一串) = p
综上:String=UJWP1jMp
easy-100(LCTF)
https://ctf.bugku.com/challenges/detail/id/121.html
jeb反编译查看java核心代码
public class MainActivity extends q {
private String v;
public MainActivity() {
super();
}
static String a(MainActivity arg1) {
return arg1.v; // 调用私有字符串变量
}
static boolean a(MainActivity arg1, String arg2, String arg3) { // d.java调用,关键是arg3参数
return arg1.a(arg2, arg3); // 调用私有函数a
}
private boolean a(String arg4, String arg5) {
return new c().a(arg4, arg5).equals(new String(new byte[]{21, -93, -68, -94, 86, 117, -19, -68, -92, 33, 50, 118, 16, 13, 1, -15, -13, 3, 4, 103, -18, 81, 30, 68, 54, -93, 44, -23, 93, 98, 5, 59})); // 这里新调用了一个c类的a函数
}
protected void onCreate(Bundle arg3) {
super.onCreate(arg3);
this.setContentView(2130968602);
ApplicationInfo v0 = this.getApplicationInfo();
v0.flags &= 2;
this.p();
this.findViewById(2131427413).setOnClickListener(new d(this)); // 监听点击提交的函数
}
private void p() { // 最为关键
try {
InputStream v0_1 = this.getResources().getAssets().open("url.png"); // 获取图像
int v1 = v0_1.available();
byte[] v2 = new byte[v1];
v0_1.read(v2, 0, v1);
byte[] v0_2 = new byte[16];
System.arraycopy(v2, 144, v0_2, 0, 16);
this.v = new String(v0_2, "utf-8");
}
catch(Exception v0) {
v0.printStackTrace();
}
}
}
d.java如下
class d implements View$OnClickListener {
d(MainActivity arg1) {
this.a = arg1;
super();
}
public void onClick(View arg5) {
if(MainActivity.a(this.a, MainActivity.a(this.a), this.a.findViewById(2131427414).getText().toString())) { // 点击后又使用了MainActivity的a函数,提交的第二个参数是MainActivity.a返回的是一个预先构造好的字符串(在p函数里构造的),第三个参数是输入的字符串
View v0 = this.a.findViewById(2131427412);
Toast.makeText(this.a.getApplicationContext(), "Congratulations!", 1).show();
((TextView)v0).setText(2131099682);
}
else {
Toast.makeText(this.a.getApplicationContext(), "Oh no.", 1).show();
}
}
}
c.java如下
public class c {
public c() {
super();
}
public String a(String arg5, String arg6) {
String v0 = this.a(arg5); // 对v字符串调用私有方法处理
String v1 = "";
a v2 = new a(); // 又调用了a类
v2.a(v0.getBytes()); // 生成密钥
try {
v0 = new String(v2.b(arg6.getBytes()), "utf-8"); // 加密输入字符串
}
catch(Exception v0_1) {
v0_1.printStackTrace();
v0 = v1;
}
return v0;
}
private String a(String arg4) {
String v0_2;
try {
arg4.getBytes("utf-8");
StringBuilder v1 = new StringBuilder();
int v0_1;
for(v0_1 = 0; v0_1 < arg4.length(); v0_1 += 2) {
v1.append(arg4.charAt(v0_1 + 1));
v1.append(arg4.charAt(v0_1));
}
v0_2 = v1.toString();
}
catch(UnsupportedEncodingException v0) {
v0.printStackTrace();
v0_2 = null;
}
return v0_2;
}
}
a.java
public class a {
private SecretKeySpec a;
private Cipher b;
public a() {
super();
}
protected void a(byte[] arg4) {
if(arg4 != null) {
goto label_15;
}
try {
this.a = new SecretKeySpec(MessageDigest.getInstance("MD5").digest("".getBytes("utf-8")), "AES");
this.b = Cipher.getInstance("AES/ECB/PKCS5Padding");
return;
label_15:
this.a = new SecretKeySpec(arg4, "AES"); // 密钥
this.b = Cipher.getInstance("AES/ECB/PKCS5Padding");
}
catch(UnsupportedEncodingException v0) {
v0.printStackTrace();
}
catch(NoSuchAlgorithmException v0_1) {
v0_1.printStackTrace();
}
catch(NoSuchPaddingException v0_2) {
v0_2.printStackTrace();
}
}
protected byte[] b(byte[] arg4) {
this.b.init(1, this.a);
return this.b.doFinal(arg4);
}
}
至此已经知道这是个AES加密,需要分析密钥生成
这道简单题困扰着我一中午,原因:
- url.png是assets里的,不是res/drawable里的;
- 本来用java文件写代码,发现始终报错读不到文件,恼火,后来才发现,必须要有抛出,所以照着原来的代码就可以过的。。。属于java学的不精了
编写java解密(好久没拼这么多的java了,很不熟练,一个个报错)
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
public class easy {
public static String get_key() {
// 获取密钥
try {
File file = new File("url.png");
FileInputStream v0_1 = new FileInputStream(file);
int v1 = v0_1.available();
byte[] v2 = new byte[v1];
v0_1.read(v2, 0, v1);
byte[] v0_2 = new byte[16];
System.arraycopy(v2, 144, v0_2, 0, 16); // 144:144+16的字节串
String v = new String(v0_2, "utf-8");
System.out.println("key: "+v);
return v;
}
catch(Exception v0) {
v0.printStackTrace();
return null;
}
}
public static String a(String arg4) {
String v0_2;
try {
arg4.getBytes("utf-8");
StringBuilder v1 = new StringBuilder();
int v0_1;
for(v0_1 = 0; v0_1 < arg4.length(); v0_1 += 2) {
v1.append(arg4.charAt(v0_1 + 1));
v1.append(arg4.charAt(v0_1));
}
v0_2 = v1.toString();
}
catch(UnsupportedEncodingException v0) {
v0.printStackTrace();
v0_2 = null;
}
return v0_2;
}
public static void Decrypt(String v, byte[] c) {
// AES解密
SecretKeySpec a;
Cipher b;
try {
a = new SecretKeySpec(v.getBytes(), "AES"); // 密钥
b = Cipher.getInstance("AES/ECB/PKCS5Padding");
b.init(Cipher.DECRYPT_MODE, a);
byte[] input = b.doFinal(c);
String flag = new String(input, "utf-8");
System.out.println(flag);
}
catch(Exception v0) {
v0.printStackTrace();
}
}
public static void main(String[] args) {
String key = get_key();
key = a(key);
Decrypt(key, new byte[]{21, -93, -68, -94, 86, 117, -19, -68, -92, 33, 50, 118, 16, 13, 1, -15, -13, 3, 4, 103, -18, 81, 30, 68, 54, -93, 44, -23, 93, 98, 5, 59});
}
}
SafeBox(NJCTF)
https://ctf.bugku.com/challenges/detail/id/122.html
这道题绕了个弯,把真实的代码隐藏在了androidTest里而不是MainActivity,得看AndroidManifest(算是长了个教训,卡了一会)
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.geekerchina.hi" platformBuildVersionCode="25" platformBuildVersionName="7.1.1" xmlns:android="http://schemas.android.com/apk/res/android">
<uses-sdk android:minSdkVersion="15" android:targetSdkVersion="25" />
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme">
<activity android:label="@string/app_name" android:name="com.geekerchina.hi.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:label="@string/app_name" android:name="com.geekerchina.hi.androidTest">
<intent-filter>
<action android:name="android.intent.action.androidTest" />
</intent-filter>
</activity>
</application>
</manifest>
如上有两个activity标签,有一个调用的是"com.geekerchina.hi.androidTest",所以俩都得试试,最后发现是androidTest
public class androidTest extends AppCompatActivity {
public androidTest() {
super();
}
protected void onCreate(Bundle arg4) {
super.onCreate(arg4);
this.setContentView(2130968604);
this.findViewById(2131427415).setOnClickListener(new View$OnClickListener(this.findViewById(2131427414)) {
public void onClick(View arg13) {
int v11 = 3;
String v6 = "NJCTF{have";
int v4 = Integer.parseInt(this.val$Et1.getText().toString());
if(v4 > 10000000 && v4 < 99999999) {
int v7 = 1;
int v8 = 10000000;
int v3 = 1;
if(Math.abs(v4 / 1000 % 100 - 36) == v11 && v4 % 1000 % 584 == 0) {
int v5 = 0;
while(v5 < v11) {
if(v4 / v7 % 10 != v4 / v8 % 10) {
v3 = 0;
}
else {
v7 *= 10;
v8 /= 10;
++v5;
continue;
}
break;
}
if(v3 != 1) {
return;
}
this.val$Et1.setText(v6 + (((char)(v4 / 1000000))) + (((char)(v4 / 10000 % 100))) + (((char)(v4 / 100 % 100 + 10))) + "f4n}");
}
}
}
});
}
}
java脚本循环验证即可:
public class safebox {
public static void main(String args[]) {
int v11 = 3;
String v6 = "NJCTF{have";
for (int v4 = 10000000; v4 < 99999999; v4++) {
int v7 = 1;
int v8 = 10000000;
int v3 = 1;
if(Math.abs(v4 / 1000 % 100 - 36) == v11 && v4 % 1000 % 584 == 0) {
int v5 = 0;
while(v5 < v11) {
if(v4 / v7 % 10 != v4 / v8 % 10) {
v3 = 0;
}
else {
v7 *= 10;
v8 /= 10;
++v5;
continue;
}
break;
}
if(v3 != 1) {
continue;
}
System.out.println(v6 + (((char)(v4 / 1000000))) + (((char)(v4 / 10000 % 100))) + (((char)(v4 / 100 % 100 + 10))) + "f4n}");
}
}
}
}
特殊的Base64
https://ctf.bugku.com/challenges/detail/id/153.html
ida64分析,发现base64加密相关字符串,base64特殊在换了码表,对应切换下即可
import base64
table = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0987654321/+'
correct_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
c = 'mTyqm7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI=='
new_c = []
for i in range(len(c)):
if c[i] != '=':
new_c.append(correct_table[table.index(c[i])])
else:
new_c.append(c[i])
print(base64.b64decode(''.join(new_c)))
Mountain climbing
https://ctf.bugku.com/challenges/detail/id/125.html
upx脱壳,ida32查核心代码,还有必须得装VC,试了本机win11和虚拟机winxp,装了dll也没用,没办法因为懒的装VC所以找回了旧电脑(以前上汇编课装了VC)
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
char v4; // [esp+0h] [ebp-160h]
char v5; // [esp+0h] [ebp-160h]
int v6; // [esp+D0h] [ebp-90h]
int j; // [esp+DCh] [ebp-84h]
int v8; // [esp+DCh] [ebp-84h]
int i; // [esp+E8h] [ebp-78h]
int v10; // [esp+E8h] [ebp-78h]
char Str[104]; // [esp+F4h] [ebp-6Ch] BYREF
srand(0xCu);
j_memset(&unk_423D80, 0, 0x9C40u);
for ( i = 1; i <= 20; ++i )
{
for ( j = 1; j <= i; ++j )
dword_41A138[100 * i + j] = rand() % 100000;
}
sub_41134D("input your key with your operation can get the maximum:", v4);
sub_411249("%s", (char)Str);
if ( j_strlen(Str) == 19 )
{
sub_41114F(Str);
v6 = 0;
v8 = 1;
v10 = 1;
*(_DWORD *)dword_423D78 += dword_41A138[101];
while ( v6 < 19 )
{
if ( Str[v6] == 76 )
{
++v10;
*(_DWORD *)dword_423D78 += dword_41A138[100 * v10 + v8];
}
else
{
if ( Str[v6] != 82 )
goto LABEL_8;
++v10;
++v8;
*(_DWORD *)dword_423D78 += dword_41A138[100 * v10 + v8];
}
++v6;
}
sub_41134D("your operation can get %d points\n", dword_423D78[0]);
system("pause");
return 0;
}
else
{
LABEL_8:
sub_41134D("error\n", v5);
system("pause");
return 0;
}
}
c代码逆向还原
#include <bits/stdc++.h>
using namespace std;
int main() {
char v4; // [esp+0h] [ebp-160h]
char v5; // [esp+0h] [ebp-160h]
int v6; // [esp+D0h] [ebp-90h]
int j; // [esp+DCh] [ebp-84h]
int v8; // [esp+DCh] [ebp-84h]
int i; // [esp+E8h] [ebp-78h]
int v10; // [esp+E8h] [ebp-78h]
char Str[104]; // [esp+F4h] [ebp-6Ch] BYREF
srand(12);
int dword_41A138[2100];
for ( i = 1; i <= 20; ++i )
{
for ( j = 1; j <= i; ++j )
dword_41A138[100 * i + j] = rand() % 100000;
}
int dword_423D78 = 0;
// sub_41114F(Str);
v6 = 0;
v8 = 1;
v10 = 1;
dword_423D78 += dword_41A138[101];
while ( v6 < 19 )
{
if ( dword_41A138[100 * (v10+1) + v8] > dword_41A138[100 * (v10+1) + v8 + 1])
{
++v10;
dword_423D78 += dword_41A138[100 * v10 + v8];
Str[v6] = 'L';
}
else
{
++v10;
++v8;
dword_423D78 += dword_41A138[100 * v10 + v8];
Str[v6] = 'R';
}
++v6;
}
Str[19] = '\0';
printf("input: %s\n", Str);
printf("your operation can get %d points\n", dword_423D78);
system("pause");
return 0;
}
上述代码得到的19位序列去跑依然报错,因为有这个sub_41114F对Str做了处理
第一次用ida调试,跟ollydbg比较相似所以用起来还是比较熟练
入口函数下断点:并定位核心代码(不是虚存处理那个函数,第二个)
push eax之后是call,即传参和调用函数,eax里的值就是我们输入字符的地址
调用结束后发现字符串已变化,4C56即LV
分析该函数核心汇编代码
此时ebp存储的输入字符串的位置
.text:0041195C mov eax, [ebp-44h] ; 起始为0
.text:0041195F add eax, 1 ; 每个循环+1
.text:00411962 mov [ebp-44h], eax ; 记录次数
.text:00411965
.text:00411965 loc_411965: ; CODE XREF: .text:0041195A↑j
.text:00411965 cmp dword ptr [ebp-44h], 13h ; 比较次数和13h(19,正好是输入长度)
.text:00411969 jge short sub_411994 ; 目标操作数<源操作数,则SF不等于ZF,不会跳转,如果大于等于则跳转,即下面的不再运行了,相当于for循环i=19了
.text:0041196B mov eax, [ebp-44h] ; 再存储回eax寄存器
.text:0041196E and eax, 80000001h ; 和1与运算,奇数则为1.偶数为0
.text:00411973 jns short loc_41197A ; 与运算结果如果为非负数,则SF=0,这里必跳转
.text:00411975 dec eax ; 由上可知当i=0、2等偶数时eax会置零
.text:00411976 or eax, 0FFFFFFFEh
.text:00411979 inc eax
.text:0041197A
.text:0041197A loc_41197A: ; CODE XREF: .text:00411973↑j
.text:0041197A test eax, eax ; 检查eax是否为0,为0则ZF=1
.text:0041197C jz short loc_411992 ; ZF=1则跳转
.text:0041197E mov eax, [ebp+8] ; 取ebp+两个字节的长度值到eax
.text:00411981 add eax, [ebp-44h] ; +1
.text:00411984 movsx ecx, byte ptr [eax] ; 取输入的一个字符值到ecx
.text:00411987 xor ecx, 4 ; ecx和4异或
.text:0041198A mov edx, [ebp+8] ;
.text:0041198D add edx, [ebp-44h]
.text:00411990 mov [edx], cl ; 异或完的值拷贝回去
.text:00411992 ; 总结来说i=0、2等偶数时eax=0,会跳过异或操作
.text:00411992 loc_411992: ; CODE XREF: .text:0041197C↑j
.text:00411992 jmp short loc_41195C ; 无条件跳回循环
综上分析可得:偶数位的字符会和4异或,奇数位不变(累死了,一个个汇编分析的,因为才学了点ollydbg想尝试下,第一次汇编代码分析),如果视频的话应该可以更清楚地展示思路
修改上面c代码,加入异或操作,运行得到RVRVRHLVRVLVLVRVLVL
if (v6 % 2 == 1)
Str[v6] ^= 4;
程序验证:可以看到拿到了分数且满足最高,RVRVRHLVRVLVLVRVLVL即为flag
小结
这一节学了特别多知识,也了解到了逆向方向:
- 前面做了好几道安卓逆向,基本了解安卓架构,和逆向使用的工具、查找方法;大部分都是需要去收集加密算法代码,做一个逆向解密
- 后面接触到了ollydbg,因为卡在某一题上面,wp也看不懂,所以去跟着小甲鱼学习了下解密Ollydbg,B站上的课,讲的很基础很有意思,基本了解了各种快捷键和搜索方式,汇编代码也看了一点,重点关注了根据标志位判断条件句的跳转
- 最后实战做了道对我来说难度相对大些的题目,第一次尝试ida动态分析,相比ollydbg难用些,但也能做,分析了很多汇编代码,加深了各种指令、标志位、跳转等记忆,分析了一整个晚上,收获很大