吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1914|回复: 1
收起左侧

[Android CTF] moectf2023 reverse几道题的wp(UPX/Xor/ANDROID/ezandroid/RC4)

[复制链接]
418 发表于 2023-10-19 21:27
本帖最后由 418 于 2023-10-19 21:31 编辑

Reverse

UPX!

__int64 sub_140079760()
{
  char *v0; // rdi
  __int64 i; // rcx
  unsigned __int64 v2; // rax
  char v4[32]; // [rsp+0h] [rbp-20h] BYREF
  char v5; // [rsp+20h] [rbp+0h] BYREF
  char input_flag[76]; // [rsp+28h] [rbp+8h] BYREF
  int j; // [rsp+74h] [rbp+54h]
  unsigned __int64 v8; // [rsp+148h] [rbp+128h]

  v0 = &v5;
  for ( i = 34i64; i; --i )
  {
    *(_DWORD *)v0 = -858993460;
    v0 += 4;
  }
  sub_140075557(&unk_1401A7008);
  sub_140073581("welcome to moectf");
  sub_140073581("I put a shell on my program to prevent you from reversing it, you will never be able to reverse it hhhh~~");
  sub_140073581("Now tell me your flag:");
  memset(input_flag, 0, 0x2Aui64);
  sub_1400727F8("%s", input_flag);
  for ( j = 0; ; ++j )
  {
    v8 = j;
    v2 = sub_140073829(input_flag);
    if ( v8 >= v2 )
      break;
    input_flag[j] ^= 0x67u;
    if ( byte_140196000[j] != input_flag[j] )
    {
      sub_140073973("try again~~");
      sub_1400723F7(0i64);
    }
  }
  sub_140073973("you are so clever!");
  sub_140074BCF(v4, &unk_140162070);
  return 0i64;
}

说实话,我不知道sub_1400727F8 sub_140073829是干嘛的,于是无视了它们。。。。。。

可以看到它计算了输入的每个字符串,和0x67进行异或运算,如果遇到结果不是byte_140196000的就不行

byte_140196000

.data:0000000140196000 byte_140196000  db 0Ah, 8, 2, 4, 13h, 1, 1Ch, 'W', 0Fh, '8', 1Eh, 'W'
.data:0000000140196000                                         ; DATA XREF: sub_140079760+CA↑o
.data:0000000140196000                 db 12h, '8', ',', 9, 'W', 10h, '8', '/', 'W', 10h, '8'
.data:0000000140196000                 db 13h, 8, '8', '5', 2, 11h, 'T', 15h, 14h, 2, '8', '2'
.data:0000000140196000                 db '7', '?', 3 dup('F'), 1Ah, 17h dup(0)

用python写脚本

byte_140196000 = [0x0A, 0x08, 0x02, 0x04, 0x13, 0x01, 0x1C, ord('W'), 0x0F, ord('8'), 0x1E, ord('W'),
                  0x12, ord('8'), ord(','), 0x09, ord('W'), 0x10, ord('8'), ord('/'), ord('W'), 0x10, ord('8'),
                  0x13, 0x08, ord('8'), ord('5'), 0x02, 0x11, ord('T'), 0x15, 0x14, 0x02, ord('8'), ord('2'),
                  ord('7'), ord('?')] + [ord('F')] * 3 + [0x1A] + [0x00] * 0x17

newlist = []

print(len(byte_140196000))
# print(byte_140196000)

for i in range(64):
    newlist.append(byte_140196000[i] ^  0x67)

print(newlist)

byte_array = bytes(newlist)
string_ = byte_array.decode('utf-8')
print(string_)

输出 moectf{0h_y0u_Kn0w_H0w_to_Rev3rse_UPX!!!}ggggggggggggggggggggggg

Xor

db = [0x54, 0x56, 0x5C, 0x5A, 0x4D, 0x5F, 0x42, 0x60, 0x56, 0x4C, 0x66, 0x52, 0x57, 0x09, 0x4E, 0x66, 0x51, 0x09, 0x4E, 0x66, 0x4D, 0x09, 0x66, 0x61, 0x09, 0x6B, 0x18, 0x44]
for i in range(len(db)):
    db[i] ^= 0x39

print(db)
# 将字节值列表转换为 bytes 对象
byte_array = bytes(db)

# 将 bytes 对象转换为字符串
string = byte_array.decode()

print(string)

ANDROID

public class FlagActivity {
    public static char[] enc = {25, 7, 0, 14, 27, 3, 16, '/', 24, 2, '\t', ':', 4, 1, ':', '*', 11, 29, 6, 7, '\f', '\t', '0', 'T', 24, ':', 28, 21, 27, 28, 16};
    public static char[] key = {'t', 'h', 'e', 'm', 'o', 'e', 'k', 'e', 'y'};
    public static byte[] bytes = new byte[31];

    public static void main(String[] args) {

        for (int i = 0; i < 31; i++) {
            bytes[i] = (byte) (enc[i] ^ key[i % key.length]);
        }
        String result = new String(bytes);
        System.out.println(result);
    }
}

ezandroid

看java代码可以直到,输入长度为23的字符串

反编译libezandroid.so,导出函数只有JNI_onload,说明是动态注册的

用脚本导出函数的偏移地址

var symbols = Module.enumerateSymbolsSync("libart.so");
var addrRegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
    var symbol = symbols[i];
    if (symbol.name.indexOf("art") >= 0 &&
        symbol.name.indexOf("JNI") >= 0 &&
        symbol.name.indexOf("RegisterNatives") >= 0 &&
        symbol.name.indexOf("CheckJNI") < 0) {
        addrRegisterNatives = symbol.address;
        console.log("RegisterNatives is at ", symbol.address, symbol.name);
    }
}
console.log("addrRegisterNatives=", addrRegisterNatives);

if (addrRegisterNatives != null) {
    Interceptor.attach(addrRegisterNatives, {
        onEnter: function (args) {
            var env = args[0];
            var java_class = args[1];
            var class_name = Java.vm.tryGetEnv().getClassName(java_class);

            // native 在 jadx 里显示的类 只改这里就行
            var taget_class = "com.doctor3.ezandroid.MainActivity";

            if (class_name === taget_class) {
                console.log("\n[RegisterNatives] method_count:", args[3]);
                var methods_ptr = ptr(args[2]);
                var method_count = parseInt(args[3]);

                for (var i = 0; i < method_count; i++) {
                    // Java中函数名字的
                    var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
                    // 参数和返回值类型
                    var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
                    // C中的函数指针
                    var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));

                    var name = Memory.readCString(name_ptr); // 读取java中函数名
                    var sig = Memory.readCString(sig_ptr); // 参数和返回值类型
                    var find_module = Process.findModuleByAddress(fnPtr_ptr); // 根据C中函数指针获取模块

                    var offset = ptr(fnPtr_ptr).sub(find_module.base) // fnPtr_ptr - 模块基地址
                    // console.log("[RegisterNatives] java_class:", class_name);
                    console.log("name:", name, "sig:", sig, "module_name:", find_module.name, "offset:", offset);
                    //console.log("name:", name, "module_name:", find_module.name, "offset:", offset);
                }
            }
        }
    });
}

// frida -U -l hook.js -f com.doctor3.ezandroid --no-pause
// frida -UF -l hook.js

name: check sig: (Ljava/lang/String;)I module_name: libezandroid.so offset: 0x17b4

我的手机是64位,地址就直接是0x17b4

ida左边 Function name 选择 0x17b4

进入函数

// check函数
bool __fastcall sub_17B4(JNIEnv_ *env, __int64 a2, __int64 s)
{
  _BYTE *s_UTFChars; // [xsp+18h] [xbp-A8h]

  _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
  s_UTFChars = sub_1840(env, s, 0LL);           // return a1->functions->GetStringUTFChars(a1, a2, a3);
  sub_187C(env, s, s_UTFChars);                 // return (a1->functions->ReleaseStringUTFChars)(a1, a2, a3);
  return sub_D7C(s_UTFChars);
}

进入 sub_D7C

// check
bool __fastcall sub_D7C(_BYTE *s_UTFChars)
{
  _BYTE *v1; // x8
  bool v3; // [xsp+14h] [xbp-1Ch]
  char *v4; // [xsp+18h] [xbp-18h]
  _BOOL4 v6; // [xsp+2Ch] [xbp-4h]

  v4 = &asc_3B50[18];
  while ( 2 )
  {
    v3 = 0;
    if ( *s_UTFChars )  // 23个字符还没整完
      v3 = *v4 != 42;  // 42:* 
    if ( v3 )
    {
      v1 = s_UTFChars++;
      switch ( *v1 )
      {
        case 'a':
          --v4;  // asc_3B50[]下标减1
          continue; // 继续循环,不走break
        case 'd':
          ++v4;  // asc_3B50[]下标加1
          continue;
        case 's':
          v4 += 15;  // asc_3B50[]下标加15
          continue;
        case 'w':
          v4 -= 15;  // asc_3B50[]下标减15
          continue;
        default:
          v6 = 0;
          break;
      }
    }
    else
    {
      v6 = *v4 == 35;  // v4[25] = #
    }
    break;
  }
  return v6;
}

也就是说,输入的字符串要能使v6返回的是1,也就是 v6 = *v4 == 35 相当于最后*v4==35

看不懂代码的可以把它抠出来,简单处理一下,下断点调试,就能知道在干啥了

# include <stdio.h>
#include <stdbool.h>

int sub_D7C(unsigned char *s_UTFChars);

int main() {
    // char s[] = "ssaassssdddddwwddddwwww";
    char s[] = "ssaassssdddddwwddddwwaa";
    int v6 = sub_D7C(s);
    printf("the result-> %d\n", v6);
    return 0;
}

int sub_D7C(unsigned char *s_UTFChars) {
    // char asc_3B50[] = "******************@******#*******.******.*****...******.*****.**"
    //             "******.*****.****.....*****.****.*********......****************"
    //             "*******";

    char asc_3B50[] = "******************@**************.************...****#..*****.********.*****.****.....*****.****.*********......***********************";
    unsigned char *v1;
    bool v3;
    char *v4;
    bool v6;

    v4 = &asc_3B50[18]; // 64 @
    printf("v4 = %d\n", *v4);
    // v4 += 15;
    // printf("v4 = %d\n", *v4);
    while (2) {
        printf("循环了一次\n");
        v3 = 0;
        if (*s_UTFChars) // 23个字符还没整完
            v3 = *v4 != 42; // 42:* 
            printf("v3 = %d\n", v3);
        if (v3) { // asc_3B50[?] 不是 *
            v1 = s_UTFChars++; 
            switch (*v1) {
                case 'a':
                    printf("switch 2 a\n");
                    --v4;
                    continue; // 继续循环,不走break
                case 'd':
                    printf("switch 2 d\n");
                    ++v4;
                    continue;
                case 's':
                    printf("switch 2 s\n");
                    v4 += 15;
                    continue;
                case 'w':
                    printf("switch 2 w\n");
                    v4 -= 15;
                    continue;
                default:
                    printf("switch 2 default\n");
                    v6 = false;
                    break;
            }
        } else { // asc_3B50[?] = *
            printf("v4 meet *");
            printf("v4 = %d\n", *v4);
            v6 = *v4 == 35; // asc_3B50[25] = #
        }
        break;
    }
    return v6;
}

点 asc_3B50 看看是啥东西

.data:0000000000003B50 asc_3B50        DCB "******************@******#*******.******.*****...******.*****.**"
.data:0000000000003B50                                         ; DATA XREF: LOAD:00000000000000F8↑o
.data:0000000000003B50                                         ; sub_D7C+4↑o ...
.data:0000000000003B50                 DCB "******.*****.****.....*****.****.*********......****************"
.data:0000000000003B50                 DCB "*******",0

是一串字符串。不过这是假的,待会再说

分析代码可把问题简化为:

 有一串字符串长为135的字符串 ******************@******#*******.******.****...******.*****.********.*****.****.....*****.****.*********......**********************
 从@所在的下标[18]开始,直到[25]#结束,下标可以+-1或+-15;必须执行23次这样的运算,且不能落在是*的地方
-1称为'a'操作,+1称为 'd'操作......

我原先是想用搜索算法,不过既然已经写到这里,我意识到了其中的玄只因:135可以被15整除,那串字符串可以格式化为如下

***************
***@******#****
***.******.****
*...******.****
*.********.****
*.****.....****
*.****.********
*......********
***************

从 @ 开始走,wsad是游戏常见的控制上下左右移动,走到终点#,恰好走23步

于是输入的字符串就是 ssaassssdddddwwddddwwww

但是提示flag不对

原来在 JNI_Onload 那里, asc_3B50 被更改了

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
  jint v4; // [xsp+44h] [xbp-CCh]
  __int64 v5; // [xsp+48h] [xbp-C8h]
  __int64 env; // [xsp+78h] [xbp-98h] BYREF
  char v7[136]; // [xsp+80h] [xbp-90h] BYREF
  __int64 v8; // [xsp+108h] [xbp-8h]

  v8 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  strcpy(
    v7,
    "******************@**************.************...****#..*****.********.*****.****.....*****.****.*********......****"
    "*******************");
  v5 = __strlen_chk(v7, 0x88u);
  __memcpy_chk(asc_3B50, v7, v5, 136LL);
  env = 0LL;
  if ( sub_1700(vm, &env, 65540LL) )            // return (a1->functions->FindClass)(a1, a2, a3);
  {
    v4 = -1;
  }
  else
  {
    clazz = sub_173C(env, "com/doctor3/ezandroid/MainActivity");// return a1->functions->FindClass(a1, a2);
    if ( clazz )
    {
      if ( (sub_1770(env, clazz, off_2938, 1u) & 0x80000000) != 0 )// RegisterNatives
        v4 = -1;
      else
        v4 = 65540;
    }
    else
    {
      v4 = -1;
    }
  }
  _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
  return v4;
}

真正的asc_3B50如下

***************
***@***********
***.***********
*...****#..****
*.********.****
*.****.....****
*.****.********
*......********
***************

输入字符串应该是 ssaassssdddddwwddddwwaa

结合java代码, flag 是 moectf{ssaassssdddddwwddddwwaa}

RC4

全局搜索字符串 flag 定位到sub_140079A70函数

image.png

__int64 sub_140079A70()
{
  char *v0; // rdi
  __int64 i; // rcx
  char v3[32]; // [rsp+0h] [rbp-30h] BYREF
  char v4; // [rsp+30h] [rbp+0h] BYREF
  char v5[256]; // [rsp+40h] [rbp+10h] BYREF
  char v6[256]; // [rsp+160h] [rbp+130h] BYREF
  char v7[44]; // [rsp+278h] [rbp+248h] BYREF
  int v8; // [rsp+2A4h] [rbp+274h]
  int j; // [rsp+2C4h] [rbp+294h]

  v0 = &v4;
  for ( i = 172i64; i; --i )
  {
    *v0 = -858993460;
    v0 += 4;
  }
  sub_14007555C(&unk_1401A7007);
  memset(v5, 0, sizeof(v5));
  memset(v6, 0, sizeof(v6));
  strcpy(v7, "moectf2023");
  v8 = 0;
  sub_140073581("welcome to moectf!!!");
  sub_140073581("This is a very common algorithm ");
  sub_140073581("show your flag:");
  sub_1400727F8("%s", byte_140197260);
  if ( sub_140073829(byte_140197260) == 37 )
  {
    sub_140075052(v5, v6, byte_140197260, 38, v7, 10);  // 看提示,应该在进行rc4加密
    for ( j = 0; j < 0x26; ++j )
    {
      if ( byte_140196000[j] == byte_140197260[j] )
        ++v8;
    }
  }
  if ( v8 == 37 )
    sub_140073973("right!flag is your input!");
  else
    sub_140073973("try again~");
  sub_140074BCF(v3, &unk_140162100);
  return 0i64;
}

一共37个字符串,byte_140197260 应该是输入的flag

在经过 sub_140075052(v5, v6, byte_140197260, 38, v7, 10); 的处理后

byte_140196000[j] == byte_140197260[j]

看下加密算法

__int64 __fastcall sub_1400795E0(__int64 a1, __int64 a2, __int64 input_flag, int _38, __int64 moectf2023, unsigned int _10)
{
  __int64 result; // rax 10是密钥"moectf2023"的长度
  int i; // [rsp+24h] [rbp+4h] a1 a2 一个是s盒 一个是T盒
  int j; // [rsp+24h] [rbp+4h]
  int v9; // [rsp+24h] [rbp+4h]
  int v10; // [rsp+44h] [rbp+24h]
  int v11; // [rsp+44h] [rbp+24h]
  char v12; // [rsp+64h] [rbp+44h]
  char v13; // [rsp+64h] [rbp+44h]
  int v14; // [rsp+A4h] [rbp+84h]

  result = sub_14007555C(&unk_1401A7007);
  v10 = 0;
  v14 = 0;
  for ( i = 0; i < 256; ++i )
  {
    *(a1 + i) = i;
    *(a2 + i) = *(moectf2023 + i % _10);        // T盒用来保存子密钥(密钥流)
    result = (i + 1);
  }
  for ( j = 0; j < 256; ++j )
  {
    v10 = (*(a2 + j) + *(a1 + j) + v10) % 256;  // 打乱s盒的值 a1
    v12 = *(a1 + v10);
    *(a1 + v10) = *(a1 + j);
    *(a1 + j) = v12;
    result = (j + 1);
  }
  v9 = 0;
  v11 = 0;
  while ( _38 )
  {
    v9 = (v9 + 1) % 256;
    v11 = (*(a1 + v9) + v11) % 256;
    v13 = *(a1 + v11);                          // 交换s[v9] 和s[v11]
    *(a1 + v11) = *(a1 + v9);
    *(a1 + v9) = v13;
    *(input_flag + v14++) ^= *(a1 + (*(a1 + v11) + *(a1 + v9)) % 256);// 将明文与密钥流(打乱的s盒) 进行异或加解密
    result = --_38;
  }
  return result;
}

结合提示,上网搜索rc4算法,看样子就是它了

解密

def rc4_decrypt(ciphertext, key):
    S = list(range(256))
    j = 0
    out = []

    # Key-scheduling algorithm
    for i in range(256):
        j = (j + S[i] + key[i % len(key)]) % 256
        S[i], S[j] = S[j], S[i]

    # Pseudo-random generation algorithm
    i = j = 0
    for byte in ciphertext:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        out.append(byte ^ S[(S[i] + S[j]) % 256])

    return bytes(out)

ciphertext = [0x1B, 0x9B, 0xFB, 0x19, 0x06, 0x6A, 0xB5, 0x3B, 0x7C, 0xBA, 0x03,
              0xF3, 0x91, 0xB8, 0xB6, 0x3D, 0x8A, 0xC1, 0x48, 0x2E, 0x50,
              0x11, 0xE7, 0xC7, 0x4F, 0xB1, 0x27, 0xCF, 0xF3, 0xAE, 0x03,
              0x09, 0xB2, 0x08, 0xFB, 0xDC, 0x22]

key = "moectf2023"

plaintext = rc4_decrypt(ciphertext, key.encode())
print(plaintext)

得到 moectf{y0u_r3a11y_understand_rc4!!!!}

Jail

level 0

摘自知乎 https://zhuanlan.zhihu.com/p/60257325

>>> eval("__import__('os').system('whoami')")
desktop-fa4b888\pythoncat
>>> eval("__import__('subprocess').getoutput('ls ~')")
#结果略,内容是当前路径的文件信息

用了第二个命令,输出

Answer result: flag
server.py

打开flag文件再读取

compile("with open('./flag', 'r') as file:\n\tflag_content=file.read(); print(flag_content)", '<stdin>','exec')

得到flag :D

免费评分

参与人数 2威望 +2 吾爱币 +101 热心值 +2 收起 理由
qtfreet00 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
抱薪风雪雾 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

binarystudy123 发表于 2023-10-20 15:22
感谢师傅,wp很详细
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-29 12:36

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表