0x00 前言
游戏题,主要学学游戏题应该怎么做,以前碰到游戏题都在misc或者压根没做出来(然后没WP)
拿到这题是懵的,没有符号就算了,压根不知道该干什么
兜兜转转搜到了源码PacManX,本来想着有源码自行编译之后拿去BinDiff一下,但是自行编译很多环境是不一样的,所以不行
通过搜索字符串,查找一局游戏结束时的操作,发现也没有特别的,这时候就卡住了
后面查看WP发现原来是放在了读文件的操作上,看到游戏确实有给多四个文件,看来以后做游戏题还得注意文件读写这块(毕竟有存档之类的,这些东西也是可以藏数据的)
0x01 解
无壳,64位,直接上IDA
想直接动调,但是有反调试
通过在import表搜索exit可以找到不少相关调用,一个个看,最后发现在高亮处这个地方很怪
F5一下可以看到对两个字符串进行了判断
这边这个反调试是这样的:
1.用函数CreateToolhelp32Snapshot
,参数dwFlags=2
,th32ProcessID=0
,返回值给到变量hSnapshot
2.创建一个PROCESSENTRY32W结构的变量pe
,其中dwSize=568
3.调用函数Process32First,传入hSnapshot
和pe
4.一个循环不断判断pe.th32ProcessID
是否是CurrentProcessId
,不是则调用Process32NextW,获取下一个进程ID
当判断成立,则令变量th32ProcessID = pe.th32ParentProcessID
再次调用CreateToolhelp32Snapshot
获取快照,不过这一次参数dwFlags=2
,th32ProcessID=pe.th32ParentProcessID
(挺怪的,dwFlags=2
的情况下后面的参数实际上没有用的)
5.又是一个循环,与第4步流程差不多,不过循环要找th32ProcessID
的变成了创建当前程序的父进程的ID,返回结构体PROCESSENTRY32W
6.找到后关闭Handler
,并且取结构体PROCESSENTRY32W
中的szExeFile
,之后经过一系列转换(函数wcslen
和WideCharToMultiByte
之类的字符串转换),最终得到当前程序父进程的名字,如果父进程名应为explorer.exe
或者powershell.exe
则正常,否则退出(这边我觉得挺离谱的,看不起我CMD是吗)
所以过反调试挺简单,最后的一个判断patch掉就行(tab看汇编,jz改为jnz)
(反反调试应该有要研究这个的,继续挖坑)
反调试看完继续看imports表
从读写文件下手,即针对函数fread
和fwrite
下手
第一个位置就很可疑,有一些读注册表的操作
需要注意的是函数sub_7FF6F0EF419A
跟进去是一个解密函数,参数是加密的东西,解密过程比较简单
1 2 3 4 5 6 7 8 9
| const char *__fastcall sub_7FF6F0EFF3E0(const char *a1) { int i;
sub_7FF6F0EF4FFF((__int64)&unk_7FF6F0F3A102); for ( i = 0; i < j_strlen(a1); ++i ) a1[i] = (a1[i] ^ 0x11) - 34; return a1; }
|
根据这个可以找到各字符串的含义
1 2 3 4 5 6 7 8 9
| unk_7FF6F0F2E538 = 'Software\PacManX' unk_7FF6F0F2E530 = 'Error' unk_7FF6F0F2E54C = 'MYFLAG' unk_7FF6F0F2E590 = 'game.data' OldFilename = 'game.tmp' unk_7FF6F0F2E580 = 'powerpowerpo' unk_7FF6F0F2E558 = 'powerpowerpowerpowerpowerpowerpo' FileName = 'game.ps1' unk_7FF6F0F2E5C0 = 'powershell.exe -File game.ps1'
|
根据这个可以大致知道会检查一个路径是HKEY_CURRENT_USER\Software\PacManX\MYFLAG
的注册表值(长为36Bytes),然后会有一个.ps1
文件的出现(从game.tmp改过来的)用于检查flag,这个文件是退出时才会出现,而且用完马上remove
那就新建个注册表值,然后下个断点让它出来
运行到这里能在同目录下找到一个.ps1
文件,不过混淆很严重
可以手动去,也可以用工具,这次试试用工具PowerDecode
用法很简单,会点英语就行,人家甚至提供GUI,我哭死
不过最后不会生成plainscript出来,要自己去log里面复制
解出来这个,好像有三层混淆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| function enenenenene { param( $plaintextBytes, $keyBytes ) $S = 0..255 $j = 0 for ($i = 0; $i -lt 256; $i++) { $j = ($j + $S[$i] + $keyBytes[$i % $keyBytes.Length]) % 256 $temp = $S[$i] $S[$i] = $S[$j] $S[$j] = $temp }
$i = 0 $j = 0 $ciphertextBytes = @() for ($k = 0; $k -lt $plaintextBytes.Length; $k++) { $i = ($i + 1) % 256 $j = ($j + $S[$i]) % 256 $temp = $S[$i] $S[$i] = $S[$j] $S[$j] = $temp $t = ($S[$i] + $S[$j]) % 256 $ciphertextBytes += ($plaintextBytes[$k] -bxor $S[$t]) }
return $ciphertextBytes } function enenenenene1 { param( $inputbyte ) $key = @(0x70, 0x6f, 0x77, 0x65, 0x72) $encryptedText = @(); for ($k = 0; $k -lt $inputbyte.Length; $k++) { $encryptedText = enenenenene -plaintextBytes $inputbyte -keyBytes $key; $key = enenenenene -plaintextBytes $key -keyBytes $encryptedText; } return $encryptedText + $key; } function enenenenene2 { param( $inputbyte ) $key = @(0x70, 0x30, 0x77, 0x65, 0x72) for ($k = 0; $k -lt $inputbyte.Length; $k++) { $inputbyte[$k] = $inputbyte[$k] + $key[$k % $key.Length] } return $inputbyte; } function enenenenene3 { param( $inputbyte ) $key = @(0x70, 0x30, 0x77, 0x33, 0x72) for ($k = 0; $k -lt $inputbyte.Length; $k++) { $inputbyte[$k] = $inputbyte[$k] * $key[$k % $key.Length] } return $inputbyte; } $registryPath = 'HKCU:\Software\PacManX'
$valueName = 'MYFLAG' $value = Get-ItemPropertyValue $registryPath $valueName $plaintext = @($value) | ForEach-Object { $input = $_ $plaintext = @() for ($i = 0; $i -lt $input.Length; $i++) { $plaintext += [int][char]$input[$i] } $plaintext } if ($plaintext.Length -ne 36) { Set-Content -Path "log.txt" -Value "ERROR" exit } $encrypted1Text = enENenenene2 -inputbyte (enenenENene2 -inputbyte (enenenenene3 -inputbyte (Enenenenene2 -inputbyte (enenenenene2 -inputbyte (enenenenene2 -inputbyte (enenenenene1 -input $plaintext)))))) $result = @(38304, 8928, 43673, 25957 , 67260, 47152, 16656, 62832 , 19480 , 66690, 40432, 15072 , 63427 , 28558 , 54606, 47712 , 18240 , 68187 , 18256, 63954 , 48384, 14784, 60690 , 21724 , 53238 , 64176 , 9888 , 54859 , 23050 , 58368 , 46032 , 15648 , 64260 , 17899 , 52782 , 51968 , 12336 , 69377 , 27844 , 43206 , 63616) for ($k = 0; $k -lt $result.Length; $k++) { if ($encrypted1Text[$k] -ne $result[$k]) { Set-Content -Path "log.txt" -Value "ERROR" exit
}
} Set-Content -Path "log.txt" -Value "RIGHT"
|
enenenenene
一眼RC4
其他的都就一些简单运算,直接解就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| from Crypto.Cipher import ARC4 enc1 = [38304, 8928, 43673, 25957 , 67260, 47152, 16656, 62832 , 19480 , 66690, 40432, 15072 , 63427 , 28558 , 54606, 47712 , 18240 , 68187 , 18256, 63954 , 48384, 14784, 60690 , 21724 , 53238 , 64176 , 9888 , 54859 , 23050 , 58368 , 46032 , 15648 , 64260 , 17899 , 52782 , 51968 , 12336 , 69377 , 27844 , 43206 , 63616] def de1(enc): key = enc[-5:] enc = enc[:-5] rc4 = ARC4.new(enc) key = rc4.decrypt(key) rc4 = ARC4.new(key) enc = rc4.decrypt(enc) print(enc) def de2(enc): key = [0x70, 0x30, 0x77, 0x65, 0x72] for i in range(len(enc)): enc[i] -= key[i%5] return enc
def de3(enc): key = [0x70, 0x30, 0x77, 0x33, 0x72] for i in range(len(enc)): enc[i] //= key[i%5] return enc
de1(bytes(de2(de2(de2(de3(de2(de2(enc1))))))))
|
这边有需要注意的是RC4的解密次数,虽然在原函数里面是循环加密了36次,但解密的时候只需要解密一次就行了(应该是有什么数学原因吧)