REVERSE-PRACTICE-BUUCTF-19
- [RoarCTF2019]polyre
- [安洵杯 2019]game
- [SCTF2019]Strange apk
- [CFI-CTF 2018]IntroToPE
[RoarCTF2019]polyre
elf文件,无壳,用ida分析
main函数的结构,多重循环,是控制流平坦化,参考:利用符号执行去除控制流平坦化
装好angr,使用脚本deflat.py去除控制流平坦化,脚本取自:deflat
将去除控制流平坦化的文件attachment_recovered拖入ida中分析
main函数还是不好分析,有很多while和do while循环,实际上是虚假控制流,使用脚本去除虚假控制流,在ida的Script command执行
def patch_nop(start,end):for i in range(start,end):PatchByte(i, 0x90)def next_instr(addr):return addr+ItemSize(addr)st = 0x0000000000401117
end = 0x0000000000402144addr = st
while(addr<end):next = next_instr(addr)if "ds:dword_603054" in GetDisasm(addr):while(True):addr = nextnext = next_instr(addr)if "jnz" in GetDisasm(addr):dest = GetOperandValue(addr, 0)PatchByte(addr, 0xe9)PatchByte(addr+5, 0x90) offset = dest - (addr + 5)PatchDword(addr + 1, offset)print("patch bcf: 0x%x"%addr)addr = nextbreakelse:addr = next
去除虚假控制流之后的main函数
主要逻辑为
输入的字符每8个字节为1组,组成1个64位的signed int v4,对v4进行64次循环运算,如果v4为非负,则v4乘以2,相当于左移1位,最高位的0移出,最低位补零,v4必为偶数(如果左移一位后最高位为1,则v4变成了负数值),如果v4为负,则先左移1位,再与0xB0004B7679FA26B3异或,左移一位时,最高位的1被移出了(写逆运算脚本时需要加回来最高位的1),最低位补零,由于3的二进制为0011,异或后v4必为奇数
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{signed __int64 v4; // [rsp+1E0h] [rbp-110h]signed int j; // [rsp+1E8h] [rbp-108h]signed int i; // [rsp+1ECh] [rbp-104h]signed int k; // [rsp+1ECh] [rbp-104h]char s1[48]; // [rsp+1F0h] [rbp-100h]char input[60]; // [rsp+220h] [rbp-D0h]unsigned int v10; // [rsp+25Ch] [rbp-94h]char *v11; // [rsp+260h] [rbp-90h]int v12; // [rsp+26Ch] [rbp-84h]bool v13; // [rsp+272h] [rbp-7Eh]unsigned __int8 v14; // [rsp+273h] [rbp-7Dh]int v15; // [rsp+274h] [rbp-7Ch]char *v16; // [rsp+278h] [rbp-78h]int v17; // [rsp+284h] [rbp-6Ch]int v18; // [rsp+288h] [rbp-68h]bool v19; // [rsp+28Fh] [rbp-61h]char *v20; // [rsp+290h] [rbp-60h]int v21; // [rsp+298h] [rbp-58h]bool v22; // [rsp+29Fh] [rbp-51h]__int64 v23; // [rsp+2A0h] [rbp-50h]bool v24; // [rsp+2AFh] [rbp-41h]__int64 v25; // [rsp+2B0h] [rbp-40h]__int64 v26; // [rsp+2B8h] [rbp-38h]__int64 v27; // [rsp+2C0h] [rbp-30h]__int64 v28; // [rsp+2C8h] [rbp-28h]int v29; // [rsp+2D0h] [rbp-20h]int v30; // [rsp+2D4h] [rbp-1Ch]char *v31; // [rsp+2D8h] [rbp-18h]int v32; // [rsp+2E0h] [rbp-10h]int v33; // [rsp+2E4h] [rbp-Ch]bool v34; // [rsp+2EBh] [rbp-5h]v10 = 0;memset(input, 0, 0x30uLL);memset(s1, 0, 0x30uLL);printf("Input:", 0LL);v11 = input;__isoc99_scanf("%s", input);for ( i = 0; ; ++i ){v12 = i;v13 = i < 64;if ( i >= 64 )break;v14 = input[i];v15 = v14;if ( v14 == '\n' ) // 换行符'\n'改成字符串结束符'\0'{v16 = &input[i];*v16 = 0;break;}v17 = i + 1;}for ( j = 0; ; ++j ){v18 = j;v19 = j < 6;if ( j >= 6 ) // 循环6次break;v20 = input;v4 = *(_QWORD *)&input[8 * j]; // 输入的每8个字节一组,组成1个64位的v4,小端序for ( k = 0; ; ++k ){v21 = k;v22 = k < 64;if ( k >= 64 ) // 循环64次break;v23 = v4;v24 = v4 < 0;if ( v4 >= 0 ) // 如果v4非负,v4*=2,相当于左移一位,结果必为偶数,如果左移一位后最高位为1,则v4变成了负数{v27 = v4;v28 = 2 * v4;v4 *= 2LL;}else // 如果v4为负,v4乘2后再异或0xB0004B7679FA26B3,相当于先左移一位再异或,结果必为奇数,注意这时最高位的1被移出了{v25 = 2 * v4;v26 = 2 * v4;v4 = 2 * v4 ^ 0xB0004B7679FA26B3LL;}v29 = k;}v30 = 8 * j;v31 = &s1[8 * j]; // 每组完成64次循环后的值赋给s1*(_QWORD *)v31 = v4;v32 = j + 1;}v33 = memcmp(s1, &unk_402170, 48uLL); // s1和已知值比较,小端序v34 = v33 != 0;if ( v33 != 0 )puts("Wrong!");elseputs("Correct!");return v10;
}
写逆运算脚本即可得到flag
#coding:utf-8
arr=[0x96, 0x62, 0x53, 0x43, 0x6D, 0xF2, 0x8F, 0xBC, 0x16, 0xEE,0x30, 0x05, 0x78, 0x00, 0x01, 0x52, 0xEC, 0x08, 0x5F, 0x93,0xEA, 0xB5, 0xC0, 0x4D, 0x50, 0xF4, 0x53, 0xD8, 0xAF, 0x90,0x2B, 0x34, 0x81, 0x36, 0x2C, 0xAA, 0xBC, 0x0E, 0x25, 0x8B,0xE4, 0x8A, 0xC6, 0xA2, 0x81, 0x9F, 0x75, 0x55]
enc=[]
for i in range(6):#以小端序的方式读值tmp='0x'for j in range(7,-1,-1):tmp+=hex(arr[i*8+j]).replace('0x','').zfill(2)enc.append(tmp)
flag_data=[]
for i in range(len(enc)):tmp=int(enc[i],16)for j in range(64):#循环64次sign=tmp&1 #判断当前v4的符号 &1后为1即为奇数 为0即为偶数if sign: #奇数要异或回去tmp^=0xB0004B7679FA26B3tmp//=2 #无论正负都要右移一位回去if sign: #奇数右移一位后还要补回左移一位时移出的1tmp|=0x8000000000000000flag_data.append(hex(tmp).replace('0x','').replace('L',''))
flag_str=""
for i in range(6):#由于存储方式是小端序 所以需要从后往前的读tmp=flag_data[i]for j in range(len(tmp)-2,-1,-2):num=int('0x'+tmp[j:j+2],16)flag_str+=chr(num)
print(flag_str)
#flag{6ff29390-6c20-4c56-ba70-a95758e3d1f8}
[安洵杯 2019]game
elf文件,无壳,ida分析
main函数调用的许多函数都有控制流平坦化混淆,用defalt.py脚本一个一个去平坦化
main函数,获取输入,general_inspection函数利用sudoku做一些运算,不太明白作用,不过影响不大,trace函数完全填充sudoku,动调可得到完全填充的结果,check函数没什么作用,check1函数对输入进行变换,最后进入check3函数验证输入
进入check1函数,对input进行变换,首先input前半部分和后半部分交换,然后input两个一组,两两交换,最后再对input进行变换,input最后的变换结果均为数字字符
check3->check2函数,首先将变换后的input中的数字字符转成普通数字,存储到v11,然后v11顺序地填充到D0g3中元素为0的位置,未完全填充的D0g3也可通过动调得到,最后比较D0g3和sudoku
动调得到未完全填充的D0g3和已完全填充的sudoku后,写逆运算脚本即可得到flag
#coding:utf-8
sudoku=[
1,4,5,3,2,7,6,9,8,
8,3,9,6,5,4,1,2,7,
6,7,2,8,1,9,5,4,3,
4,9,6,1,8,5,3,7,2,
2,1,8,4,7,3,9,5,6,
7,5,3,2,9,6,4,8,1,
3,6,7,5,4,2,8,1,9,
9,8,4,7,6,1,2,3,5,
5,2,1,9,3,8,7,6,4]
D0g3=[
1,0,5,3,2,7,0,0,8,
8,0,9,0,5,0,0,2,0,
0,7,0,0,1,0,5,0,3,
4,9,0,1,0,0,3,0,0,
0,1,0,0,7,0,9,0,6,
7,0,3,2,9,0,4,8,0,
0,6,0,5,4,0,8,0,9,
0,0,4,0,0,1,0,3,0,
0,2,1,0,3,0,7,0,4]
arr=[]
for i in range(81):if D0g3[i]==0: #input填充到D0g3中元素为0的位置arr.append(sudoku[i])
for i in range(len(arr)):for j in range(32,128):#对input的第三次变换通过爆破来解if arr[i]+48==(j & 0xF3 | ~j & 0xC) - 20:arr[i]=j
for i in range(0,len(arr),2): #两个一组,两两交换arr[i],arr[i+1]=arr[i+1],arr[i]
arr[0:20],arr[20:40]=arr[20:40],arr[0:20]#input前半部分和后半部分交换
flag="".join(chr(i) for i in arr)
print(flag)
#KDEEIFGKIJ@AFGEJAEF@FDKADFGIJFA@FDE@JG@J
运行elf,输入,验证正确
[SCTF2019]Strange apk
apk文件,jadx-gui打开
主要逻辑在sctf.hello.c类中
data是apk自带的资源文件,在assets目录下可找到,先对data进行__方法处理,再进行_方法处理
__方法就是读取data文件的字节
_方法是对data文件的字节进行0方法的处理,再将结果写到另一个文件中
0方法,可知是对data文件读取到的字节与"syclover"做循环异或运算,结果写到另一个文件中
脚本:
f= open("D:\\ctfdownloadfiles\\data", "rb")
res= open("D:\\ctfdownloadfiles\\res", "wb")
data=f.read()
s="syclover"
res_data=""
count=0
for c in data:res_data+=chr(ord(c)^ord(s[count%len(s)]))count+=1
res.write(res_data)
res.close()
将输出的文件后缀名改为apk后,再用jadx-gui分析
先看sctf.demo.myapplication.s类,验证输入的长度是否为30,对输入的前12个字符做base64编码后与已知字符串比较,后18个字符赋给data_return
再看sctf.demo.myapplication.t类,key为"syclover"的md5的十六进制摘要,输入的后18个字符与key作为参数传入f.encode方法,返回的字符串与已知字符串比较
再看sctf.demo.myapplication.f类,str为输入的后18个字符,长度s为18,key为"syclover"的md5的十六进制摘要,为"8bfc8af07bca146c937f283b8ec768d4",长度c为32,for循环中,t先append一个输入字符,再append一个key中的字符,由于f<s<c,故t从key中append的字符总是第0个字符,即’8’,同时也知道返回字符串中某字符的下标为偶数时,其即为输入的字符
写脚本即可得到flag
import base64
s=base64.b64decode("c2N0ZntXM2xjMG1l")
res="~8t808_8A8n848r808i8d8-8w808r8l8d8}8"
for i in range(0,len(res),2):s+=res[i]
print(s)
#sctf{W3lc0me~t0_An4r0id-w0rld}
[CFI-CTF 2018]IntroToPE
exe程序,运行后输入password,点击验证
查壳发现时.Net程序,用dnSpy打开
来到ValidatePasswd.verifyPasswd()方法,简单的base64编码验证
解base64即可得到flag