阅读须知:
探索者安全团队技术文章仅供参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作,由于传播、利用本公众号所提供的技术和信息而造成的任何直接或者间接的后果及损失,均由使用者
本人负责,作者不为此承担任何责任,如有侵权烦请告知,我们会立即删除并致歉,创作不易转载请标明出处.感谢!
first
题目地址:first
- ida打开,程序接受一个输入,随后进行异或,但并未对flag修改,最后异或的结果放入了v10(后面要用到):
- 往后,又有一个循环里面执行了pthread_create函数,如下为这个函数的功能,其会创建一个线程,最重要的一个参数是该线程的入口函数start_routine:
- 进入start_routine函数查看具体功能,其将flag每4个分成一组进行加密encode_flag,如果加密后的结果v8[0]与byte_602120中的结果相等则将flag放入到res中。:
- 注意在start_routine函数中有pthread_mutex_lock和pthread_mutex_unlock两个函数,函数功能如下,其作用是为了在同时运行多个线程时访问同一个(或多个)数据而产生错误,先给数据上锁的线程优先运行,其他线程在该线程解锁互斥量后才能运行,因此总的来时,先拿到flag的先运行,后拿到的后运行。但是start_routine中有个usleep(v3)函数,导致我们无法判断哪部分的flag先进行加密,所以要对解密出来的6部分flag进行组合,得到正确输入的flag:
- 接下来关注加密函数encode_flag,可见时一个MD5加密,加密后的结果放在byte_602120中,可以直接进爆破,脚本如下:
import hashlib
a=[ 0x47, 0x46, 0xBB, 0xBD, 0x02, 0xBB, 0x59, 0x0F, 0xBE, 0xAC, 0x28, 0x21, 0xEC, 0xE8, 0xFC, 0x5C, 0xAD, 0x74, 0x92, 0x65, 0xCA, 0x75, 0x03, 0xEF, 0x43, 0x86, 0xB3, 0x8F, 0xC1, 0x2C, 0x42, 0x27, 0xB0, 0x3E, 0xCC, 0x45, 0xA7, 0xEC, 0x2D, 0xA7, 0xBE, 0x3C, 0x5F, 0xFE, 0x12, 0x17, 0x34, 0xE8]
print(hashlib.md5("juhu".encode()).hexdigest())
check="4746bbbd2bb59fbeac2821ece8fc5cad749265ca753ef4386b38fc12c4227b03ecc45a7ec2da7be3c5ffe121734e8"
for i in range(6):for j in range(48,128):for k in range(48,128):for m in range(48,128):for n in range(48,128):tmp=chr(j)+chr(k)+chr(m)+chr(n)crypt=hashlib.md5(tmp.encode()).hexdigest()print(tmp)if crypt[0:16]==check[16*i:(i+1)*16]:print(tmp)#"juhuhfenlapsdunuhjifiuer"
- 后续一个循环等待线程结束:
- 后面应该是一窜异或加密后进行比较(防止输入不正确的flag来得到正确的res),最后只是对前面的res与byte_6020DF和v10进行了异或:
- 所以现在只需要知道v10,有之前的分析,v10与输入的flag有关,所以只需要组合出正确的flag即可通过脚本解密出v10,最后解密脚本如下:
res='juhuhfenlapsiuerhjifdunu'
v11=0
for i in range(len(res)):tmp=ord(res[i])+iv11^=tmp
flag="juhuhfenlapsdunuhjifiuer"
key=[0xFE, 0xE9, 0xF4, 0xE2, 0xF1, 0xFA, 0xF4, 0xE4, 0xF0, 0xE7, 0xE4, 0xE5, 0xE3, 0xF2, 0xF5, 0xEF, 0xE8, 0xFF, 0xF6, 0xF4, 0xFD, 0xB4, 0xA5, 0xB2]
for i in range(len(flag)):print(chr(ord(flag[i])^key[i]^v11),end="")
#goodjobyougetthisflag233
总结:有关线程的知识,入口函数设置,共享资源的占用,互斥量:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>// 共享资源
int count = 0;
// 互斥量
pthread_mutex_t lock;// 线程函数
void *print_hello1(void *arg)
{sleep(4); // 睡眠3秒,让2先拿到共享资源for (int i = 0; i < 10; i++){printf("%d", i);}pthread_mutex_lock(&lock); // 锁定共享资源count = 1;printf("\nHello from thread1!:%d\n", count);pthread_mutex_unlock(&lock); // 如果不解除共享资源,那么下一个线程永远不会开始return NULL;
}void *print_hello2(void *arg)
{sleep(3);pthread_mutex_lock(&lock);count = 2;printf("Hello from thread1!:%d\n", count);sleep(2);pthread_mutex_unlock(&lock);sleep(1);printf("hahahaha\n");return NULL;
}int main()
{pthread_t thread_id1, thread_id2; // 储存创建线程的IDint result1, result2;// 初始话互斥变量pthread_mutex_init(&lock, NULL);// 两个线程同时运行,但是遇到互斥量时,先拿到共享资源的线程优先执行result1 = pthread_create(&thread_id1, NULL, print_hello1, NULL); // 创建第一个线程result2 = pthread_create(&thread_id2, NULL, print_hello2, NULL); // 创建第二个线程if (result1 != 0){printf("Error creating thread.");exit(1);}pthread_join(thread_id1, NULL); // 等待线程结束pthread_join(thread_id2, NULL);printf("%d\n", thread_id1);printf("%d\n", thread_id2);printf("Thread joined.");// 销毁互斥变量pthread_mutex_destroy(&lock);return 0;
}
- 先锁定共享资源的线程会先运行,在其没有解锁共享资源时,其他线程都会被阻塞,但是在锁定共享资源前,线程都会同步运行下去,大家壳自行验证,线程是否阻塞是根据 互斥lock是否被上锁决定,在使用 pthread_mutex_lock(&lock);上锁之前会检查lock是否已经锁定,如果锁定则线程会被阻塞在pthread_mutex_lock(&lock)函数的位置,其上锁之前的部分不会受到影响(均可进行自行验证)。
game
题目地址:game
- 找到关键代码直接修改判断条件结束循环,直接输出flag:
- 修改后如下:
- 随便按几下就会直接输出flag=zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}:
总结:game解题的关键
- 梳理好游戏的逻辑后,修改关键的判断语句代码或者直接修改RIP,来快速达成通关条件得到flag。
key
题目地址:key
- 看题目的描述,这题似乎要在程序种找到钥匙,打开门才行:
- 程序运行直接看蒙了,没有提示要接受输入,推测应该是在程序中打开文件直接读取的数据,或者程序要的输入(key)直接作为数据存储在程序中:
- ida打开查看逻辑,果然没有接受任何输入,开始有两端while循环对两个字符串进行了加密,并且只有这两个循环对其进行了修改:
- 往后看有一个sub_401620函数,进到里面发现打开了一个flag文件(与前面猜想的一样),打开文件后自然要判断打开是否成功,再读取文件中的内容(看文件名里面似乎就住着flag):
- 由于这里给的路径是出题人本机的路径,我们可以在自己的主机上创建这路径并生成flag.txt文件,或者直接修改路径如下:
- 通过动态调试,后续的if语句就是判断文件是否打开成功,猜测后面的sub_D12E90函数是读取打开的文件中的内容,存放到v23,动态调试发现果然如此,后面应该时判断了读取是否成功。
- 最后,目的flag已经读取出来,并且程序中的字符串也解密完成,只需要比较即可,比较函数并没有直接给出,我们根据结果 “Congrats You got it!” 来反推,要达到 “Congrats You got it!”,v7必须是0,而v7是函数sub_4020C0的返回值,所以可以直接推断函数sub_4020C0就是比较函数:
- 虽然函数sub_4020C0中的逻辑一言难尽,但是不妨碍我们做出以上推测(或则可以在flag.txt文件中输入不同的字符串进行调试,来反推函数sub_4020C0的功能):
- 利用前的加密逻辑编写脚本即可解密出需要的key:
a=">----++++....<<<<."
key="themida"
for i in range(len(a)):print(chr((ord(a[i])^ord(key[i%len(key)]))+9+22),end="")
- 或者直接运行程序,然后再内存中查找,这里存放的位置并不在表面上的v29中,真的在哪里大家就自己区找喽:
最后来分析一下加密函数sub_4021E0:
- 看一下其中加密函数sub_4021E0的声明类型是 __thiscall,这种函数在传递参数的时候有一个特点,只会使用栈传参,且从右往左入栈,不会使用通用寄存器(另外还有其他类型的函数,如__fastcall约定的函数,其传递参数的方式都不相同,约定也都不一样,大家可自行学习):
- 加密函数sub_4021E0ida表面上似乎解释错了,人家有三个接受输入,但是ida只传了两个(size和char a3),但是这个函数的this指针好像是个全局变量,查看调用这个函数的汇编代码个月发现其使用栈只传递了两个参数,this指针这参数根本没给,所以它可能是一个全局变量(所以才不用传参,直接默认了相当于),往全局变量里面放加密后的flag,那当然就不用额外再在程序中定义临时变量: