前言:
坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源:https://www.52pojie.cn/thread-1582287-1-1.html
立帖为证!--------记录学习的点点滴滴
0x1 题目来源
1.活动已结束,题目打包放到爱盘供大家下载学习(web在线题目一周后下线):
https://down.52pojie.cn/Challenge/Happy_New_Year_2023_Challenge.rar
2.我很菜,春节一个题都没干出来,遇到困难就退缩了,现在活动结束了,抄一抄别的大佬wp分享出来,只能看懂前三个,后面wp我看不懂了!
0x2 题二
1.先跑起来看看
2.从目前收集到的信息就是校验字符串是否正确,会判断字符串长度是否相等,拖进OD右键搜索中文字符串可以看到很多提示。
3.这是刚刚出现的错误提示,这里调试一下就知道是判断字符串长度,0x1D就是29位:
0040132E . 837E F4 1D cmp dword ptr ds:[esi-0xC],0x1D
00401332 . 0F84 A5000000 je 【2023春.004013DD
00401338 . C70424 D03344>mov dword ptr ss:[esp],【2023春.004433D0
0040133F . BF 34004400 mov edi,【2023春.00440034 ; Length Error, please try again
4.输入12345678912345678912345678900测试,继续往下调试,发现会提示错误,可以看到有一个跳转过来,点上去发现是一个cmp比较下来的。
00401501 > \C70424 D03344>mov dword ptr ss:[esp],【2023春.004433D0
00401508 . BA 61004400 mov edx,【2023春.00440061 ; Wrong,please try again.
00401417 . 381C32 cmp byte ptr ds:[edx+esi],bl
0040141A 0F85 E1000000 jnz 【2023春.00401501
5.重来,打上断点,看看这里比较的是什么,右键-数据窗口中跟随-》内存地址,发现比较的好像就是我输入的字符串的第一个字符1。
6.将下面的jnz nop掉,在这里反复循环几次看看,验证了我的猜想,那就笨办法一步步读出每一个字符,在纸上记录下来,最终得到flag:flag{52PoJie2023HappyNewYear},输入验证一下,成功。
7.用od分析到长度判断后可以用ida动态调试,如果直接看会有点晕,但是刚刚od里面已经分析长度为29,所以这里*(v14[0] - 12)就是我输入字符串的长度。
if ( *(v14[0] - 12) == 29 )
{
v13 = 0;
while ( 1 )
{
if ( *(v14[0] - 12 + 8) >= 0 )
_ZNSs12_M_leak_hardEv(v14);
if ( *(v14[0] + v13) != (dword_43F000[v13] >> 2) )
break;
if ( ++v13 >= *(v14[0] - 12) )
{
v7 = _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc(&dword_4433D0, "Success");
_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_(v7);
system("Pause");
8.while循环里面第一个if可以不用管自己过去,根据提示第二个if必须成立,否则跳出去就失败了,第三个if就是判断循环次数, ++v13移动下标,判断循环次数等不等于字符串长度。再看第二个if,v14逐个和dword_43F000右移两位后做比较,鼠标移上去能看到数组变量的值,点进去,右键修改类型为array。
注: 2 dup(1C0),指的是两个1C0,写脚本复制别漏了。
9.可根据上面的分析编写脚本:
#include <stdio.h>
int main()
{
int flag[] = {0x198, 0x1B0, 0x184, 0x19C, 0x1EC, 0x0D4, 0x0C8, 0x140, 0x1BC,
0x128, 0x1A4, 0x194, 0x0C8, 0x0C0, 0x0C8, 0x0CC, 0x120, 0x184,
0x1C0, 0x1C0, 0x1E4, 0x138, 0x194, 0x1DC, 0x164, 0x194, 0x184,
0x1C8, 0x1F4};
// 输出flag
for (int i = 0; i < 29; i++)
{
printf("%c", flag[i]>>2);;
}
return 1;
}
0x3 题三
1.反编译看代码,可以看到onclick函数,必须点击999次才会执行decrypt函数得到flag。
public static final void m19onCreate$lambda0(MainActivity this$0, TextView key, View view) {
Intrinsics.checkNotNullParameter(this$0, "this$0");
Intrinsics.checkNotNullParameter(key, "$key");
MainActivity mainActivity = this$0;
this$0.jntm(mainActivity);
key.setText(String.valueOf(this$0.num));
if (this$0.check() == 999) {
Toast.makeText(mainActivity, "快去论坛领CB吧!", 1).show();
key.setText(this$0.decrypt("hnci}|jwfclkczkppkcpmwckng\u007f", 2));
}
}
public final String decrypt(String encryptTxt, int i) {
Intrinsics.checkNotNullParameter(encryptTxt, "encryptTxt");
char[] charArray = encryptTxt.toCharArray();
Intrinsics.checkNotNullExpressionValue(charArray, "this as java.lang.String).toCharArray()");
StringBuilder sb = new StringBuilder();
for (char c : charArray) {
sb.append((char) (c - i));
}
String sb2 = sb.toString();
Intrinsics.checkNotNullExpressionValue(sb2, "with(StringBuilder()) {\n… toString()\n }");
return sb2;
}
2.本想改代码,把999改成3,但是发现模拟器不能运行apk,就会很难受了,好在这题逻辑不难,主要是执行decrypt函数就可以了,去掉无用的,保留关键代码。
decrypt("hnci}|jwfclkczkppkcpmwckng\u007f", 2);
public final String decrypt(String encryptTxt, int i) {
Intrinsics.checkNotNullParameter(encryptTxt, "encryptTxt");
char[] charArray = encryptTxt.toCharArray();
Intrinsics.checkNotNullExpressionValue(charArray, "this as java.lang.String).toCharArray()");
StringBuilder sb = new StringBuilder();
for (char c : charArray) {
sb.append((char) (c - i));
}
String sb2 = sb.toString();
Intrinsics.checkNotNullExpressionValue(sb2, "with(StringBuilder()) {\n… toString()\n }");
return sb2;
}
3.把这段代码搬运到eclipse里面,跑一遍得到flag{zhudajiaxinniankuaile}。
package ctf;
public class test01 {
public static void main(String[] args) {
//把参数值直接写进来
String encryptTxt = "hnci}|jwfclkczkppkcpmwckng\u007f";
int i = 2;
//照搬代码
char[] charArray = encryptTxt.toCharArray();
StringBuilder sb = new StringBuilder();
for (char c : charArray) {
sb.append((char) (c - i));
}
String sb2 = sb.toString();
System.out.println(sb2);
}
}
0x4 题四
1.还是先反编译看一下代码:
public static final void m19onCreate$lambda0(MainActivity this$0, View view) {
Intrinsics.checkNotNullParameter(this$0, "this$0");
A a = A.INSTANCE;
EditText editText = this$0.edit_uid;
EditText editText2 = null;
if (editText == null) {
Intrinsics.throwUninitializedPropertyAccessException("edit_uid");
editText = null;
}
String obj = StringsKt.trim((CharSequence) editText.getText().toString()).toString();
EditText editText3 = this$0.edit_flag;
if (editText3 == null) {
Intrinsics.throwUninitializedPropertyAccessException("edit_flag");
} else {
editText2 = editText3;
}
if (a.B(obj, StringsKt.trim((CharSequence) editText2.getText().toString()).toString())) {
Toast.makeText(this$0, "恭喜你,flag正确!", 1).show();
} else {
Toast.makeText(this$0, "flag错误哦,再想想!", 1).show();
}
}
2.可以看到由我输入uid和flag然后进行运算,关键判断就是 if (a.B(obj, StringsKt.trim((CharSequence) editText2.getText().toString()).toString())) 这一行a.B我们点进去看看是什么方法,第一个参数是我输入的uid,第二个参数是我输入的flag。
public final boolean B(String str, String str2) {
Intrinsics.checkNotNullParameter(str, "str");
Intrinsics.checkNotNullParameter(str2, "str2");
if ((str.length() == 0 && str2.length() == 0) || !StringsKt.startsWith$default(str2, "flag{", false, 2, (Object) null) || !StringsKt.endsWith$default(str2, "}", false, 2, (Object) null)) {
return false;
}
String substring = str2.substring(5, str2.length() - 1);
Intrinsics.checkNotNullExpressionValue(substring, "this as java.lang.String…ing(startIndex, endIndex)");
C c = C.INSTANCE;
MD5Utils mD5Utils = MD5Utils.INSTANCE;
Base64Utils base64Utils = Base64Utils.INSTANCE;
String encode = B.encode(str + "Wuaipojie2023");
Intrinsics.checkNotNullExpressionValue(encode, "encode(str3)");
byte[] bytes = encode.getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(bytes, "this as java.lang.String).getBytes(charset)");
return Intrinsics.areEqual(substring, c.cipher(mD5Utils.MD5(base64Utils.encodeToString(bytes)), 5));
}
3.分析一下上面的代码:
1)先看第一个if判断长度是否为0,然后startsWith函数百度一下作用:如果字符串以指定的前缀开始,则返回 true;否则返回 false,这里就是判断开头是否为flag{,后面一个函数是判断末尾的,作用差不多。
2)str2.substring(5, str2.length() - 1);取flag{}里面的内容
3)C c = C.INSTANCE这里创建了一个类对象,具体干什么先不管,后面再分析。
4)后面连着几行,很好理解,将uid拼接"Wuaipojie2023"调用B类的encode方法进行处理字符串,接着将处理后的字符串变成bytes字节数组。
public class B {
public static String encode(String str) {
int length = str.length();
char[] cArr = new char[length];
int i = length - 1;
while (i >= 0) {
int i2 = i - 1;
cArr[i] = (char) (str.charAt(i) ^ '5');
if (i2 < 0) {
break;
}
i = i2 - 1;
cArr[i2] = (char) (str.charAt(i2) ^ '2');
}
return new String(cArr);
}
}
5)到最后一句比较了,可以看到将得到的bytes先进行base64加密,在进行md5加密,通过c类对象调用cipher方法,最后和flag{}里面的内容作比较。
4.B类的encode方法没看懂,先不管,等会直接调用,去看c类的cipher方法,这里面还用上了方法重载套娃:
private final char cipher(char c, int i) {
char c2 = Character.isUpperCase(c) ? 'A' : 'a';
return (char) (((char) (((((char) (c - c2)) + (i % 26)) + 26) % 26)) + c2);
}
public final String cipher(String str, int i) {
Intrinsics.checkNotNullParameter(str, "str");
StringBuilder sb = new StringBuilder();
int length = str.length();
for (int i2 = 0; i2 < length; i2++) {
if (Intrinsics.compare((int) str.charAt(i2), 65) >= 0 && Intrinsics.compare((int) str.charAt(i2), 90) <= 0) {
sb.append(cipher(str.charAt(i2), i));
} else if (Intrinsics.compare((int) str.charAt(i2), 97) < 0 || Intrinsics.compare((int) str.charAt(i2), 122) > 0) {
sb.append(str.charAt(i2));
} else {
sb.append(cipher(str.charAt(i2), i));
}
}
String sb2 = sb.toString();
Intrinsics.checkNotNullExpressionValue(sb2, "sb.toString()");
return sb2;
}
5.反正代码流程是弄清楚了,直接改造一下试试:
package ctf;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class test01 {
public static void main(String[] args) throws NoSuchAlgorithmException, UnsupportedEncodingException {
// uid+拼接后的字符串直接写上
String uid = "750905Wuaipojie2023";
// 调用encode()
String en_str = encode(uid);
//base64和md5
en_str = Base64.getEncoder().encodeToString(en_str.getBytes());
byte[] en_flag = MessageDigest.getInstance("MD5").digest(en_str.getBytes());
//这里调用了别人的转16进制代码
String md5_str = bytesToHex(en_flag);
String flag = cipher(md5_str,5);
System.out.println("flag{"+flag+"}");
}
// 将encode函数照搬
public static String encode(String uid) {
int length = uid.length();
char[] cArr = new char[length];
int i = length - 1;
while (i >= 0) {
int i2 = i - 1;
cArr[i] = (char) (uid.charAt(i) ^ '5');
if (i2 < 0) {
break;
}
i = i2 - 1;
cArr[i2] = (char) (uid.charAt(i2) ^ '2');
}
return new String(cArr);
}
public static char cipher(char c, int i) {
char c2 = Character.isUpperCase(c) ? 'A' : 'a';
return (char) (((char) (((((char) (c - c2)) + (i % 26)) + 26) % 26)) + c2);
}
public static String cipher(String str, int i) {
// 这里Intrinsics.compare这个函数的意思百度一下就懂了,就是判断是不是在65和90之间的,后面一个也一样。
StringBuilder sb = new StringBuilder();
int length = str.length();
for (int i2 = 0; i2 < length; i2++) {
if ((str.charAt(i2) - 65) >= 0 && (str.charAt(i2) - 90) <= 0) {
sb.append(cipher(str.charAt(i2), i));
} else if ((str.charAt(i2) - 97) < 0 || (str.charAt(i2) - 122) > 0) {
sb.append(str.charAt(i2));
} else {
sb.append(cipher(str.charAt(i2), i));
}
}
String sb2 = sb.toString();
return sb2;
}
public static String bytesToHex(byte bytes[]) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
int number = bytes[i] & 0xff;
String hex = Integer.toHexString(number);
if (number >= 0 && number < 16) {
sb.append("0" + hex);
} else {
sb.append(hex);
}
}
return sb.toString();
}
}
6.这里写脚本,我是照着前面分析的来,先取我的uid直接拼上Wuaipojie2023,然后几个调用的方法直接把代码copy过来,报错的哪些检查语句直接删掉,Intrinsics.compare这个函数不能删,是比较用的,百度一下就能改造,所有方法改为静态的即可,但是最后一直是乱码,和别人的对比发现主要在md5后的16进制转换没有做。