HFCTF2022_theShellcode
the_shellcode
用ida载入之后,显然这个地方被加壳了,这里是Themida壳
定位到OPE
这是是使用插件直接反反调试
sharpOD
安装sharpOD,设置其中的配置:主要时要勾上图中标注的那个选项(这个可以去程序中的反调试)
按f9就能够运行起程序,就能够运行起程序了
OPE定位并且下硬件断点
按E打开可执行模块窗口
可执行模块窗口:(快捷键:Alt+E)列出了当前被调试进程加载的所有可执行模块。它也显示了很多有用的信息,比如模块大小、入口地址、模块版本、以及可执行文件路径等。
打开该模块我们看到时一堆乱码,在这里我们需要去掉模块分析(汇编窗口,右键 -> 分析 -> 从模块中删除分析)
通过查找程序执行时输出的字符串 “please input the shellcode:” 定位到程序的正真入口的地方
右键->Search for -> All referenced text strings
通过上面的字符串表我们就到达了程序的入口处了 push ebp的位置,我们就直接在这个地方下断点(方便之后直接定位到程序的入口)
这个地方需要下硬件断点,这样定位到的才是这个代码的位置(因为重调之后这片数据又会成为未解压缩的状态),程序的入口的位置是 0x9111c0
再次运行程序就能够直接到达程序的入口点位置了
dump和修复IAT表
直接dump下来的程序中的函数不能简单明了的知道它的作用,修复之后的代码更易懂
直接dump程序
使用Ollydump就可以将程序dump下来
在内存映射窗口查看起始地址 为910000
设置起始地址和入口点地址,注意入口点位置是偏移地址
因为这个程序里面的输入表有修改,所以这个地方dump出来的程序是不能够运行的
将dump出来的这个程序放入ida之中查看,通过字符串定位到关键代码的位置
这里的符号表没有修复,所以程序里面的函数不知道其含义,作用
未修复的IAT表,已经修复的是能够直接知道函数的作用的,未修复的是直接用地址作为函数名的
可以看到未修复的IAT就是通过不断地跳转到达目标函数地位置,动态调试地方式跟一下看一下函数地执行过程
跟未修复的IAT表的过程
我们要跟地是未修复地,图中地第一个函数,可以知道它是已经修复地,第二个是未修复地,所以我们要跟进地是第二个
F7跟进之后可以看到又是一个跳转,没有到达目标地代码
又跟进入,又是一个跳转
又跟进去,在这个页面之中我们Ctrl+F9,到达ret的地方
查看它的堆栈,就看到它真正调用的函数名称
通过这样的特性,我们编写脚本进行修复
修复IAT表(脚本+Scylla)
去掉不需要修复的IAT:
- IAT已经到达结束位置
- IAT大于72C0 0000(说明IAT是正确指向)
- IAT表的值为0(就不需要修复了)
脚本:(从上面我能知道我的基址是0x910000)
1 | VAR OEP |
运行完之后我们就能看到IAT被修复了
现在就需要使用,[Scylla](输入表 (pediy.com))修复一下这个IAT表,再dump出来
用管理员权限打开Scylla x86,设置入口点,获得IAT表
得到输入表,并且删除破坏的表
先将程序 Dump,再对应dump下来的程序 Fix Dump
用ida打开这个修复的文件
再打开程序除去作者编译的时候去掉的符号表,其它的IAT就被修复好了
代码分析1
xxtea解密
xxtea加密,但是注意里面有变形
对比标准的xxtea的MX:MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
这里的变形成:MX (((z>>6^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
key值:116,111,114,97
xxtea对比的数据:
1 | 0x4b6b89a1,0x74c15453,0x4092a06e,0x429b0c07,0x40281e84,0x8b5b44c9,0x66feb37b,0x3c77a603,0x79c5892d, |
xxtea的key:
1 | 116,111,114,97 |
xxtea解密脚本:
1 |
|
xxtea解密之后的数据
1 | 0x6243e703,0x993831bb,0x925c2396,0x60925c81,0x5ca0925c,0xbd784193, |
循环右移
循环右移3位:(每8位数)
脚本:
1 | src = [0x03,0xe7,0x43,0x62,0xbb,0x31,0x38,0x99,0x96,0x23,0x5c,0x92,0x81,0x5c,0x92,0x60,0x5c,0x92,0xa0,0x5c,0x93,0x41,0x78,0xbd,0x52,0x31,0x99,0xff,0x99,0x06,0x65,0xe1,0x0b,0xe3,0x10,0x61,0x01,0x0e,0x7e,0x68,0x18,0xc7,0x17,0x87,0x92,0xba,0x5c,0x92,0x80,0x5c,0x12,0xe1,0x18,0x16,0x5c,0x02,0xc3,0x2c,0x06,0x78,0x24,0xf5,0x00,0x00,0x00,0x18,0x16,0x82,0x5c,0x42,0xc0,0x5c,0xc2,0x01,0x18,0xd6,0x1c,0xcf,0x00,0x78,0x24,0x4d,0x00,0x00,0x00,0x4a,0x5c,0xa1,0x5c,0x18,0x97,0x99,0xff,0x99,0x06,0x65,0x0e,0x7e,0x68,0x18,0xc7,0xd1,0x26,0xab,0xa7,0x18,0xe3,0x21,0x20,0xd9,0xe3,0x21,0x60,0xab,0xce,0x99,0xff,0x99,0x4e,0x1c,0x16,0x82,0x78,0xb5,0x20,0x50,0x0e,0x7e,0x68,0x18,0xc7,0x0a,0x1c,0xcf,0x70,0xab,0x8f,0x0e,0x7e,0x68,0xba,0x99,0xff,0x99,0x4e,0x5c,0xa2,0x21,0xe1,0x92,0x78,0xb5,0xe0,0x70,0xc5,0x3b,0x33,0x33,0x33,0xbf,0x5f,0x8e,0xd7,0x5c,0x16,0x0e,0x47,0xf8,0x18,0x16,0x6c,0x20,0x04,0x59,0xc6,0xd2,0x78,0xb5,0x20,0x50,0x59,0x1e,0x0e,0x7e,0x68,0x18,0xc7,0x0a,0x1c,0xcf,0x70,0xab,0xa6,0x0e,0x7e,0x68,0xd9,0xe1,0x21,0xa3,0xb0,0x43,0x29,0x9b,0x00,0x00,0x5c,0x26,0x43,0x73,0x7b,0x00,0x00,0xa2,0x82,0x5c,0xe2,0x21,0x42,0xff,0x9e,0x5f,0xa0,0x43,0x29,0x9b,0x00,0x00,0x5c,0x26,0x43,0xcb,0x2b,0x9b,0x00,0xa2,0x82,0x5c,0xe2,0x21,0x42,0xff,0x9e,0xc2,0xc2,0xc2,0xc2,0xc2,0xc2,0xc2,0xc2,0xc2,0x0b,0x1e,0xc2,0xfa,0xd2,0x5c,0x90,0x4f,0x58,0xff,0xff,0xff] |
1 | 0x60,0xfc,0x68,0x4c,0x77,0x26,0x07,0x33,0xd2,0x64,0x8b,0x52,0x30,0x8b,0x52,0x0c,0x8b,0x52,0x14,0x8b,0x72, |
base解密
这个地方的逆运算实际上是base系列的加密
每三个元素为一组,然后每次去6位的数,这个六位的数在表中的索引是什么
base的表:(注意数据的顺序)
1 | 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, |
分析代码逆向解密脚本:
1 | table = [ 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, |
向程序之中输入这串字符串就到达了下部分的输入flag
代码分析2
这个部分的函数并不能直接得到,它是将上部分输入的shellcode的base加密的结果写入内存之中,并将这部分数据解析为函数来运行
分配内存空间生成函数
下一部分按照我们输入的shellcode进行base加密(第一次加密)之后的元素,并且将加密的这个结果写入这个空间之中,通过xref这个写入的数组Src我们能够得到
所以写入的数据是:
1 | 0x60,0xfc,0x68,0x4c,0x77,0x26,0x07,0x33,0xd2,0x64,0x8b,0x52,0x30,0x8b,0x52,0x0c,0x8b,0x52,0x14,0x8b,0x72,0x28,0x0f,0xb7,0x4a,0x26,0x33,0xff,0x33,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0xc1,0xcf,0x0d,0x03,0xf8,0xe2,0xf0,0x52,0x57,0x8b,0x52,0x10,0x8b,0x42,0x3c,0x03,0xc2,0x8b,0x40,0x78,0x85,0xc0,0x0f,0x84,0xbe,0x00,0x00,0x00,0x03,0xc2,0x50,0x8b,0x48,0x18,0x8b,0x58,0x20,0x03,0xda,0x83,0xf9,0x00,0x0f,0x84,0xa9,0x00,0x00,0x00,0x49,0x8b,0x34,0x8b,0x03,0xf2,0x33,0xff,0x33,0xc0,0xac,0xc1,0xcf,0x0d,0x03,0xf8,0x3a,0xc4,0x75,0xf4,0x03,0x7c,0x24,0x04,0x3b,0x7c,0x24,0x0c,0x75,0xd9,0x33,0xff,0x33,0xc9,0x83,0xc2,0x50,0x0f,0xb6,0x04,0x0a,0xc1,0xcf,0x0d,0x03,0xf8,0x41,0x83,0xf9,0x0e,0x75,0xf1,0xc1,0xcf,0x0d,0x57,0x33,0xff,0x33,0xc9,0x8b,0x54,0x24,0x3c,0x52,0x0f,0xb6,0x1c,0x0e,0xb8,0x67,0x66,0x66,0x66,0xf7,0xeb,0xd1,0xfa,0x8b,0xc2,0xc1,0xe8,0x1f,0x03,0xc2,0x8d,0x04,0x80,0x2b,0xd8,0x5a,0x0f,0xb6,0x04,0x0a,0x2b,0xc3,0xc1,0xcf,0x0d,0x03,0xf8,0x41,0x83,0xf9,0x0e,0x75,0xd4,0xc1,0xcf,0x0d,0x3b,0x3c,0x24,0x74,0x16,0x68,0x25,0x73,0x00,0x00,0x8b,0xc4,0x68,0x6e,0x6f,0x00,0x00,0x54,0x50,0x8b,0x5c,0x24,0x48,0xff,0xd3,0xeb,0x14,0x68,0x25,0x73,0x00,0x00,0x8b,0xc4,0x68,0x79,0x65,0x73,0x00,0x54,0x50,0x8b,0x5c,0x24,0x48,0xff,0xd3,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x61,0xc3,0x58,0x5f,0x5a,0x8b,0x12,0xe9,0x0b,0xff,0xff,0xff |
利用010生成文件得到代码
将上面这些16进制数据放入010之中,保存为16进制文件,再用ida32打开文件,打开之后,将其声明一下函数,f5反编译,就得到
根据输入的flag参数定位到关键代码的位置,分析逻辑
flag和v17的输入进行相同轮数的操作,操作相似,只是flag中加入了v12的某项值
每轮flag和v17加密的结果都要相同,说明flag的每个数都要比v17的每个数大v12[j]%5,这样每轮之中flag减去v12[j]%5,运算结果就相等了
我们需要得到v17和v12这两个数组
动态调试得到数据
使用动态调试的方式得到输入v17和v12,调试程序到图中所示位置(call的那个函数就是程序运行中初始化得到的函数)
进入这个call的程序之中,对比我们之前反编译得到的该函数(ida),知道该函数就是我们要找到的这个函数,可以结合ida之中的反编译和汇编代码来分析代码,定位到我们需要的数据的位置
向下调试,根据循环和移位的特征运算来定位
通过特征代码 移位运算ror和com+jnz定位到v17
定位到v17的数据是: is program cannot be run in DOS mode(我们实际上只需要14位的数据)
向下走在下一个循环之中定位到v12 :LoadLibraryExA
解密得flag
动态调试的数据以及第二部分代码的分析,我们能够得到第二次输入的flag的数据,根据最后的提示,将第一部分的输入和第二部分的输入结合在一起然后md5散列就能够得到我们要的HFCTF{}
解密脚本:
1 | import hashlib |
最后将这两个结果拼接一下,并且再md5一下就能得到最后的结果了
HFCTF{2B794E95022F2FE46106C21BBF57A755}
RITCTF_SOUP
用ida打开,将对比的字符串,每两个数一组,写成数组的形式,作为密文
用动态调试的方法,得到RC4的密钥是 soup,上解密脚本解密
RC4的解密脚本:
1 | #include<stdio.h> |
提交的flag是 RS{BR0CC0L1_CH3DD@R}