418 发表于 2023-10-19 21:27

moectf2023 reverse几道题的wp(UPX/Xor/ANDROID/ezandroid/RC4)

本帖最后由 418 于 2023-10-19 21:31 编辑


# Reverse

## UPX!

```c
__int64 sub_140079760()
{
char *v0; // rdi
__int64 i; // rcx
unsigned __int64 v2; // rax
char v4; // BYREF
char v5; // BYREF
char input_flag; // BYREF
int j; //
unsigned __int64 v8; //

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 ^= 0x67u;
    if ( byte_140196000 != input_flag )
    {
      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_140196000db 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写脚本

```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('?')] + * 3 + + * 0x17


newlist = []

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

for i in range(64):
    newlist.append(byte_140196000 ^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

```python
db =
for i in range(len(db)):
    db ^= 0x39

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

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

print(string)
```

## ANDROID

```java
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;

    public static void main(String[] args) {

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

## ezandroid

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

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

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

```javascript
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrRegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
    var symbol = symbols;
    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;
            var java_class = args;
            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 method_count:", args);
                var methods_ptr = ptr(args);
                var method_count = parseInt(args);

                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(" 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

进入函数

```c
// check函数
bool __fastcall sub_17B4(JNIEnv_ *env, __int64 a2, __int64 s)
{
_BYTE *s_UTFChars; //

_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

```c
// check
bool __fastcall sub_D7C(_BYTE *s_UTFChars)
{
_BYTE *v1; // x8
bool v3; //
char *v4; //
_BOOL4 v6; //

v4 = &asc_3B50;
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 = #
    }
    break;
}
return v6;
}
```

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

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

```c
# 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; // 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 = #
      }
      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的字符串 ******************@******#*******.******.****...******.*****.********.*****.****.....*****.****.*********......**********************
从@所在的下标开始,直到#结束,下标可以+-1或+-15;必须执行23次这样的运算,且不能落在是*的地方
-1称为'a'操作,+1称为 'd'操作......
```

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


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

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

于是输入的字符串就是 `ssaassssdddddwwddddwwww`

但是提示flag不对

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

```c
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
jint v4; //
__int64 v5; //
__int64 env; // BYREF
char v7; // BYREF
__int64 v8; //

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`函数




```c
__int64 sub_140079A70()
{
char *v0; // rdi
__int64 i; // rcx
char v3; // BYREF
char v4; // BYREF
char v5; // BYREF
char v6; // BYREF
char v7; // BYREF
int v8; //
int j; //

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 == byte_140197260 )
      ++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 == byte_140197260

看下加密算法

```c
__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; // a1 a2 一个是s盒 一个是T盒
int j; //
int v9; //
int v10; //
int v11; //
char v12; //
char v13; //
int v14; //

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 和s
    *(a1 + v11) = *(a1 + v9);
    *(a1 + v9) = v13;
    *(input_flag + v14++) ^= *(a1 + (*(a1 + v11) + *(a1 + v9)) % 256);// 将明文与密钥流(打乱的s盒) 进行异或加解密
    result = --_38;
}
return result;
}
```

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

解密

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

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

    # Pseudo-random generation algorithm
    i = j = 0
    for byte in ciphertext:
      i = (i + 1) % 256
      j = (j + S) % 256
      S, S = S, S
      out.append(byte ^ S[(S + S) % 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文件再读取

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

得到flag :D

binarystudy123 发表于 2023-10-20 15:22

感谢师傅,wp很详细
页: [1]
查看完整版本: moectf2023 reverse几道题的wp(UPX/Xor/ANDROID/ezandroid/RC4)