看雪·深信服 2021 KCTF 春季赛 | 第五题设计思路及解析
武林大会在即,看雪er们跟随武林少才的脚步齐聚华山,开启第五题《华山论剑》,一共有 4 支战队完成目标。
xtgo战队当仁不让,率先拿下本题,仅用时52092秒。 hzqmwne战队和雨落星沉战队紧追不舍,很快也成功攻破此题。
目前赛程已经过半,我们来看看目前场上的状况吧!攻击方排名前10如下:
大家在赛场上你追我赶,好不热闹!接下来我们一起来看看本题的“通关宝典”吧!
出题团队简介
本题出题战队为 ArmVMP :
专家点评
赛题设计思路
算法简单,可玩性高,有兴趣可以随时交流。
规则2的demo为:KCTF-2.sign.apk
其中规则2的两组序列号如下:
serial:7C9815255BFE832D3F93140B
serial:17726331DA0fE737149c8202
设计思路
2. hash值做明文和Java_com_example_hellojni_HelloJni_stringFromJNI地址做密钥K参与rc4运算(稍作修改),其中hash前12字节与密钥流按位做异或并与hash后12字节按位相加得到新的12字节串值。
3. 将新得到的十六进制12字节串转换为ASCII码形式即为24字节的serial值。
保护方法
解题思路
赛题解析
本赛题解析由看雪论坛 mb_mgodlfyn 给出:
先尝试用ida在真机上调试,体验很不好(最主要的问题是,0x5000处的汇编指令ida识别为"BL LR, #0xBA ",但是单步调试时无法进入这条指令内部,不知道原因)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <assert.h>
struct str {
const char *s;
int len;
};
//struct str global_name = {.s = "ed8b9244350d3644", .len = 16};
//struct str global_serial = {.s = "7C9815255BFE832D3F93140B", .len = 24};
struct str global_name = {.s = "KCTF", .len = 4};
struct str global_serial = {.s = "17726331DA0FE737149C8202", .len = 24};
//struct str global_serial = {.s = "17726331da0fe737149c8202", .len = 24};
struct JNINativeInterface_ {
unsigned int f[0x1000/4];
};
typedef struct JNIEnv_ {
struct JNINativeInterface_ *functions;
} JNIEnv;
void JNICALL_FindClass(JNIEnv *env, const char *name) {
printf("JNICALL_FindClass %s\n", name);
}
void JNICALL_NewStringUTF(JNIEnv *env, const char *utf) {
printf("%s %s\n", __func__, utf);
}
int JNICALL_GetMethodID(JNIEnv *env, void *clazz, const char *name, const char *sig) {
printf("%s %p %s %s\n", __func__, clazz, name, sig);
return 0x55555501;
}
void *JNICALL_CallObjectMethod(JNIEnv *env, void *obj, int methodID) {
assert(methodID == 0x55555501);
printf("%s %p %x\n", __func__, obj, methodID);
return obj;
}
int JNICALL_GetArrayLength(JNIEnv *env, struct str *array) {
printf("%s %p\n", __func__, array);
return array->len;
}
unsigned char *JNICALL_GetByteArrayElements(JNIEnv *env, struct str *array, int isCopy) {
printf("%s %p %d\n", __func__, array, isCopy);
return array->s;
}
void JNICALL_ReleaseByteArrayElements(JNIEnv *env, void *array, void *elems, int mode) {
printf("%s %p %p %d\n", __func__, array, elems, mode);
}
void *got_malloc(int size) {
void *r = malloc(size);
printf("%s %d %p\n", __func__, size, r);
return r;
}
void got_free(void *p) {
printf("%s\n", __func__);
free(p);
}
void got_memset(char *p, int n, int count) {
printf("%s %p %d %d\n", __func__, p, n, count);
memset(p, n, count);
}
void bp(void) {
;
}
void stack_chk_guard(void) {
printf("%s\n", __func__);
}
void imp___gnu_Unwind_Find_exidx(void) {
printf("%s\n", __func__);
}
void cxa_call_unexpected(void) {
printf("%s\n", __func__);
}
int main(void) {
int fd = open("libhello-jni.so", O_RDONLY);
unsigned char *fmem = mmap(NULL, 0x7000, PROT_READ, MAP_PRIVATE, fd, 0);
unsigned char *mem = mmap(0xdead0000, 0x8000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
memcpy(mem, fmem, 0x2b13);
memcpy(mem+0x3e8c, fmem+0x2e8c, 0x1a8);
memcpy(mem+0x5000, fmem+0x4000, 0x26bc);
munmap(fmem, 0x7000);
close(fd);
*(unsigned int *)(mem+0x3f98) = mem+0x1034+1; // xxxxxxxxxx2+1
*(unsigned int *)(mem+0x3f9c) = stack_chk_guard;
*(unsigned int *)(mem+0x3fb8) = imp___gnu_Unwind_Find_exidx;
*(unsigned int *)(mem+0x3fc4) = cxa_call_unexpected;
*(unsigned int *)(mem+0x3fc8) = mem+0x3f98; // _GLOBAL_OFFSET_TABLE_@got
*(unsigned int *)(mem+0x3fa0) = mem+0x10e4+1; // Java_com_example_hellojni_HelloJni_stringFromJNI_ptr+1
*(unsigned int *)(mem+0x3fa4) = mem+0x4004; // f_data_key_dllink
*(unsigned int *)(mem+0x3fa8) = mem+0x401c; // f_sucess
*(unsigned int *)(mem+0x3fdc) = got_malloc; // malloc@got
*(unsigned int *)(mem+0x3fe0) = got_memset; // memset@got
*(unsigned int *)(mem+0x3fe4) = got_free; // free@got
for (int i = 0x3f98; i < 0x4000; i+=4) {
if (*(unsigned int *)(mem+i) == 0) {
//*(unsigned int *)(0x77770000+i) = i;
}
}
struct JNINativeInterface_ jnii = {.f = {0}};
for(int i = 0; i < 0x100; i++) {
jnii.f[i] = 0x11110000+i*4;
}
jnii.f[0x18/4] = JNICALL_FindClass;
jnii.f[0x29c/4] = JNICALL_NewStringUTF;
jnii.f[0x84/4] = JNICALL_GetMethodID;
jnii.f[0x88/4] = JNICALL_CallObjectMethod;
jnii.f[0x2ac/4] = JNICALL_GetArrayLength;
jnii.f[0x2e0/4] = JNICALL_GetByteArrayElements;
jnii.f[0x300/4] = JNICALL_ReleaseByteArrayElements;
JNIEnv env;
env.functions = &jnii;
printf("&global_name: %p, &global_serial: %p\n", &global_name, &global_serial);
bp();
((void (*)(int, int, int, int, int))(mem+0x10e4+1))(&env, 0xaaaa, &global_name, &global_serial, 0xdddd);
return 0;
}
调试:
从thumb指令开始(0x5d04),通过一个B跳转跳过1或2个dword(0x5d06和0x5d08)到后面的arm指令(0x5d10),先是 BX PC ,然后是 STR PC, [SP,#var_FC] 把PC放入栈,最后B跳转到一个外部函数(0x7270),外部函数返回到下一条指令。
外部函数大部分以push所有寄存器开始,以pop所有寄存器结束,通过栈上保存的PC向前找参数(0x5d06和0x5d08),返回到下一条指令(0x5d1c)。
LOAD:00005D04 ; ---------------------------------------------------------------------------
LOAD:00005D04 CODE16
LOAD:00005D04 B sub_5D10 ; Branch
LOAD:00005D04 ; ---------------------------------------------------------------------------
LOAD:00005D06 CODE32
LOAD:00005D06 DCW 0xBF00
LOAD:00005D08 DCD 8, 0x30D00
LOAD:00005D10 CODE16
LOAD:00005D10
LOAD:00005D10 ; =============== S U B R O U T I N E =======================================
LOAD:00005D10
LOAD:00005D10 ; Attributes: thunk
LOAD:00005D10
LOAD:00005D10 sub_5D10 ; CODE XREF: LOAD:00005D04↑j
LOAD:00005D10 BX PC ; Branch to/from Thumb mode
LOAD:00005D10 ; ---------------------------------------------------------------------------
LOAD:00005D12 DCB 1
LOAD:00005D13 DCB 0
LOAD:00005D13 ; End of function sub_5D10
LOAD:00005D13
LOAD:00005D14 CODE32
LOAD:00005D14
LOAD:00005D14 ; =============== S U B R O U T I N E =======================================
LOAD:00005D14
LOAD:00005D14
LOAD:00005D14 sub_5D14 ; CODE XREF: sub_5D10↑j
LOAD:00005D14
LOAD:00005D14 var_FC = -0xFC
LOAD:00005D14
LOAD:00005D14 STR PC, [SP,#var_FC] ; Store to Memory
LOAD:00005D18 B sub_7270 ; Branch
LOAD:00005D18 ; End of function sub_5D14
LOAD:00005D1C ; ---------------------------------------------------------------------------
LOAD:00005D1C CODE16
LOAD:00005D1C B sub_5D28 ; Branch
LOAD:00005D1C ; ---------------------------------------------------------------------------
LOAD:00005D1E CODE32
几个关键的位置:
前期调试分析过程很漫长,但最终找出serial很简单:
serial:17726331DA0FE737149C8202
往期解析
1. 看雪·深信服 2021 KCTF 春季赛 | 第二题设计思路及解析
主办方
第六题正在火热进行中,
-
看雪·众安 KCTF赛况直播 | 战况升级!比分逐渐拉大
看雪·众安 2021 KCTF秋季赛 已于11月15日中午12点正式开赛!第9题《万事俱备》已经持续2天。现在跟随我一起看看赛况如何吧!截至发文,目前该题围观人数多达927人,已有4支战队攻破此题!分
-
看雪·众安 KCTF赛况直播 | 白热化!第八题《群狼环伺》谁先拿下?
看雪·众安 2021 KCTF秋季赛 已于11月15日中午12点正式开赛!今天中午12点,第8题《群狼环伺》已正式开战!截至发文,该题共计围观人数:325,攻破战队数:0截至发文防守方总排名如下:攻击
-
看雪·众安 2021 KCTF 秋季赛 | 第六题设计思路及解析
看雪·众安 2021 KCTF秋季赛的第六题《窥伺者谁》已于今天中午12点截止答题,经统计,本题围观人数多达1080人,共计6支战队成功破解。恭喜v3r1t4s501用时19720秒拿下“一血”,接下
[广告]赞助链接:
关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
随时掌握互联网精彩
- SequoiaDB 巨杉数据库金融级分布式关系型数据库
- PlayOK:在线和其他真人玩家对弈,摸鱼,我是专业的
- AppNode:服务器内部错误:接受服务协议失败
- 二进制系列之Pwn篇-组合购买更有性价比
- 字节跳动裁撤“派对岛”项目团队;苹果承认iPhone 14系列存在SIM卡识别问题;deepin 20.7.1发布|极客头条
- 11 年膨胀 575 倍,微信为何从“小而美”变成了“大而肥”?
- TikTok 宣布将数据存储于 Oracle 服务器!
- 喜报!又拍云入选 SegmentFault 思否 2021 年度技术团队
- 内核漏洞学习-HEVD-NullPointerDereference
- 张耀疆:赋能业务是首席信息安全官的发展方向
- 我,对安全研究专家下手的黑客!
- 开源TCP/IP库中曝出33个漏洞,超100万物联网设备受影响!