ciscn2024re复现

ciscn2024复现总结:

首先,这次比赛最大的收获就是完整的遇到了这么多不同语言的逆向程序,也有了对复杂程序逆向的见解,虽然最后还是被大佬打烂了,但还是跟师哥收获到了许多,记录复现一下这次的比赛吧

1、ASM_RE:

我认为这是最简单的一道题目,程序给了汇编代码,我当时是直接给了ai,让他分析了一下加密逻辑,一个简单的加密,数据在最后提取

方法一:AI分析或者阅读汇编

fig:

fig:

给一下脚本:

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分析,学习一下吧

方法二:提取机器码,IDA分析:

将asm文本文件中的代码段的机器码提取出来,就是下面这些:

fig:

放入文件中,然后进行ida反编译

 

2、APP_dbg

这道题当时看到还是比较简单的,和我做的一道ISCC的安卓题差不多,都是考主动调用然后去获得KEY和IV的,简单分析一下代码吧:

fig:

代码给的还是很简单的,说明了DES的CBC模式,所以我们只要主动调用一下native层的getiv和getkey函数就行

fig:

贴一下代码,我使用的是frida进行hook的

FridaHOOK

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解密了

fig:

用flag{}进行包裹就ok啦

本来记得看到有个大佬也是没出来但是有其他方法的,突然找不到大佬的博客在哪了,

好奇能不能直接对so文件进行ida动调取值,之前试过一次,看不明天,找机会一定要再去尝试一下

3、rust_baby

rust的题目,又臭又长的代码,但是对于大佬来说依旧是just so so

向一位师哥请教了这个题目,(师哥yydss),现在复现一下这个题目

 

加密流程简介:

首先简单说一下这个程序的流程:

自己进行的加密然后异或33,再进行一层流密码加密,最后base64加密

fig:

所以这个关键就是找到这些加密的地方,分析一下加密代码,然后提取这些加密后的数据,或者提取一下这些密码的密钥

先来说说我的方法吧,后面有大佬的方法

分析流程;

1、定位主要函数:

fig:

我是根据这些base64来定位到的这个函数,从而进行分析

2、第一个加密函数:

fig:

这个就是我刚才所说的第一个加密函数的地方,然后异或了0x33

进去看看加密函数:

fig:

看着有点小乱,给他简化一下:

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

fig:

最后有一个异或,所以我们可以通过动调去dump这个值,至于怎么快速找到这个地方,我觉得对上个加密函数的值进行交叉引用比较快速

fig:

在此处进行获取值,但是他会进行很多次轮换,所以要多次进行dump取值,可以使用idapython进行统一dump,也可以一次一此进行dump

我简单记录一下,

fig:

这是第一次dump下来的值,下面的0xAB是rust进行的填充

fig:

就这样多进行几轮我们就可以得到完整的key,其实根据这个我们可以猜到他的数目,他是与密文进行进行异或的,密文也可以通过动调得到,但是他给提示了a,就是那个前面一堆base64的地方,里面有密文,也有第一层函数的第三个参数henhen,因为他最后是base64加密的,所以只要是与base64相近·的就是密文,然后与我们dump下来的进行异或就可以得到第一层的密文,然后再进行解密

4、第三个加密函数:

fig:

这一处就是一个简单的base64加密以及一个cmp函数

最后的cmp函数在此处

fig:

双击v106看看密文是什么

fig:

给一下解密代码:

base64密文提取一下:

fig:

解密代码:

下面给一下解密代码:
关键是流密码key的提取


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

fig:

此处是进行异或的地方

fig:

这些就是根据我们之前所输入的值进行的数据,我们将其改为0,最后不就是key了吗

fig:

fig:

最后可以得到key

fig:

这样做更加快捷也不会发生错误

大佬的视频讲解

4、gdb_debug

ida分析一下逻辑:
fig:

大致逻辑还是比较清晰的,直接一处处动调获得他的随机值就好

浅浅给一下代码:

解密代码:

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,不需要再一点点找

代码如下:

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 会是对应的值。然后,你打印出每个属性名和对应的值

获取一下:
fig:

得到:

__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分析:

首先先来看一下这个语言特性:

fig:

先来看一下这些变量名啥的都是什么意思,不能只为找答案而看代码

1、__pyx_pyargnames[0]

这个东东什么东西,借助ai分析一下:

__pyx_pyargnames[0] 是 Cython 生成的代码中的一个内部变量,通常用于处理函数参数名称。在 Cython 编译的 C 代码中,这些变量帮助处理传递给函数的参数,并确保正确地解析参数名称和位置

按照我的理解就是一个指针数组,来存储函数的参数

下面给了个例子:

def my_function(int a, int b):
return a + b

生成的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 代码中的一个内部变量,通常用于存储字符串常量

简单来说是存储字符串

举个例子:

def greet(name):
print(f"Hello, {name}!")

编译为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分析:

fig:

有个参数0,有个跳转,过去看看

fig:

根据名字才有个随机数random的还有一个seed的,所以应该是要调用random调用

继续看

fig:

多种迹象表明,他的种子就是0

3、whereistheflag1分析

trytry完之后就是flag1,看看他有什么操作

fig:

看看他的跳转,这样一看cpython还是有规律的hhh

fig:

猜测是base64加密一下

1、base64encode

fig:

果真如此

继续往下看可以看到有这个randint

2、randint()

fig:

randint作用:

random.randint(a, b)
'''
生成从a到b之间的随机整数
'''

继续往下看:

3、Xor:

fig:

有一个异或,应该就是随机数与明文之间的异或了

他的随机数我们也可以猜到就是从0到明文的长度之间

其他的函数就没有什么用了,可能是用来迷惑的,好像其他的这几个函数也有randint,但是他的范围变了,所以不用管,也可能有其他原因

解密代码:

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语言的题目没做,有点难啊,逆向的学习依旧任重而道远,但也还是蛮快乐的,继续加油吧

如果有什么错误,请各位大佬师傅多多指教!

posted @ 2024-06-08 09:37  小waaaa  阅读(119)  评论(0)    收藏  举报