本帖最后由 xiaowaaa 于 2024-6-9 15:58 编辑
ciscn2024复现总结:
首先,这次比赛最大的收获就是完整的遇到了这么多不同语言的逆向程序,也有了对复杂程序逆向的见解,虽然最后还是被大佬打烂了,但还是跟师哥收获到了许多,记录复现一下这次的比赛吧
1、ASM_RE:
我认为这是最简单的一道题目,程序给了汇编代码,我当时是直接给了ai,让他分析了一下加密逻辑,一个简单的加密,数据在最后提取
方法一:AI分析或者阅读汇编
给一下脚本:
[Python] 纯文本查看 复制代码 data=[0x1fd7,0x21b7,0x1e47,0x2027,0x26e7,0x10d7,0x1127,0x2007,0x11c7,0x1e47,
0x1017,0x1017,0x11f7,0x2007,0x1037,0x1107,0x1f17,0x10d7,0x1017,0x1017,0x1f67,0x1017,
0x11c7,0x11c7,0x1017,0x1fd7,0x1f17,0x1107,0x0f47,0x1127,0x1037,0x1e47,0x1037,0x1fd7,0x1107,0x1fd7,0x1107,0x2787]
for i in range(len(data)):
data[i]-=0x1e
for i in range(len(data)):
data[i]^=0x4d
for i in range(len(data)):
data[i]-=0x14
for i in range(len(data)):
data[i] //= 0x50
for i in range(len(data)):
print(chr(data[i]),end='')
print()
但是总不能都交给ai分析吧,万一遇到复杂的还是不行的,最好的方法就是去读汇编,看了大佬的WP,是将其提取机器码然后放入IDA分析,学习一下吧
2、APP_dbg
这道题当时看到还是比较简单的,和我做的一道ISCC的安卓题差不多,都是考主动调用然后去获得KEY和IV的,简单分析一下代码吧:
代码给的还是很简单的,说明了DES的CBC模式,所以我们只要主动调用一下native层的getiv和getkey函数就行
贴一下代码,我使用的是frida进行hook的
FridaHOOK
[JavaScript] 纯文本查看 复制代码
function main() {
Java.perform(function () {
//加载了Checker类
var CheckerClass = Java.use("com.example.re11113.jni");
// 调用getiv方法
var result = CheckerClass.getiv();
console.log("getiv returned: " + result);
console.log("=================================================" );
var CheckerClass = Java.use("com.example.re11113.jni");
// 调用getkey方法
var result2 = CheckerClass.getkey();
console.log("getkey returned: " + result2);
console.log("=================================================" );
});
}
setImmediate(main);
得到:
Key: A8UdWaeq
Iv: Wf3DLups 通过这个代码我获取到了iv的值,但是一直获取不到key的值,我觉得我的代码是没有问题的,毕竟已经获得到了iv的值,key的值应该不会出错,但是就是出不来,一直报返回值是UTF-8不符的错误,真的不理解,如果有大佬知道,还请解惑一下,不会真的是代码写错了吧。
DES解密:
接下里就是DES解密了
用flag{}进行包裹就ok啦 本来记得看到有个大佬也是没出来但是有其他方法的,突然找不到大佬的博客在哪了,好奇能不能直接对so文件进行ida动调取值,之前试过一次,看不明天,找机会一定要再去尝试一下
3、rust_baby
rust的题目,又臭又长的代码,但是对于大佬来说依旧是just so so向一位师哥请教了这个题目,(师哥yydss),现在复现一下这个题目 加密流程简介:
首先简单说一下这个程序的流程:自己进行的加密然后异或33,再进行一层流密码加密,最后base64加密
所以这个关键就是找到这些加密的地方,分析一下加密代码,然后提取这些加密后的数据,或者提取一下这些密码的密钥先来说说我的方法吧,后面有大佬的方法 分析流程;
1、定位主要函数:
我是根据这些base64来定位到的这个函数,从而进行分析
2、第一个加密函数:
这个就是我刚才所说的第一个加密函数的地方,然后异或了0x33 进去看看加密函数:
看着有点小乱,给他简化一下:
[Python] 纯文本查看 复制代码 def encrtpt( val, key, henhen){
for i in range(4):
k0=key[2*i]
k1=key[2*i+1]
h0=henhen[2*i]
h1=henhen[2*i+1]
v9=(k0^h0 | k1 ^ h1) &1
ret[i*2] = val[k0&7] -v9 +i
ret[i*2+1] = val[h1&7] -v9 +i
return ret
}
def decrypt(enc , key , henhen){
for i in range(len(key)):
key_bytes =pack('<Q', key[i])
henhen_bytes =pack('<Q', henhen[i])
ret = [None] * 8
new_cip=cip[i*8:(i+1)*8]
for i in range(0,4):
k0 = key_bytes[2 * i]
k1 = key_bytes[2 * i + 1]
h0 = henhen_bytes[2 * i]
h1 = henhen_bytes[2 * i + 1]
v9 = (k0 ^ h0 | k1 ^ h1) & 1
ret[k0&7] = (new_cip[2*i] + v9 - i)
ret[k1&7] = (new_cip[2*i+1] + v9 - i)
for i in range(len(ret)):
print(chr(ret[i]),end='')
}
3、第二个加密函数:
看这个代码格式有点像AES
最后有一个异或,所以我们可以通过动调去dump这个值,至于怎么快速找到这个地方,我觉得对上个加密函数的值进行交叉引用比较快速
在此处进行获取值,但是他会进行很多次轮换,所以要多次进行dump取值,可以使用idapython进行统一dump,也可以一次一此进行dump我简单记录一下,
这是第一次dump下来的值,下面的0xAB是rust进行的填充
就这样多进行几轮我们就可以得到完整的key,其实根据这个我们可以猜到他的数目,他是与密文进行进行异或的,密文也可以通过动调得到,但是他给提示了a,就是那个前面一堆base64的地方,里面有密文,也有第一层函数的第三个参数henhen,因为他最后是base64加密的,所以只要是与base64相近·的就是密文,然后与我们dump下来的进行异或就可以得到第一层的密文,然后再进行解密
4、第三个加密函数:
这一处就是一个简单的base64加密以及一个cmp函数最后的cmp函数在此处
双击v106看看密文是什么
给一下解密代码:base64密文提取一下:
解密代码:
下面给一下解密代码:
关键是流密码key的提取
[Python] 纯文本查看 复制代码
import base64
from struct import pack,unpack
cip='igdydo19TVE13ogW1AT5DgjPzHwPDQle1X7kS8TzHK8S5KCu9mnJ0uCnAQ4aV3CSYUl6QycpibWSLmqm2y/GqW6PNJBZ/C2RZuu+DfQFCxvLGHT5goG8BNl1ji2XB3x9GMg9T8Clatc='
key='3F8gIsJ5GVY12otH0xn8VRTN0ntYWQlC3iy0SNnyG6lA4ab7/zjB1eLod3hvIgTmFj4MNVJc/cHlWRzQrlqy3Rn4QuYsiVnlEZzIe4Fwf2+8bwKP9/TIcK4C+FvicggJb79LObXQHqM='
cip=bytearray(base64.b64decode(cip))
key=bytearray(base64.b64decode(key))
enc=[0]*104
for i in range(len(cip)):
cip[i]=cip[i]^key[i]^0x33
key=[0xE71675B493928150, 0x37C65D4C7BA24118, 0x2F6E0584C3B26920 ,0xC74625ECF3321978, 0x9FFE15F4FB724940, 0x27C69D1453F2E1B0,0xE71675B493928150, 0x37C65D4C7BA24118, 0x2F6E0584C3B26920 ,0xC74625ECF3321978, 0x9FFE15F4FB724940, 0x27C69D1453F2E1B0,0xE71675B493928150]
henhen=[1, 1, 4, 5, 1, 4, 1, 9, 1, 9, 8, 1, 0]
for i in range(len(key)):
key_bytes =pack('<Q', key[i])
henhen_bytes =pack('<Q', henhen[i])
ret = [None] * 8
new_cip=cip[i*8:(i+1)*8]
for i in range(0,4):
k0 = key_bytes[2 * i]
k1 = key_bytes[2 * i + 1]
h0 = henhen_bytes[2 * i]
h1 = henhen_bytes[2 * i + 1]
v9 = (k0 ^ h0 | k1 ^ h1) & 1
ret[k0&7] = (new_cip[2*i] + v9 - i)
ret[k1&7] = (new_cip[2*i+1] + v9 - i)
for i in range(len(ret)):
print(chr(ret[i]),end='')
快捷方法:
大佬所讲解的方法在dump取值比较快捷,利用0与让任何值异或值不会发生变化的特点进行取值,就是将第二处加密的地方把值改为0所以最后比较的时候就是key
此处是进行异或的地方
这些就是根据我们之前所输入的值进行的数据,我们将其改为0,最后不就是key了吗
最后可以得到key
这样做更加快捷也不会发生错误 大佬的视频讲解
大佬视频
4、gdb_debug
ida分析一下逻辑:
大致逻辑还是比较清晰的,直接一处处动调获得他的随机值就好浅浅给一下代码:
解密代码:
[Python] 纯文本查看 复制代码
enc='congratulationstoyoucongratulationstoy'
key=[ 0xBF, 0xD7, 0x2E, 0xDA, 0xEE, 0xA8, 0x1A, 0x10, 0x83, 0x73,
0xAC, 0xF1, 0x06, 0xBE, 0xAD, 0x88, 0x04, 0xD7, 0x12, 0xFE,
0xB5, 0xE2, 0x61, 0xB7, 0x3D, 0x07, 0x4A, 0xE8, 0x96, 0xA2,
0x9D, 0x4D, 0xBC, 0x81, 0x8C, 0xE9, 0x88, 0x78]
#先恢复最后一层的异或
cip=[0]*38
for i in range(len(enc)):
cip[i]=ord(enc[i])^key[i]
rand1=[217,15,24,189,199,22,129,190,248,74,101,242,93,171,43,51,212,165,103,152,159,126,43,93,194,175,142,58,76,165,117,37,180,141,227,123,163,100]
rand2=[0xde,0xaa,0x42,0xfc,0x9,0xe8,0xb2,0x6,0xd,0x93,0x61,0xf4,0x24,0x49,0x15,0x1,0xd7,0xab,0x4,0x18,0xcf,0xe9,0xd5,0x96,0x33,0xca,0xf9,0x2a,0x5e,0xea,0x2d,0x3c,0x94,0x6f,0x38,0x9d,0x58,0xea]
iv = bytes.fromhex("120E1B1E110507011022061716081913040F020D250C03151C140B1A18091D231F20240A0021")
for i in range(len(cip)):
cip[i]=(cip[i])^rand2[i]
#打乱顺序的恢复:
m=[0]*38
for i in range(len(cip)):
m[iv[i]] = cip[i]
flag=''
for i in range(38):
flag+=chr(m[i]^rand1[i])
print(flag)
快捷方法:
直接下断点然后右键edit breakpoint然后使用idapython进行dump,不需要再一点点找代码如下:
[Python] 纯文本查看 复制代码
import idaapi
import idc
# 目标地址
address = 0x00005617AB600B17
# 确保 IDA 在调试模式下运行
if not idaapi.dbg_can_query():
print("请启动调试器并运行到目标地址")
else:
# 跳转到目标地址
idc.jumpto(address)
# 设置断点
idc.add_bpt(address)
# 运行到断点
idaapi.continue_process()
# 等待暂停
idaapi.wait_for_next_event(idaapi.WFNE_SUSP, -1)
# 读取 al 寄存器的值
al_value = idc.get_reg_value("al")
# 移除断点
idc.del_bpt(address)
# 打印寄存器值
print(f"{al_value},")
5、whereThel1b
前言:
首先可以看到so文件是一个cpython编写的文件,他是非常难看的,同时可以看到他写了一个python文件对这个so文件进行引用,那么我们要尽可能的多利用这个python文件获取信息。那么这时候我们就可以利用python的一个函数dict.items()利用whereThel1b.__dict__.items()首先:whereThel1b 是导入的模块然后whereThel1b.__dict__ 是一个字典,包含了模块 whereThel1b 的所有属性和它们对应的值whereThel1b.__dict__.items() 返回一个视图对象,提供了模块 whereThel1b 的属性和它们对应值的键值对for i, j in whereThel1b.__dict__.items(): 循环遍历这个视图对象,在每次迭代中,i 会是属性名,j 会是对应的值。然后,你打印出每个属性名和对应的值 获取一下:
得到:
[Python] 纯文本查看 复制代码
__name__ = whereThel1b
__doc__ = None
__package__ =
__loader__ = <_frozen_importlib_external.ExtensionFileLoader object at 0x7dac51b634c0>
__spec__ = ModuleSpec(name='whereThel1b', loader=<_frozen_importlib_external.ExtensionFileLoader object at 0x7dac51b634c0>, origin='/home/xiaowaaa/桌面/whereistheflag/whereThel1b.so')
__file__ = /home/xiaowaaa/桌面/whereistheflag/whereThel1b.so
__builtins__ = <module 'builtins' (built-in)>
base64 = <module 'base64' from '/usr/lib/python3.10/base64.py'>
random = <module 'random' from '/usr/lib/python3.10/random.py'>
trytry = <cyfunction trytry at 0x7dac525732a0>
whereistheflag = <cyfunction whereistheflag at 0x7dac52573370>
whereistheflag1 = <cyfunction whereistheflag1 at 0x7dac52573440>
whereistheflag2 = <cyfunction whereistheflag2 at 0x7dac52573510>
encry = [86, 96, 121, 96, 74, 60, 120, 57, 85, 83, 72, 32, 98, 88, 57, 62]
whereistheflag3 = <cyfunction whereistheflag3 at 0x7dac525735e0>
whereistheflag4 = <cyfunction whereistheflag4 at 0x7dac525736b0>
check_flag = <cyfunction check_flag at 0x7dac52573780>
whereistheflag5 = <cyfunction whereistheflag5 at 0x7dac52573850>
whereistheflag6 = <cyfunction whereistheflag6 at 0x7dac52573920>
__test__ = {}
接下来一步步来硬看吧,希望可以发现一点什么东西:
1、cpython分析:
首先先来看一下这个语言特性:
先来看一下这些变量名啥的都是什么意思,不能只为找答案而看代码
1、__pyx_pyargnames[0]
这个东东什么东西,借助ai分析一下:__pyx_pyargnames[0] 是 Cython 生成的代码中的一个内部变量,通常用于处理函数参数名称。在 Cython 编译的 C 代码中,这些变量帮助处理传递给函数的参数,并确保正确地解析参数名称和位置按照我的理解就是一个指针数组,来存储函数的参数下面给了个例子:[Python] 纯文本查看 复制代码 def my_function(int a, int b):
return a + b
生成的c代码:[C] 纯文本查看 复制代码
static PyObject *__pyx_pw_7example_1my_function(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
int __pyx_v_a;
int __pyx_v_b;
static PyObject **__pyx_pyargnames[] = {&__pyx_n_a, &__pyx_n_b, 0};
PyObject *values[2] = {0, 0};
if (__pyx_kwds) {
PyObject* kw_args[2] = {0, 0};
if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, kw_args, 0) < 0))
return NULL;
} else {
values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
}
__pyx_v_a = __Pyx_PyInt_As_int(values[0]);
__pyx_v_b = __Pyx_PyInt_As_int(values[1]);
// 函数主体
PyObject *__pyx_r = PyInt_FromLong(__pyx_v_a + __pyx_v_b);
return __pyx_r;
}
/*
__pyx_pyargnames[0] 和 __pyx_pyargnames[1] 分别对应 my_function 的参数名称 a 和 b
__pyx_pyargnames 是一个指针数组,存储了函数参数的名称。这些名称用于参数解析,特别是在处理关键字参数时
PyObject **values[2] 是一个数组,用于临时存储传递给函数的参数值
*/
这样看还是和这个题目比较相似的
2、_pyx_mstate_global_static
首先看他的名我们也可以大致猜测出来他的作用,静态全局变量嘛,他的实际意义也就是是Cython 生成的一部分内部变量或结构体名称。这类名称通常包含用于管理模块状态、全局变量或静态变量的信息
3、__pyx_n_s_pla
__pyx_n_s_pla 是 Cython 编译生成的 C 代码中的一个内部变量,通常用于存储字符串常量简单来说是存储字符串的举个例子:[Python] 纯文本查看 复制代码
def greet(name):
print(f"Hello, {name}!")
编译为c代码:[C] 纯文本查看 复制代码
/* "example.pyx":1
* def greet(name):
* print(f"Hello, {name}!")
*/
static const char __pyx_k_hello[] = "Hello, ";
static const char __pyx_k_name[] = "name";
static const PyObject *__pyx_n_s_name;
static int __Pyx_InitStrings(void) {
__pyx_n_s_name = PyUnicode_FromString(__pyx_k_name); if (unlikely(!__pyx_n_s_name)) __PYX_ERR(0, 0, __pyx_L1_error)
return 0;
__pyx_L1_error:
return -1;
}
static PyObject *__pyx_pw_example_1greet(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
PyObject *__pyx_v_name = 0;
PyObject *__pyx_t_1 = NULL;
__Pyx_RefNannyDeclarations
__Pyx_RefNannySetupContext("greet", 0);
if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, __pyx_args, __pyx_n_s_name, 0) < 0)) __PYX_ERR(0, 0, __pyx_L1_error)
__pyx_v_name = __pyx_t_1;
// 函数主体省略
}
/*
__pyx_k_name 是一个包含字符串 "name" 的 C 字符串常量。
__pyx_n_s_name 是一个 PyObject 指针,用于引用 Python 字符串对象 "name"
*/
4、__pyx_kwds
这个_pyx_kwds 是 Cython 生成的 C 代码中的一个内部变量,用于处理传递给函数的关键字参数。在 Cython 编译的 C 代码中,__pyx_kwds 用于捕获传递给函数的关键字参数,并将其转换为 C 代码中可以处理的形式也即是关键字参数,用于函数传递参数因为python中,函数可以接受两种参数,位置参数和关键字参数,位置参数是根据它们在函数定义中的位置传递的参数,而关键字参数是带有名称的参数。
5、__pyx_args
__pyx_args 是 Cython 编译生成的 C 代码中的一个内部变量,用于处理传递给函数的位置参数相对于上面的关键字参数一样,也是一个参数
6、__pyx_L4_argument_unpacking_done
还是先根据变量名先猜测,这个应该就是一个解包的标签__pyx_L4_argument_unpacking_done 是 Cython 编译生成的 C 代码中的一个标签(label),用于表示函数参数解包完成的位置。在 Cython 编译的 C 代码中,标签通常用于控制程序流程,标识代码中的特定位置,以便在需要时跳转到该位置这个有点像我们平常间的label什么的,就是指示作用,说明我们的函数要去跳转到哪一部分
7、ob_refcnt
ob_refcnt 是 Python 对象的一个成员,用于表示对象的引用计数。引用计数是一种内存管理技术,用于跟踪对象被引用的次数 我就将其认为是一个计数器,相当于循环里的i一样 根据这些就可以先进行大致的分析,对于其他陌生的我们也可以根据变量名进行猜测,他的工作也就是起到从python到c的过渡作用,所以我们还是可以根据所学进行猜测
2、trytry分析:
有个参数0,有个跳转,过去看看
根据名字才有个随机数random的还有一个seed的,所以应该是要调用random调用继续看
多种迹象表明,他的种子就是0
3、whereistheflag1分析
trytry完之后就是flag1,看看他有什么操作
看看他的跳转,这样一看cpython还是有规律的hhh
猜测是base64加密一下
1、base64encode
果真如此继续往下看可以看到有这个randint
2、randint()
randint作用:
[Python] 纯文本查看 复制代码 random.randint(a, b)
'''
生成从a到b之间的随机整数
'''
继续往下看:
3、Xor:
有一个异或,应该就是随机数与明文之间的异或了他的随机数我们也可以猜到就是从0到明文的长度之间其他的函数就没有什么用了,可能是用来迷惑的,好像其他的这几个函数也有randint,但是他的范围变了,所以不用管,也可能有其他原因
解密代码:
[Python] 纯文本查看 复制代码
import base64
import random
encry = [108, 117, 72, 80, 64, 49, 99, 19, 69, 115, 94, 93, 94, 115, 71, 95, 84, 89, 56, 101, 70, 2, 84, 75, 127, 68, 103,
85, 105, 113, 80, 103, 95, 67, 81, 7, 113, 70, 47, 73, 92, 124, 93, 120, 104, 108, 106, 17, 80, 102, 101, 75, 93, 68, 121, 26]
lens=len(encry)
random.seed(0)
rand=[random.randint(0,lens) for i in range(len(encry))]
flag=''
for i in range(len(encry)):
flag+=chr(rand[i]^encry[i])
print(base64.b64decode(flag))
小tips:
当时我想要动调这个的,但是一直附加不上,问了问大佬是解决办法是使用:sudo ./linux_server64
成功解决,这个题目如果想要动调的话好像还得跳出一个死循环
总结:
收获还是蛮多的,还有一道go语言的题目没做,有点难啊,逆向的学习依旧任重而道远,但也还是蛮快乐的,继续加油吧如果有什么错误,请各位大佬师傅多多指教!
附件:
链接:https://pan.baidu.com/s/1HF7NHzvrNkehGMaQ8YC1Ig?pwd=1234
提取码:1234 |