利用OpenCV的色容差检测功能,做到了马来西亚国旗——辉煌条纹(Jalur Gemilang)的检测
当前功能仅有检测到Jalur Gemilang时,冻结那一帧,之后可能会再添加一些2333的功能。
由于该程序目标为马来西亚人,所以提示语也采用了马来文🤣

实验效果

OPENCV_PJG

# pjg.py V3
import cv2
import numpy as np

lower_red1 = np.array([0, 100, 100])
upper_red1 = np.array([10, 255, 255])

lower_red2 = np.array([160, 100, 100])
upper_red2 = np.array([180, 255, 255])

lower_white = np.array([0, 0, 200])
upper_white = np.array([180, 30, 255])

lower_yellow = np.array([20, 100, 100])
upper_yellow = np.array([30, 255, 255])

lower_blue = np.array([100, 100, 100])
upper_blue = np.array([130, 255, 255])


def dF(frame):
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    mask_red = cv2.inRange(hsv, lower_red1, upper_red1)
    mask_red2 = cv2.inRange(hsv, lower_red2, upper_red2)
    mask_white = cv2.inRange(hsv, lower_white, upper_white)
    mask_blue = cv2.inRange(hsv, lower_blue, upper_blue)
    mask_yellow = cv2.inRange(hsv, lower_yellow, upper_yellow)
    mask = cv2.bitwise_or(mask_red, mask_red2, mask_white)
    mask = cv2.bitwise_or(mask, mask_blue)
    mask = cv2.bitwise_or(mask, mask_yellow)
    if cv2.countNonZero(mask_red) > 100 and cv2.countNonZero(mask_white) > 100 and cv2.countNonZero(mask_blue) > 100 and cv2.countNonZero(mask_yellow) > 100:
        return True


cap = cv2.VideoCapture(0)
print("Pengesan Jalur Gemilang\nVersi Aplikasi: V3\nMasukkan 'q' untuk keluar\n")
while True:
    ret, frame = cap.read()
    if dF(frame):
        print('Unsur Jalur Gemilang dikesankan!!!')
        cv2.waitKey(0)

    cv2.imshow('Pengesan Jalur Gemilang', frame)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

    if cv2.getWindowProperty('Pengesan Jalur Gemilang', cv2.WND_PROP_VISIBLE) < 1:
        cap.release()
        cv2.destroyAllWindows()
        break

很久以前接触到日系手机内核里面各式各样的LSM的时候,就对LSM背后到底是什么,产生了一些奇怪的兴趣。
最近有些得空,读了一些关于LSM,钩子,内核相关的东西,再搭上以前对FJSEC这些OEM LSM的逆向的经验。
尝试胡乱弄个LSM出来看看?也许有用也也许没用,又成为了一个慢慢挖坑再填坑的作品。
为啥叫NGLSM?NG是我很早用的一个网名,全称是Nango,不懂当初为什么会想到这个名字,但是既然想到了,那么就这样叫吧。

// 2023.06.08-00.00.01/FIRST RELEASE
// 2023.06.11-00.01.01/ADD PROTECT OF SETSOCKOPT/GETSOCKOPT
// 2025.03.16-00.02.01/ADD PROTECT OF /DATA/LOCAL/TMP FOLDER
#include <linux/lsm_hooks.h>
#include <linux/binfmts.h>
#include <linux/path.h>
#include <linux/cred.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/mount.h>
#include <linux/bprm.h>
#include <linux/socket.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/kernel.h>
#include <linux/string.h>

#define STATUS_FILE          "/data/security/lsm/status"
#define STATUS_PERMISSIVE    "PERMISSIVE"
#define STATUS_ENFORCING     "ENFORCING"
#define STATUS_MAX           100
#define TASK_FLAG_RESTRICTED (1 << 30)

static inline bool is_restricted_task(struct task_struct *task)
{
    return (task->flags & TASK_FLAG_RESTRICTED) != 0;
}


static int nglsm_status(void)
{
    struct file *f;
    char buf[STATUS_MAX];
    loff_t pos = 0;
    int status = -1;
    ssize_t ret;

    f = filp_open(STATUS_FILE, O_RDONLY, 0);
    if (IS_ERR(f)) {
        pr_err("[NGLSM] STATUS FILE CAN'T ACCESSABLE: %s\n", STATUS_FILE);
        return status;
    }

    ret = kernel_read(f, buf, STATUS_MAX - 1, &pos);
    if (ret < 0) {
        pr_err("[NGLSM] STATUS FILE CAN'T ACCESSABLE: %s\n", STATUS_FILE);
        filp_close(f, NULL);
        return status;
    }
    buf[ret] = '\0';

    if (strncmp(buf, STATUS_PERMISSIVE, strlen(STATUS_PERMISSIVE)) == 0)
        status = 0;
    else if (strncmp(buf, STATUS_ENFORCING, strlen(STATUS_ENFORCING)) == 0)
        status = 1;

    filp_close(f, NULL);
    return status;
}

static int nglsm_bprm_check(struct linux_binprm *bprm)
{
    const char *path = bprm->filename;
    size_t len = strlen("/data/local/tmp/");
    if (path && (strncmp(path, "/data/local/tmp/", len) == 0)) {
        pr_info("[NGLSM] TEMPORARY EXECUTABLE FILE RESTRICTED: %s\n", path);
        current->flags |= TASK_FLAG_RESTRICTED;
    }
    return 0;
}

static int nglsm_check(void)
{
    int global_status = nglsm_status();
    if (global_status == 0 && !is_restricted_task(current))
        return 0;
    return -EPERM;
}

static int nglsm_sb_mount(const char *dev_name, struct path *path,
                            const char *type, unsigned long flags, void *data)
{
    if (nglsm_check() == 0)
        return 0;
    pr_err("[NGLSM] MOUNT RESTRICTED: global_status=%d, restricted=%d\n",
           nglsm_status(), is_restricted_task(current));
    return -EPERM;
}

static int nglsm_sb_umount(struct vfsmount *mnt, int flags)
{
    if (nglsm_check() == 0)
        return 0;
    pr_err("[NGLSM] UMOUNT RESTRICTED: global_status=%d, restricted=%d\n",
           nglsm_status(), is_restricted_task(current));
    return -EPERM;
}

static int nglsm_sb_pivotroot(struct path *old_path, struct path *new_path)
{
    if (nglsm_check() == 0)
        return 0;
    pr_err("[NGLSM] PIVOTROOT RESTRICTED: global_status=%d, restricted=%d\n",
           nglsm_status(), is_restricted_task(current));
    return -EPERM;
}

static int nglsm_socket_getsockopt(struct socket *sock, int level, int optname,
                                     char __user *optval, int __user *optlen)
{
    if (nglsm_check() == 0)
        return 0;
    pr_err("[NGLSM] GETSOCKOPT RESTRICTED: global_status=%d, restricted=%d\n",
           nglsm_status(), is_restricted_task(current));
    return -EPERM;
}

static int nglsm_socket_setsockopt(struct socket *sock, int level, int optname,
                                     char __user *optval, unsigned int optlen)
{
    if (nglsm_check() == 0)
        return 0;
    pr_err("[NGLSM] SETSOCKOPT RESTRICTED: global_status=%d, restricted=%d\n",
           nglsm_status(), is_restricted_task(current));
    return -EPERM;
}

static int lsm_status_proc_show(struct seq_file *m, void *v)
{
    int status = nglsm_status();
    switch (status) {
    case 0:
        seq_printf(m, "PERMISSIVE\n");
        break;
    case 1:
        seq_printf(m, "ENFORCING\n");
        break;
    default:
        seq_printf(m, "UNKNOWN\n");
    }
    return 0;
}

static int lsm_status_proc_open(struct inode *inode, struct file *file)
{
    return single_open(file, lsm_status_proc_show, NULL);
}

static const struct file_operations lsm_status_proc_fops = {
    .owner   = THIS_MODULE,
    .open    = lsm_status_proc_open,
    .read    = seq_read,
    .llseek  = seq_lseek,
    .release = single_release,
};

static struct security_hook_list nglsm_hooks[] __lsm_ro_after_init = {
    LSM_HOOK_INIT(bprm_check, nglsm_bprm_check),
    LSM_HOOK_INIT(sb_mount, nglsm_sb_mount),
    LSM_HOOK_INIT(sb_umount, nglsm_sb_umount),
    LSM_HOOK_INIT(sb_pivotroot, nglsm_sb_pivotroot),
    LSM_HOOK_INIT(socket_getsockopt, nglsm_socket_getsockopt),
    LSM_HOOK_INIT(socket_setsockopt, nglsm_socket_setsockopt),
};

static int __init nglsm_init(void)
{
    if (!proc_create("lsm_status", 0444, NULL, &lsm_status_proc_fops)) {
        pr_err("[NGLSM] PROC ACCESS FAILED\n");
        return -ENOMEM;
    }

    security_add_hooks(nglsm_hooks, ARRAY_SIZE(nglsm_hooks), "nglsm");
    pr_info("[NGLSM] NGLSM HOOKED\n");
    return 0;
}

security_initcall(nglsm_init);

AWA.SH

此脚本可在Alpine Linux下快速部署网页应用所需的服务器及服务

功能

  • 可选择使用的语言,包括PHP, Python, Node.js
  • 可选择网页服务器,包括Nginx, Apache, Lighttpd
  • 可选择数据库引擎,包括MariaDB, SQLite
  • 可从软件源内自动寻找最新的PHP版本并执行安装

使用

wget --no-check-certificate https://obj.yuu.ink/script/awa.sh && chmod +x awa.sh && ./awa.sh

更新

R2(2023.07.14)
修复一个严重错误
R1(2023.06.02)
初版

最近把很早以前购买的三星平板拿出来继续使用,其SoC是三星半导体自制的Exynos 9611,由于我买的是WIFI+LTE版本,其存在基带。很久以前用搭载Exynos4210的三星S2的时候,就对三星的奇怪的香农基带感到好奇。
历经快十年的对于逆向的爱好,最近再阅读了一些大牛写的代码和他们提供的Loader,成功的对目前最新的Exynos Shannon基带固件做到了解包,解压后可直接使用ShannonRE项目之中的Loader和静态分析工具进行分析。
当然如果你只是对三星的调试模式比较好奇的话,在我的解压工具中,其将自动从main区块中搜索AT指令,可通过终端模拟器连接到Samsung DM端口发送AT指令进行调试。
Pixel上的Tensor SoC中的基带,需要从运行时的设备直接Dump才可取得本工具可解析的镜像!

# siu.py v2
import struct
import re
import argparse
from contextlib import contextmanager
from dataclasses import dataclass
from typing import BinaryIO, Type, Pattern, Optional

@dataclass
class TOCHeader:
    name: str
    start: int
    size: int
    secdata: int
    queue: int

    def print_info(self):
        print(f"Block Name: {self.name}")
        print(f"Start Offset: 0x{self.start:08x}")
        print(f"Size: 0x{self.size:08x}")
        print(f"Sec Data: 0x{self.secdata:08x}")
        print(f"Queue: {self.queue}")

class ImageHandler:
    HEADER_SIZE = 32
    TOC_STRUCT_FORMAT = "<12s4xix4xiiii"
    
    def __init__(self, file_stream: BinaryIO):
        self.file = file_stream
    
    @contextmanager
    def _seek_context(self, position: Optional[int] = None):
        original = self.file.tell()
        try:
            if position is not None:
                self.file.seek(position)
            yield
        finally:
            self.file.seek(original)
            
    def read_toc_header(self, expected_name: str, position: Optional[int] = None) -> TOCHeader:
        with self._seek_context(position):
            try:
                buffer = self.file.read(self.HEADER_SIZE)
                if len(buffer) < self.HEADER_SIZE:
                    raise ValueError("Incomplete TOC header data")
                    
                fields = struct.unpack(self.TOC_STRUCT_FORMAT, buffer)
                name = fields[0].rstrip(b"\x00").decode("utf-8")
                
                if name != expected_name:
                    raise ValueError(f"Invalid TOC header. Expected {expected_name}, got {name}")
                    
                return TOCHeader(
                    name=name,
                    start=fields[1],
                    size=fields[2],
                    secdata=fields[3],
                    queue=fields[4]
                )
                
            except (struct.error, UnicodeDecodeError) as e:
                raise RuntimeError(f"Failed to parse TOC header: {e}") from e

    def extract_image(self, header: TOCHeader, image_type: str) -> bytes:
        with self._seek_context(header.start):
            return self.file.read(header.size)

    @staticmethod
    def find_strings(data: bytes, pattern: Pattern[bytes], filename: str) -> int:
        matches = re.findall(pattern, data)
        with open(filename, "wb") as f:
            for match in matches:
                f.write(match + b"\n")
        return len(matches)

class ShannonUnpacker:
    IMAGE_TYPES = ["BOOT", "MAIN", "VSS", "NV"]
    
    def __init__(self, filename: str):
        self.filename = filename
        self.handler: Optional[ImageHandler] = None
        self.headers = {}
        
    def process_file(self):
        with open(self.filename, "rb") as f:
            self.handler = ImageHandler(f)
            self._process_toc_headers()
            self._extract_images()
            
    def _process_toc_headers(self):
        try:
            # Initial TOC header validation
            toc_header = self.handler.read_toc_header("TOC")
            toc_header.print_info()
            
            # Process subsequent headers
            for img_type in self.IMAGE_TYPES:
                self.headers[img_type] = self.handler.read_toc_header(img_type)
                self.headers[img_type].print_info()
                
            # Process OFFSET header
            self.headers["OFFSET"] = self.handler.read_toc_header("OFFSET")
            self.headers["OFFSET"].print_info()
            
        except RuntimeError as e:
            print(f"[!] Processing error: {e}")
            raise
            
    def _extract_images(self):
        for img_type in self.IMAGE_TYPES:
            data = self.handler.extract_image(self.headers[img_type], img_type)
            with open(f"{img_type.lower()}.bin", "wb") as f:
                f.write(data)
            print(f"Extracted {img_type} image")

    def analyze_images(self, at_search: bool, version_pattern: Optional[str]):
        with open("main.bin", "rb") as f:
            main_data = f.read()
            
        if at_search:
            count = ImageHandler.find_strings(main_data, rb"AT\+[^\x00]*", "AT.TXT")
            print(f"Found {count} AT command strings")
            
        if version_pattern:
            pattern = re.escape(version_pattern).encode() + rb"[^\x00]*"
            count = ImageHandler.find_strings(main_data, pattern, "VER.TXT")
            if count > 0:
                with open("VER.TXT", "rb") as f:
                    print(f"Possible version: {f.readline().decode().strip()}")

def main():
    parser = argparse.ArgumentParser(
        description="SHANNON Image Unpacker v2",
        epilog="Xiang Xiang Cui Cui"
    )
    parser.add_argument("file", help="Input firmware file")
    parser.add_argument("--at", action="store_true", help="Extract AT commands")
    parser.add_argument("--ver", help="Search for version strings (provide model prefix)")
    
    args = parser.parse_args()
    
    try:
        unpacker = ShannonUnpacker(args.file)
        unpacker.process_file()
        unpacker.analyze_images(args.at, args.ver)
        
    except Exception as e:
        print(f"Critical error occurred: {e}")
        raise SystemExit(1) from e

if __name__ == "__main__":
    main()

在开始之前,请确定你的设备型号是C1200 v1,以及备份好你的PPPoE账户和密码信息以及VLAN信息
由于马来西亚版本软件阉割了波束成形和AP功能,所以尝试将其刷成公开版。

确定版本

首先使用默认用户名和密码登录

默认用户名为admin,密码为TIME(主MAC地址后四位)
比如你的MAC地址为FF:FF:FF:FF:AA:C0,则对应密码是TIMEAAC0

TPTIME2EU_CheckVersion
首先在TIME旧固件下进入Advanced界面,向下滑动到最下方
TPTIME2EU_VersionCk
即可查看到当前的版本
TIME版本有一个特殊点就是完全没有任何关于系统更新的功能在前台,必须通过审查元素的方式才能调出。
而且只有1.0.0 Build 20180502 rel.45702 (EU)一个软件版本号,从来没有更新过

更新前准备

首先需要进入本来的管理页面,进入Advanced界面,在左侧找到Guest Network
TPTIME2EU_OriginalGuest
在Guest Network按钮上右键选择检查,进入DevTools
找到如下节点
TPTIME2EU_GuestPoint
改为
TPTIME2EU_DevTools_Moded
再次点击Guest Network,即可进入被隐藏的固件更新页面!
TPTIME2EU_UpdatePage

更新到中间固件

TP-LINK也不是傻瓜,不会让我们那么轻易的跨版本刷入固件开启更多功能,但是在2018年发布的46288 BETA版本中,这个版本没有刷写验证,所以我们就选择46288版本进行版本中继,继而就可以刷入正常的RELEASE版本。
TPTIME2EU_Flash46288
TPTIME2EU_Flash46288_Progress

更新到最新发布版固件

等待设备自动重启后,你会发现它的整个UI都大变样,同时也有BETA水印
TPTIME2EU_NewUi
直接设置好密码,进入新的页面
TPTIME2EU_NewBar
此时已经变成了公开版,所以也不再需要DevTools来打开固件更新页面了
TPTIME2EU_NewSidebar
直接进入Firmware Upgrade页面
选择2022年最新发布的58207版本,即可更新到RELEASE版本!
TPTIME2EU_Flash58207
TPTIME2EU_Flash58207_Progress

完成

享受波束成形和AP模式吧!
TPTIME2EU_Finish

固件下载

中转用BETA固件: C1200v1_eu_1.1.3_20180502_46288.bin
欧洲/东南亚区域最新版本固件: C1200v1_eu_1.1.4_20220517_58207.bin
美洲区域最新版本固件: C1200v1_us_1.1.3_20220517_58249.bin
TIME原版固件: C1200v1_time_1.0.0_20180502_45702.bin
注:如果需要刷回TIME版固件,请刷入一遍中转用BETA固件才可刷入TIME版固件