2023 CISCN 部分RE复现

Posted Carykd

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2023 CISCN 部分RE复现相关的知识,希望对你有一定的参考价值。

2023 CISCN REVERSE

本次初赛情况一般般吧,简单复现一下ezbyte和movAside

ezbyte

原方法(FAILED)

定位完后就可以进入Judge_out

重点就是judge_in

从这一步开始到后面就变得举步维艰了。

正面强做,但始终没能找到些什么。
虽然也没什么好可惜的其实,因为的确是不会呢。


新方法

首先了解一下DWARF

这是以一种统一的风格来适应多种语言符号化的源码级调试需求的调试信息格式

具体可以参考DWARF5文档

首先readelf -wf ezbyte > output.txt,表示把调试信息中的frame输出到output.txt中

一方面,上一次做不出来是因为我们卡在了hard函数部分,因此我们着重关注这一部分的调试信息。

我们注意到,0x404c21处比较特殊,出现了很长的表达式。

于是我们阅读这个部分的表达式,可以发现它最终作用在r12上。
经过一系列的压栈、异或等运算后,可以复原出大致逻辑。
我们猜测r12应该为0。

脚本
from z3 import *
from Crypto.Util.number import *
r12 = BitVec(\'r12\', 64)
r13 = BitVec(\'r13\', 64)
r14 = BitVec(\'r14\', 64)
r15 = BitVec(\'r15\', 64)
s = Solver()
s.add((r12 + 1892739) ^ 1237891274917891239 == 2616514329260088143)
s.add((r13 + 8971237) ^ 1209847170981118947 == 8502251781212277489)
s.add((r14 + 1512312) ^ 1098791727398412397 == 2451795628338718684)
s.add((r15 + 9123704) ^ 1890878197237214971 == 8722213363631027234)
if s.check() == sat:
# for m in models print long_to_bytes
	for m in s.model():
		print(f"m : long_to_bytes(s.model()[m].as_long())[::-1]")
# flage609efb5-e70e-4e94-ac69-ac31d96c3861

moveAside

调试过程中可以发现是逐个字符比对的,调用的strcmp。
那么可以通过爆破解决这个题目。
但我们需要判断是否通过了strcmp,于是可以通过hook解决。

hook构建
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h> // 用于LINUX动态链接库操作的头文件

#define unlikely(x) __builtin_expect(!!(x),0)   
// #define likely(x) __builtin_expect(!!(x), 1)
// 使用likely(),执行 if 后面的语句的机会更大,使用 unlikely(),执行 else 后面的语句的机会更大。

// dlsym: 从一个动态链接库或者可执行文件中获取到符号地址。
// RTLD_NEXT: 找到该符号下一次出现的地方(也就是说可以劫持原本的函数,执行我们定义的函数)
// 实现:如果g_sys_##name,即系统本身的这个函数(在这里指strcmp)返回为0(即字符串相等)时,把系统的函数
#define TRY_LOAD_HOOK_FUNC(name) if(unlikely(!g_sys_##name)) \\
    g_sys_##name=(sys_##name##_t)dlsym(RTLD_NEXT,#name); \\


#define overwrite(name,return_type,...) \\
    typedef return_type (*sys_##name##_t)(__VA_ARGS__); \\
    static sys_##name##_t g_sys_##name = NULL; \\
    extern return_type name(__VA_ARGS__)

#define __log(format,...) fprintf(stderr,"\\33[35m\\033[1m[@]"format"\\33[0m",##__VA_ARGS__)

overwrite(strcmp, int, char* a, char* b)
    TRY_LOAD_HOOK_FUNC(strcmp);
    printf("strcmp\\n");
    fflush(stdout);
    return g_sys_strcmp(a,b);

// gcc  -fPIC -shared -o strcmp_hook.so ./strcmp_hook.c  -ldl -O3 -m32


/**
 * 反编译代码如下
 * 1. 首先检查是否已经初始化g_sys_strcmp,这是一个全局变量,用于保存真实函数指针,第一次调用hook时没有初始化
 * 2. 未初始化,则初始化为真实函数地址;已初始化,则跳过这一步骤
 * 3. 放出字符串
 * 4. 清理输出缓冲区
 * 5. 调用真正的strcmp
 * 
*/
// int __cdecl strcmp(int a1, int a2)
// 
//   if ( !g_sys_strcmp )
//     g_sys_strcmp = (int)dlsym((void *)4294967295, "strcmp");
//   puts("strcmp");
//   fflush(stdout);
//   return ((int (__cdecl *)(int, int))g_sys_strcmp)(a1, a2);
// 
编译好后,我们让它优先于系统的动态链接库被加载。
爆破脚本
from pwn import *
import string
context.log_level=\'error\'
ans=\'\'
# 操作内容
print(ans, end=\'\')
for _ in range(48):
   for i in string.printable:                   #只打印可见字符
       p=process(\'/home/cary/moveAside\', env="LD_PRELOAD":"./HOOK.so")
                                                #LD_PRELOAD表示优先加载我们的so,从而让系统的so后加载,这样才能让strcmp调用时先找到hook函数
       p.sendline((ans+i).ljust(48, \'\\\'\'))      #对齐字符串为48个,右侧用\'符号填充
       recv=p.recvall(timeout=0.1)
       print(recv)
       if len(recv.splitlines())==len(ans)+4:   #将返回的字符串分开,首先有一个提示输入,然后输入回显,然后是第一次strcmp字符串;如果正确则会打印第二次strcmp字符串。
           print(i, end=\'\')                     #打印出来给我们看
           ans+=i
           break
       p.close()
# flag781dda4e-d910-4f06-8f5b-5c3755182337

2021CISCN-逆向-galss复现及总结

一、apk文件逆向

  没有接触过app开发,临时学习:

 1.apk文件逆向工具:jadx 、jeb 、jd-gui

选择jadx的gui工具,反编译apk文件(代码阅读为请教tx,app开发需要补课)

 

在主活动中查看主要的代码,

 大致逻辑为在主活动中通过checkflag()函数对用户的输入进行比较,一致则right,而loadlibrary("native-lib")则说明了checkflag函数在某个so文件中,可以在资源文件下找到:

 

只需要反编译so文件完成逆向既可以找到flag。

将.apk后缀更改为.zip,解压缩,在文件夹中可以拿到libnative-lib.so文件

拖入ida进行分析。

 

 

分析checkflag()函数

 

二、异或逆向

自下往上进行解密,第一个加密函数是对传入的值完成异或加密

 

提取出最后的结果

 

 

unsigned char result[] =
{
  0xA3, 0x1A, 0xE3, 0x69, 0x2F, 0xBB, 0x1A, 0x84, 0x65, 0xC2, 
  0xAD, 0xAD, 0x9E, 0x96, 0x05, 0x02, 0x1F, 0x8E, 0x36, 0x4F, 
  0xE1, 0xEB, 0xAF, 0xF0, 0xEA, 0xC4, 0xA8, 0x2D, 0x42, 0xC7, 
  0x6E, 0x3F, 0xB0, 0xD3, 0xCC, 0x78, 0xF9, 0x98, 0x3F
};

 

复现一下异或加密过程

key=\'12345678\'
result[k]=result[k]^key[k%8]

for(i=0;i<len(result);i+=3){
  result[i]=result[i]^result[i+2];
  result[i+2]=result[i+2]^result[i+1]
  result[i+1]=result[i+1]^result[i]^result[i+2]
}
此异或过程的逆向分析如下:
以前三位举例
new_r[0]=old_r[0]^old_r[2]
new_r[2]=old_r[2]^old_r[1]
new_r[1]=old_r[1]^old_r[0]^old_r[2]
//new是已知的,old是待求的

new_r[1]=old_r[1]^new_r[0]
=>
old_r[1]=new_r[1]^new_r[0]

new_r[2]=old_r[2]^old_r[1]  //old_r[1]在上一步后为已知
=>
old_r[2]=old_r[1]^new_r[2]

new_r[0]=old_r[0]^old_r[2]  //old_r[2]在上一步后为已知
=>
old_r[0]=new_r[0]^old_r[2]

 解密脚本:

 for i in range(0,39,3):
        res[i+1]=res[i+1]^res[i]
        res[i+2]=res[i+2]^res[i+1]
        res[i]=res[i]^res[i+2]

#应当注意异或运算的逆向过程,核心是把握对称性,其次要关注在多次的异或运算中后面的值是使用的新值还是旧值

三、RC4

后面两个函数,可以看出是RC4加密算法,

     

 

第一个为S表、T表的初始化,第二个函数为加密过程,注意RC4为对称加密,只需要按照伪代码的逻辑正着实现即可

res=[0xA3,0x1A,0xE3,0x69,0x2F,0xBB,0x1A,0x84,
0x65,0xC2,0xAD,0xAD,0x9E,0x96,0x05,0x02,
0x1F,0x8E,0x36,0x4F,0xE1,0xEB,0xAF,0xF0,
0xEA,0xC4,0xA8,0x2D,0x42,0xC7,0x6E,0x3F,
0xB0,0xD3,0xCC,0x78,0xF9,0x98,0x3F]

key=\'12345678\'
s=[0]*256
t=[0]*256

def rc4_init():
    global s
    global t
    global key
    
    for i in range(0,256):
        s[i]=i
        t[i]=ord(key[i%8])
    v9=0
    for i in range(0,256):
        middle=s[i]
        v9=(v9+s[i]+t[i])%256
        s[i]=s[v9]
        s[v9]=middle

def rc4_encrpto():
    v4=0
    v3=0
    v5=0
    global s
    global res
    for i in range(0,38):
        v4=(v4+1)%256
        v5=s[v4]
        v3=(v3+s[v4])%256
        s[v4]=s[v3]
        s[v3]=s[v5]
        res[i]^=s[(v5+s[v4])%256] 

参考:rc4:带你学加密丨RC4 (qq.com)

#注意识别RC4的加密特征,初始化的S表和T表(循环交换位置),初始化和加密都有个明显的swap()过程;另外注意对称加密的解密过程,算法一致

四、python写解题脚本的收获

res=[0xA3,0x1A,0xE3,0x69,0x2F,0xBB,0x1A,0x84,
     0x65,0xC2,0xAD,0xAD,0x9E,0x96,0x05,0x02,
     0x1F,0x8E,0x36,0x4F,0xE1,0xEB,0xAF,0xF0,
     0xEA,0xC4,0xA8,0x2D,0x42,0xC7,0x6E,0x3F,
     0xB0,0xD3,0xCC,0x78,0xF9,0x98,0x3F]

key=\'12345678\'
s=[0]*256
t=[0]*256
def rc4_init():
    global s
    global t
    global key
    
    for i in range(0,256):
        s[i]=i
        t[i]=ord(key[i%8])
    v9=0
    for i in range(0,256):
        middle=s[i]
        v9=(v9+s[i]+t[i])%256
        s[i]=s[v9]
        s[v9]=middle
    print(\'s:\',s)

def rc4_encrpto():
    v4=0
    v3=0
    v5=0
    global s
    global res
    for i in range(0,38):
        v4=(v4+1)%256
        v5=s[v4]
        v3=(v3+s[v4])%256
        s[v4]=s[v3]
        s[v3]=s[v5]
        res[i]^=s[(v5+s[v4])%256]

        
if __name__==\'__main__\':
    flag=\'\'
    for j in range(0,39):
        res[j]=res[j]^ord(key[j%8])
    print(res)
    for i in range(0,39,3):
        res[i+1]=res[i+1]^res[i]
        res[i+2]=res[i+2]^res[i+1]
        res[i]=res[i]^res[i+2]
        
    print(\'old res:\',res)
    rc4_init()
    rc4_encrpto()
    
    for i in range(len(res)):
        flag+=chr(res[i])
    print(\'now res:\',res)
    print(flag)
    
    

  #注意使用global关键字来定义全局变量

  #if __name__==\'__main__\':定义主函数

  #格外注意python中的数据类型转换,key=\'12345678\',在运算中使用到的key[i]均是使用的字符所对应的ascall码,

    ord()和chr()函数可以实现字符和对象ascall码之间的转换。

 

  

 

 

以上是关于2023 CISCN 部分RE复现的主要内容,如果未能解决你的问题,请参考以下文章

2021CISCN-逆向-galss复现及总结

2021-CISCN-fianl-ezj4va

2021-CISCN-fianl-ezj4va

2021-CISCN-fianl-ezj4va

2021-CISCN-fianl-ezj4va

CISCN逆向《babyre》