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
感谢师傅,wp很详细
页:
[1]