历年真题刷题小记

是收录在攻防世界真题区的刷题记录,在网上找到的wp大多是各战队比赛时的简单思路,没有很具体的做法,所以想着写个刷题小记当伪wp,顺便也想趁机给自己总结和记录一下。

题目指路:https://adworld.xctf.org.cn/competition

刷题范围:2019年以来的比赛中>=20解的逆向题,循序渐进吧。

P.S. 起步tcl,会有参考其他wp的思路,什么时候没有参考wp了就说明我终于能单刷了(yep!

[2019 *CTF] fanoGo

题目描述

Do you kown go & fano encode?

1
$ nc 34.92.37.22 10001

wp

相关工具/插件:[IDA] IDAGolangHelper

用了插件以后,主函数一般是main_main,所以找到主函数。

然后看到一行fano___Fano__Decode()

于是猜测是让我们输入数据,经过fanoDecode解码后等于该字符串(就是说输入数据=该字符串的fano编码)。

(我摊牌,Go实在看不懂,这里参考wp的思路了。不过如果实在做不出来我可能也会这么猜(?

然后在函数栏搜索能看到,打包的时候把fanoEncode函数也装进来了,所以想到patch call直接复用。

然后patch。

这里整理一下x86的call机器码格式(x64向下兼容:

  • E8 xxxxxxxx

    xxxxxxxx是偏移地址

    偏移地址=目标地址-当前地址-5(取完当前指令之后,pc先自增,程序再跳转;E8 xxxxxxxx有5字节

  • FF 15 [xxxxxxxx]

    xxxxxxxx是绝对地址,FF15会对当前的这个绝对地址解*号,也就是绝对地址[目标地址]

原来的:

先找到fanoEncode的地址:

可以看到fanoEncode的地址为0x45C970。

然后计算偏移地址:

patch(别忘了小端序):

patch完以后愉快地开始动态调试,配置完Debugger以后,为了看加密结果在fanoEncode函数的这里下断点。

然后动态调试

断点处打开Locals窗口(Debugger->Debugger windows->Locals)查看编码结果:

v3这里是结果的地址,我们复制下来并在IDA view里按G跳转

把这个数组dump下来就是要输入的数据。

最后的exp:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python
# ------ Python2 ------
from pwn import *

# context.log_level='debug'
r=process("./fanoGo_ori")

arr=[0x2B, 0x60, 0xC3, 0xBE, 0xC2, 0xB7, 0xC2, 0x82, 0xC2, 0x89, 0xC3, 0x95, 0x5B, 0xC2, 0x87, 0x2A, 0x69, 0x13, 0xC2, 0x96, 0x51, 0xC3, 0xBD, 0x6F, 0x32, 0x28, 0x5A, 0xC3, 0x92, 0x74, 0xC2, 0x94, 0xC2, 0x94, 0xC2, 0x95, 0xC2, 0x96, 0xC2, 0xA4, 0xC3, 0x8A, 0xC2, 0xA3, 0xC3, 0x8E, 0xC2, 0xB3, 0x24, 0x24, 0x24, 0xC2, 0xBA, 0xC2, 0xAE, 0x46, 0x2B, 0xC2, 0xAC, 0x3C, 0xC3, 0xAB, 0x32, 0x23, 0x2A, 0xC3, 0xB0, 0xC3, 0xB3, 0xC2, 0xAC, 0xC3, 0x85, 0xC2, 0x87, 0x2C, 0xC2, 0xA3, 0x6B, 0xC2, 0xAD, 0x0F, 0xC3, 0x87, 0x5C, 0xC2, 0xA8, 0xC3, 0xB3, 0xC2, 0xAF, 0xC3, 0xA1, 0xC3, 0xB9, 0x12, 0xC3, 0x8A, 0x44, 0x72, 0xC2, 0xA6, 0xC2, 0x91, 0x66, 0x6D, 0x31, 0xC3, 0xA7, 0x51, 0x64, 0x67, 0x78, 0x75, 0x6B, 0xC2, 0x96, 0xC2, 0x91, 0x51, 0xC3, 0xA7, 0x3E, 0x13, 0xC3, 0x8E, 0x57, 0x7B, 0x47, 0xC2, 0x9D, 0x45, 0x7F, 0x29, 0x11, 0xC3, 0x95, 0xC3, 0xA1, 0xC3, 0xA7, 0x59, 0xC2, 0x8A, 0x06, 0xC2, 0x8C, 0xC2, 0x91, 0xC2, 0xB5, 0x0F, 0x3A, 0xC2, 0x8E, 0xC2, 0xBA, 0xC3, 0x8B, 0xC3, 0xAA, 0xC3, 0xA8, 0xC3, 0xBC, 0xC2, 0x8E, 0x71, 0xC3, 0xBD, 0x6F, 0x32, 0x36, 0xC3, 0xB9, 0x42, 0xC3, 0xA7, 0x49, 0xC3, 0x92, 0x22, 0x79, 0xC3, 0x89, 0xC3, 0x93, 0x54, 0x79, 0xC3, 0x96, 0x63, 0x6A, 0x1F, 0xC3, 0x96, 0xC3, 0xB3, 0x23, 0x6F, 0xC2, 0x94, 0x37, 0xC2, 0x94, 0xC3, 0xA8, 0x76, 0xC3, 0x83, 0xC3, 0x8E, 0x7C, 0x3F, 0xC2, 0xAD, 0xC3, 0xA0, 0xC2, 0x9F, 0x0C, 0xC2, 0xAA, 0x7B, 0xC3, 0x83, 0x26, 0xC2, 0xAD, 0xC3, 0xB0, 0x7E, 0x3A, 0xC3, 0xA5, 0x47, 0xC2, 0x9D, 0x7F, 0x09, 0xC3, 0xA5, 0x49, 0x44, 0xC2, 0xB0, 0xC2, 0xAF, 0x0F, 0x3A, 0xC3, 0x8C, 0x50, 0x51, 0xC3, 0xBD, 0x6F, 0x32, 0x2C, 0xC3, 0x8C, 0x2D, 0x27, 0x49, 0xC3, 0xA3, 0x2A, 0xC3, 0xB0, 0xC3, 0xB3, 0xC2, 0xAC, 0xC3, 0x88, 0xC2, 0x89, 0xC3, 0xB0, 0xC2, 0x9D, 0x7E, 0x1C, 0xC2, 0x9F, 0x29, 0x11, 0x41, 0x47, 0xC3, 0xB5, 0xC2, 0xBC, 0xC3, 0x88, 0xC2, 0x9A, 0x38, 0xC3, 0xB0, 0xC3, 0xA2, 0xC2, 0xB8, 0xC3, 0xA9, 0x15, 0xC3, 0x92, 0x50, 0x40]
payload=bytearray(arr[:0x136]) #看fano_Str2Bytes可以发现结果的长度为0x136
r.sendafter(':',payload)

r.interactive()

flag

因为要nc没拿到flag(

本地打通就好


[2019 RCTF] babyre1

题目描述

It’s just a baby…

There are multiple answers, please ensure MD5(rctf{your answer}) == 5f8243a662cf71bf31d2b2602638dc1d

wrap your answer in rctf{}

wp

主函数这里逻辑比较明显,flag长度限定到16,puts(v6)很容易可以猜测是题目里说的输出Bingo!

sub_C00()函数点进去能看到:

sub_BE0()实际上是取数组对应值,取ASCII码为48(ord('0'))到48+55-1=102(ord('f'))在byte_1620数组里对应的值,稍加分析就可以知道sub_C00()是unhexlify的作用。

然后为了方便后续分析,根据函数内容和参数给变量重命名。

接着往下走到sub_1180(),主要逻辑在sub_CE0()这里:

sub_CE0()的整个大逻辑大概是:

1
2
3
4
5
6
7
8
9
10
11
12
if ( a2 > 1 )
{
/* ...... */
return 0LL;
}
result = 1LL;
if ( a2 < -1 )
{
/* ...... */
return 0LL;
}
return result;

也就是说通过调用sub_CE0()时的第二个参数a2来确定函数走哪一部分的流程。

而这个a2是可以算出来的,即sub_1180()-(v10 >> 2)

也就是说v10=a2=8(这里a2是上一层函数传进来的hexLen,输入被限制在16字符,故hexLen一定为8),所以传进去的a2=-(v10>>2)=-(8>>2)=-2,直接走下面那层a2 < -1的逻辑。

其实这个sub_CE0()函数长得不就像XXTEA的加密/解密函数吗(长得这么别致的真的不多见= =。

关于TEA系列加解密:TEA、XTEA、XXTEA加密解密算法_gsls200808的专栏-CSDN博客

在上面链接摘取一下XXTEA的实现代码:

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
#include <stdio.h>
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

void btea(uint32_t *v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) /* Coding Part */
{
rounds = 6 + 52/n;
sum = 0;
z = v[n-1];
do
{
sum += DELTA;
e = (sum >> 2) & 3;
for (p=0; p<n-1; p++)
{
y = v[p+1];
z = v[p] += MX;
}
y = v[0];
z = v[n-1] += MX;
}
while (--rounds);
}
else if (n < -1) /* Decoding Part */
{
n = -n;
rounds = 6 + 52/n;
sum = rounds*DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p=n-1; p>0; p--)
{
z = v[p-1];
y = v[p] -= MX;
}
z = v[n-1];
y = v[0] -= MX;
sum -= DELTA;
}
while (--rounds);
}
}


int main()
{
uint32_t v[2]= {1,2};
uint32_t const k[4]= {2,2,3,4};
int n= 2; //n的绝对值表示v的长度,取正表示加密,取负表示解密
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
printf("加密前原始数据:%u %u\n",v[0],v[1]);
btea(v, n, k);
printf("加密后的数据:%u %u\n",v[0],v[1]);
btea(v, -n, k);
printf("解密后的数据:%u %u\n",v[0],v[1]);
return 0;
}

很明显地,Decoding Part跟sub_CE0()a2<-1部分基本是一模一样的,也就是我们要走的这个流程。

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
if ( a2 < -1 )
{
v24 = -a2;
v25 = 0x9E3779B9 * (52 / v24 + 6);
if ( v25 )
{
v26 = &a1[v24 - 1];
v27 = ~v4;
v44 = &a1[~v4];
v28 = -v4 - 3 - ((-v4 - 4) & 0xFFFFFFFE);
do
{
v29 = v25 >> 2;
if ( v27 <= 2 )
{
v31 = v27;
}
else
{
v30 = v44;
v31 = v27;
v32 = *v44;
do
{
v33 = *(v30 - 1);
v30 -= 2;
v34 = v32;
v32 = *v30;
v35 = ((v5 ^ v25) + (v33 ^ *(_DWORD *)(a3 + 4LL * (((unsigned __int8)v31 ^ (unsigned __int8)v29) & 3)))) ^ (((4 * v5) ^ (v33 >> 5)) + ((v5 >> 3) ^ (16 * v33)));
v36 = v31 - 1;
v31 -= 2;
v37 = v34 - v35;
v38 = *v30;
v30[2] = v37;
v5 = v33
- ((((16 * v38) ^ (v37 >> 3)) + ((v32 >> 5) ^ (4 * v37))) ^ ((v32 ^ *(_DWORD *)(a3
+ 4LL
* (((unsigned __int8)v29 ^ v36) & 3)))
+ (v25 ^ v37)));
v30[1] = v5;
}
while ( v28 != v31 );
}
v39 = &a1[v31];
do
{
v40 = *--v39;
v5 = v39[1]
- (((v5 ^ v25) + (v40 ^ *(_DWORD *)(a3 + 4LL * (((unsigned __int8)v29 ^ (unsigned __int8)v31) & 3)))) ^ (((v5 >> 3) ^ (16 * v40)) + ((v40 >> 5) ^ (4 * v5))));
v39[1] = v5;
--v31;
}
while ( v31 );
v41 = *a1
- (((((unsigned int)*v26 >> 5) ^ (4 * v5)) + ((16 * *v26) ^ (v5 >> 3))) ^ ((*(_DWORD *)(a3 + 4LL * (v29 & 3)) ^ *v26)
+ (v25 ^ v5)));
v42 = v25 == 0x9E3779B9;
v25 += 0x61C88647;
v5 = v41;
*a1 = v41;
}
while ( !v42 );
}
return 0LL;
}

所以可以确定这是个XXTEA的解密过程。

接着回到sub_1180(),可以看到XXTEA decrypt后还有一些操作:

最后一个byte的值即v11要<=0x04才能进入到这个关键return,并且把让v8[a5]=0x00截断,此时a5=8-v11。

同时这里出现的这个关键变量a5,就是传进来的第五个参数即主函数的v9,这个变量控制着主函数下面的输出:

前面题目也说了,"Input right flag you can got 'Bingo!' :",所以这里就是输出Bingo!的地方,长度不对等,有两种可能:

  • v9=6,按字节xor 0x17并输出前6字节。
  • v9>=7,第七字节xor 0x17后=0x00,即第七字节=0x17,把字符串在puts的时候截断。

无论如何,前六字节都已经确定了,分别是Bingo!每字节xor 0x17的值。

加密(解密)方法确定,接下来,我们只需要确定各参数里放的数据就行了。

从调用sub_CE0()处可以依次确定主函数里的unk_202010是XXTEA的key,v5存着解密后的字符串,v9是解密后字符串的长度。

于是原关键部分标记成:

sub_13D0()中,我们可以发现一个用得很频繁的常数——0x1021

通过常数和8轮的xor可以猜测是CRC的其中一种(计网刚学完hhh),在搜索引擎查一下0x1021这个常数也能知道是一种CRC-16:

看了一下算法对比感觉是CRC-16/X25(见CRC16常见几个标准的算法及C语言实现_leumber的专栏-CSDN博客),然而是什么其实都不重要,反正校验算法就意味着不能通过逆向计算来通过27106反推出字符串。

那么,汇总一下:现在相当于我们拿到了这个字符串前六字节(xor 0x17以后等于Bingo!),并且这个字符串通过CRC-16/X25计算后出27106,由题目描述知道MD5(rctf{your answer}) == 5f8243a662cf71bf31d2b2602638dc1d。而这个字符串是由一个十六字节的输入经过unhexlify以后再经过XXTEA的解密后得到的。

既然有校验存在(md5和crc),那我们就愉快地爆破吧。

理论上说应该是只用crc爆破的,但crc容易有多解,所以题目描述又多加了一层md5防止多解。

所以其实只用md5来爆破就好。

先用[hex(ord(x)^0x17) for x in 'Bingo!']算一下前六字节:

按小端组合,可以得到这两个解密出来的数字分别是0x70797e550xZZZZ3678(高两字节未知)。

于是写得爆破的exp:

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
// MD5.c来自https://github.com/pod32g/MD5
// 下载文件后删掉main函数的定义,和本exp放在同目录下即可食用
#include "MD5.c"
// 以下是XXTEA加解密部分,来自https://blog.csdn.net/gsls200808/article/details/48243019
#include <stdio.h>
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
void btea(uint32_t *v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) /* Coding Part */
{
rounds = 6 + 52/n;
sum = 0;
z = v[n-1];
do
{
sum += DELTA;
e = (sum >> 2) & 3;
for (p=0; p<n-1; p++)
{
y = v[p+1];
z = v[p] += MX;
}
y = v[0];
z = v[n-1] += MX;
}
while (--rounds);
}
else if (n < -1) /* Decoding Part */
{
n = -n;
rounds = 6 + 52/n;
sum = rounds*DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p=n-1; p>0; p--)
{
z = v[p-1];
y = v[p] -= MX;
}
z = v[n-1];
y = v[0] -= MX;
sum -= DELTA;
}
while (--rounds);
}
}
// 爆破部分
int main(){
uint32_t const k[4]={0xE0C7E0C7, 0xC6F1D3D7, 0xC6D3C6D3, 0xC4D0D2CE};
char hex[16]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
//这里unhex不能确定是大写十六进制还是小写十六进制,都爆破一遍就好
int n= 2;
for(int i=0;i<=4;i++){ //v11<=4
for(int j=0;j<0x100;j++){ //爆破第二字节
uint32_t v[2]={0x70797e55, 0x00003678|((i<<24)+(j<<16))};
btea(v, n, k);
char flag[23]="rctf{";
int pos=5;
for(int a=0;a<2;a++){
for(int b=0;b<4;b++){
flag[pos]=hex[*((uint8_t*)v+a*4+b)>>4];
flag[pos+1]=hex[*((uint8_t*)v+a*4+b)&0x0f];
pos+=2;
}
}
flag[pos]='}';
flag[pos+1]=0;
uint8_t res[16];
md5((uint8_t*)flag,strlen(flag),res);
char flag_md5[33]={0};
for(int x=0;x<16;x++) sprintf(flag_md5+2*x,"%02x",res[x]);
if(!strcmp(flag_md5,"5f8243a662cf71bf31d2b2602638dc1d")) printf("[*]flag: %s\n",flag);
}
}
return 0;
}

爆破就能得到flag啦~

flag

rctf{05e8a376e4e0446e}


[2019 RCTF] babyre2

  • 2019 RCTF / Reverse / 28 solved
  • Completed:
  • 参考wp:
    • 有?

题目描述

yet another baby reverse for u…

nc 139.180.215.222 20000

nc 123.206.174.203 20000

wp

是一个变量巨多函数也居多的题。

分析过程不再赘述,要么比较简单要么跟前面挺像的,直接贴出最后改了名的分析结果好了:

其中有一个需要忽略的函数sub_564B320C6B90(),这个函数主要是用于输入输出用的,相当于一个VM的I/O机,可以不用管。

整个程序的流程是依次让我们输入accountpassworddata三个数据,如果经历了一系列加密以后通过了check,则输出flag。

flag


[2020 GACTF] EasyRe

  • 2020 GACTF / Reverse / 38 solved
  • Completed:2021.01.02
  • 参考wp:
    • 无(ohhhh!

题目描述

Layers of protection~

Overseas:https://drive.google.com/file/d/1TobAUm_innt7RoQ7ZI4q1CNUWKuH-Tsw/view?usp=sharing

wp

用ida打开发现main看起来没什么特别的,就是输入一个lld。

不过一个一个函数往下翻可以看到sub_8049065()这个,调用了一个mprotect函数(警觉

mprotect的作用一般是改某段内存的读/写/执行权限,并且下面sub_8048EA5()还用了一个函数(就是主函数中即将调用的函数)作为参数,这就要立刻想到smc了,sub_8048EA5()就是加密函数。

这边一看就感觉很复杂的样子,那就懒得逆了,直接动态调试看看过完加密以后sub_8048838()是什么样子。在这个函数下个断点,执行到的时候直接步入。

可以看到这边比较散乱,因为数据全部被处理过了,要看到反汇编和反编译代码就要首先把它们undefined,然后重新分析。

这个函数的起始地址是0x8048838,结束地址是0x8048DE1,算一下长度是0x8048DE1-0x8048838+1=1450。

然后用ida_bytes的del_items函数(7.4的api开始不再兼容6.x的api了,在7.4以前还能用idc.MakeUnkn,见Porting from IDAPython 6.x-7.3, to 7.4)或者选这个区域直接右键->Undefined把这部分undefined掉,再在0x8048838出按p创建函数。

可以看到这其实是个vm题,smc+vm真不愧是Layers of protection呢hhh。vm代码如下:

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
unsigned int __cdecl sub_8048838(_DWORD *a1)
{
_BYTE *v2; // [esp+18h] [ebp-20h]
unsigned int v3; // [esp+2Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
while ( 1 )
{
if ( *(_BYTE *)a1[8] == 113 )
{
a1[6] -= 4;
*(_DWORD *)a1[6] = *(_DWORD *)(a1[8] + 1);
a1[8] += 5;
}
if ( *(_BYTE *)a1[8] == 65 )
{
a1[1] += a1[2];
++a1[8];
}
if ( *(_BYTE *)a1[8] == 66 )
{
a1[1] -= a1[4];
++a1[8];
}
if ( *(_BYTE *)a1[8] == 67 )
{
a1[1] *= a1[3];
++a1[8];
}
if ( *(_BYTE *)a1[8] == 68 )
{
a1[1] /= a1[5];
++a1[8];
}
if ( *(_BYTE *)a1[8] == 0x80 )
{
a1[sub_80487EF(a1, 1)] = *(_DWORD *)(a1[8] + 2);
a1[8] += 6;
}
if ( *(_BYTE *)a1[8] == 119 )
{
a1[1] ^= a1[9];
++a1[8];
}
if ( *(_BYTE *)a1[8] == 83 )
{
putchar(*(char *)a1[3]);
a1[8] += 2;
}
if ( *(_BYTE *)a1[8] == 34 )
{
a1[1] >>= a1[2];
++a1[8];
}
if ( *(_BYTE *)a1[8] == 35 )
{
a1[1] <<= a1[2];
++a1[8];
}
if ( *(_BYTE *)a1[8] == 0x99 )
break;
if ( *(_BYTE *)a1[8] == 118 )
{
a1[3] = *(_DWORD *)a1[6];
*(_DWORD *)a1[6] = 0;
a1[6] += 4;
a1[8] += 5;
}
if ( *(_BYTE *)a1[8] == 84 )
{
v2 = (_BYTE *)a1[3];
*v2 = getchar();
a1[8] += 2;
}
if ( *(_BYTE *)a1[8] == 48 )
{
a1[1] |= a1[2];
++a1[8];
}
if ( *(_BYTE *)a1[8] == 49 )
{
a1[1] &= a1[2];
++a1[8];
}
if ( *(_BYTE *)a1[8] == 9 )
{
a1[1] = dword_804B28C;
++a1[8];
}
if ( *(_BYTE *)a1[8] == 16 )
{
a1[9] = a1[1];
++a1[8];
}
if ( *(_BYTE *)a1[8] == 17 )
{
printf("%p\n", (const void *)a1[1]);
++a1[8];
}
if ( *(_BYTE *)a1[8] == 0xA0 )
{
if ( a1[1] != 653840640 )
exit(0);
++a1[8];
}
if ( *(_BYTE *)a1[8] == 0xA1 )
{
printf("flag:");
read(0, byte_804B2E0, 0x28u);
if ( strlen(byte_804B2E0) != 33 )
exit(0);
++a1[8];
}
if ( *(_BYTE *)a1[8] == 0xB1 )
{
a1[9] = dword_804B2A0[0];
++a1[8];
}
if ( *(_BYTE *)a1[8] == 0xB2 )
{
a1[9] = dword_804B2A4;
++a1[8];
}
if ( *(_BYTE *)a1[8] == 0xA4 )
{
dword_804B2A0[*(unsigned __int8 *)(a1[8] + 1)] = a1[1];
a1[8] += 4;
}
if ( *(_BYTE *)a1[8] == 0xB3 )
{
a1[9] = dword_804B2A8;
++a1[8];
}
if ( *(_BYTE *)a1[8] == 0xB4 )
{
a1[9] = dword_804B2AC;
++a1[8];
}
if ( *(_BYTE *)a1[8] == 0xC1 )
{
a1[1] = (unsigned __int8)byte_804B2E0[*(unsigned __int8 *)(a1[8] + 1)];
a1[8] += 2;
}
if ( *(_BYTE *)a1[8] == 0xC2 )
{
if ( a1[1] != *(_DWORD *)(a1[8] + 1) )
exit(0);
a1[8] += 5;
}
}
return __readgsdword(0x14u) ^ v3;
}

仔细研究可以知道,a1这个数组里,a1[1]~a1[4]和a1[9]是一组寄存器,a1[8]指向的地方实际上是opcode(a1[8]其实就是PC),dword_804B28C则是刚刚输入的数据(myInput),byte_804B2E0就是输入的flag。

整理可以得到以下伪代码:

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
//arr->4bytes
0x09 //r1=myInput
0x10 //r9=r1
0x22 //r1>>=r2
0x23 //r1<<=r2
0x30 //r1|=r2
0x31 //r1&=r2
0x41 //r1+=r2
0x42 //r1-=r4
0x43 //r1*=r3
0x44 //r1/=r5
0x71 aa bb cc dd //push ddccbbaa
0x76 ?? ?? ?? ?? //pop r3
0x77 //r1^=r9
0x80 rx aa bb cc dd //rx=ddbbccaa
0x99 //exit
0xA0 //check1(r1==653840640)
0xA1 //check2(input flag;strlen(flag)==33)
0xA4 aa bb cc //arr[ccbbaa]=r1
0xB1 //r9=arr[0]
0xB2 //r9=arr[1]
0xB3 //r9=arr[2]
0xB4 //r9=arr[3]
0xC1 aa //r1=flag[aa]
0xC2 aa bb cc dd //check3(r1==ddbbccaa)

/* 这三个有可能是提供给我们hook用的,但是懒得想了= =总之原opcode全程没用到
if ( *(_BYTE *)a1[8] == 0x53 ){
putchar(*(char *)a1[3]);
a1[8] += 2;
}
if ( *(_BYTE *)a1[8] == 0x54 ){
v2 = (_BYTE *)a1[3];
*v2 = getchar();
a1[8] += 2;
}
if ( *(_BYTE *)a1[8] == 0x11 ){
printf("%p\n", (const void *)a1[1]);
++a1[8];
}
*/

然后去a1[8]的地方拿到opcode,反汇编。

把opcode即unk_804B080处的数据dump下来,convert成python样式的byte数组,写反汇编脚本:

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
92
opcode=[0x09, 0x10, 0x80, 0x02, 0x0D, 0x00, 0x00, 0x00, 0x22, 0x77, 0x10, 0x80, 0x02, 0x09, 0x00, 0x00, 0x00, 0x23, 0x80, 0x02, 0x00, 0x96, 0xF3, 0x78, 0x31, 0x77, 0x10, 0x80, 0x02, 0x11, 0x00, 0x00, 0x00, 0x23, 0x80, 0x02, 0x00, 0x00, 0xD4, 0x85, 0x31, 0x77, 0x10, 0x80, 0x02, 0x13, 0x00, 0x00, 0x00, 0x22, 0x77, 0xA0, 0x09, 0x80, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x31, 0x80, 0x03, 0x02, 0x00, 0x00, 0x00, 0x43, 0x80, 0x02, 0x18, 0x00, 0x00, 0x00, 0x41, 0xA4, 0x00, 0x00, 0x00, 0x09, 0x80, 0x02, 0x08, 0x00, 0x00, 0x00, 0x22, 0x80, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x31, 0x80, 0x05, 0x07, 0x00, 0x00, 0x00, 0x44, 0x80, 0x02, 0x21, 0x00, 0x00, 0x00, 0x41, 0xA4, 0x01, 0x00, 0x00, 0x09, 0x80, 0x02, 0x10, 0x00, 0x00, 0x00, 0x22, 0x80, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x31, 0x80, 0x09, 0xBB, 0x00, 0x00, 0x00, 0x77, 0x80, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x41, 0xA4, 0x02, 0x00, 0x00, 0x09, 0x80, 0x02, 0x18, 0x00, 0x00, 0x00, 0x22, 0x80, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x31, 0x80, 0x04, 0xA0, 0x00, 0x00, 0x00, 0x42, 0x80, 0x02, 0x77, 0x00, 0x00, 0x00, 0x41, 0xA4, 0x03, 0x00, 0x00, 0xA1, 0xC1, 0x00, 0xB1, 0x77, 0xC2, 0x0B, 0x01, 0x00, 0x00, 0xC1, 0x01, 0xB2, 0x77, 0xC2, 0x7A, 0x00, 0x00, 0x00, 0xC1, 0x02, 0xB4, 0x77, 0xC2, 0x95, 0x00, 0x00, 0x00, 0xC1, 0x03, 0xB3, 0x77, 0xC2, 0x06, 0x01, 0x00, 0x00, 0xC1, 0x04, 0xB2, 0x77, 0xC2, 0x7D, 0x00, 0x00, 0x00, 0xC1, 0x05, 0xB4, 0x77, 0xC2, 0xAD, 0x00, 0x00, 0x00, 0xC1, 0x06, 0xB1, 0x77, 0xC2, 0x2F, 0x01, 0x00, 0x00, 0xC1, 0x07, 0xB3, 0x77, 0xC2, 0x65, 0x01, 0x00, 0x00, 0xC1, 0x08, 0xB1, 0x77, 0xC2, 0x2D, 0x01, 0x00, 0x00, 0xC1, 0x09, 0xB1, 0x77, 0xC2, 0x2F, 0x01, 0x00, 0x00, 0xC1, 0x0A, 0xB3, 0x77, 0xC2, 0x39, 0x01, 0x00, 0x00, 0xC1, 0x0B, 0xB3, 0x77, 0xC2, 0x0D, 0x01, 0x00, 0x00, 0xC1, 0x0C, 0xB4, 0x77, 0xC2, 0xBB, 0x00, 0x00, 0x00, 0xC1, 0x0D, 0xB2, 0x77, 0xC2, 0x08, 0x00, 0x00, 0x00, 0xC1, 0x0E, 0xB3, 0x77, 0xC2, 0x0D, 0x01, 0x00, 0x00, 0xC1, 0x0F, 0xB1, 0x77, 0xC2, 0x3F, 0x01, 0x00, 0x00, 0xC1, 0x10, 0xB3, 0x77, 0xC2, 0x3A, 0x01, 0x00, 0x00, 0xC1, 0x11, 0xB3, 0x77, 0xC2, 0x61, 0x01, 0x00, 0x00, 0xC1, 0x12, 0xB2, 0x77, 0xC2, 0x57, 0x00, 0x00, 0x00, 0xC1, 0x13, 0xB1, 0x77, 0xC2, 0x20, 0x01, 0x00, 0x00, 0xC1, 0x14, 0xB3, 0x77, 0xC2, 0x0D, 0x01, 0x00, 0x00, 0xC1, 0x15, 0xB1, 0x77, 0xC2, 0x3F, 0x01, 0x00, 0x00, 0xC1, 0x16, 0xB3, 0x77, 0xC2, 0x3F, 0x01, 0x00, 0x00, 0xC1, 0x17, 0xB4, 0x77, 0xC2, 0xB5, 0x00, 0x00, 0x00, 0xC1, 0x18, 0xB1, 0x77, 0xC2, 0x13, 0x01, 0x00, 0x00, 0xC1, 0x19, 0xB4, 0x77, 0xC2, 0xA0, 0x00, 0x00, 0x00, 0xC1, 0x1A, 0xB1, 0x77, 0xC2, 0x21, 0x01, 0x00, 0x00, 0xC1, 0x1B, 0xB3, 0x77, 0xC2, 0x0D, 0x01, 0x00, 0x00, 0xC1, 0x1C, 0xB2, 0x77, 0xC2, 0x0B, 0x00, 0x00, 0x00, 0xC1, 0x1D, 0xB3, 0x77, 0xC2, 0x39, 0x01, 0x00, 0x00, 0xC1, 0x1E, 0xB1, 0x77, 0xC2, 0x73, 0x01, 0x00, 0x00, 0xC1, 0x1F, 0xB2, 0x77, 0xC2, 0x46, 0x00, 0x00, 0x00, 0x99]
i=0
while True:
if opcode[i]==0x09:
print("r1=myInput")
i+=1
elif opcode[i]==0x10:
print("r9=r1")
i+=1
elif opcode[i]==0x22:
print("r1>>=r2")
i+=1
elif opcode[i]==0x23:
print("r1<<=r2")
i+=1
elif opcode[i]==0x30:
print("r1|=r2")
i+=1
elif opcode[i]==0x31:
print("r1&=r2")
i+=1
elif opcode[i]==0x41:
print("r1+=r2")
i+=1
elif opcode[i]==0x42:
print("r1-=r4")
i+=1
elif opcode[i]==0x43:
print("r1*=r3")
i+=1
elif opcode[i]==0x44:
print("r1/=r5")
i+=1
elif opcode[i]==0x71:
idata=(opcode[i+4]<<24)+(opcode[i+3]<<16)+(opcode[i+2]<<8)+(opcode[i+1])
print("push 0x%x"%idata)
i+=5
elif opcode[i]==0x76:
idata=(opcode[i+4]<<24)+(opcode[i+3]<<16)+(opcode[i+2]<<8)+(opcode[i+1])
print("pop r3 (unknown 0x%x)"%idata)
i+=5
elif opcode[i]==0x77:
print("r1^=r9")
i+=1
elif opcode[i]==0x80:
idata=(opcode[i+5]<<24)+(opcode[i+4]<<16)+(opcode[i+3]<<8)+(opcode[i+2])
print("r%d=0x%x"%(opcode[i+1],idata))
i+=6
elif opcode[i]==0x99:
print("exit")
break
elif opcode[i]==0xA0:
print("check1(r1==653840640)")
i+=1
elif opcode[i]==0xA1:
print("check2(input flag;strlen(flag)==33)")
i+=1
elif opcode[i]==0xA4:
reg=(opcode[i+3]<<16)+(opcode[i+2]<<8)+(opcode[i+1])
print("arr[%d]=r1"%reg)
i+=4
elif opcode[i]==0xB1:
print("r9=arr[0]")
i+=1
elif opcode[i]==0xB2:
print("r9=arr[1]")
i+=1
elif opcode[i]==0xB3:
print("r9=arr[2]")
i+=1
elif opcode[i]==0xB4:
print("r9=arr[3]")
i+=1
elif opcode[i]==0xC1:
print("r1=flag[%d]"%opcode[i+1])
i+=2
elif opcode[i]==0xC2:
idata=(opcode[i+4]<<24)+(opcode[i+3]<<16)+(opcode[i+2]<<8)+(opcode[i+1])
print("check3(r1==0x%x)"%idata)
i+=5
elif opcode[i]==0x53:
print("[Unkn] 0x53: %x"%opcode[i+1])
i+=2
elif opcode[i]==0x54:
print("[Unkn] 0x54: %x"%opcode[i+1])
i+=2
elif opcode[i]==0x11:
print("[Unkn] 0x11")
i+=1
else:
print("[!]unknown opcode %d"%opcode[i])
break

得到:

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
r1=myInput
r9=r1
r2=0xd
r1>>=r2
r1^=r9
r9=r1
r2=0x9
r1<<=r2
r2=0x78f39600
r1&=r2
r1^=r9
r9=r1
r2=0x11
r1<<=r2
r2=0x85d40000
r1&=r2
r1^=r9
r9=r1
r2=0x13
r1>>=r2
r1^=r9
check1(r1==653840640)
# 可以看到check1只调用了一次,并且这里是用来check最开始输入的数字的,因此以这里为界把分析分成了两部分,以下则是check再次输入的flag部分

r1=myInput
r2=0xff
r1&=r2
r3=0x2
r1*=r3
r2=0x18
r1+=r2
arr[0]=r1
r1=myInput
r2=0x8
r1>>=r2
r2=0xff
r1&=r2
r5=0x7
r1/=r5
r2=0x21
r1+=r2
arr[1]=r1
r1=myInput
r2=0x10
r1>>=r2
r2=0xff
r1&=r2
r9=0xbb
r1^=r9
r2=0xff
r1+=r2
arr[2]=r1
r1=myInput
r2=0x18
r1>>=r2
r2=0xff
r1&=r2
r4=0xa0
r1-=r4
r2=0x77
r1+=r2
arr[3]=r1
check2(input flag;strlen(flag)==33)
r1=flag[0]
r9=arr[0]
r1^=r9
check3(r1==0x10b)
r1=flag[1]
r9=arr[1]
r1^=r9
check3(r1==0x7a)
r1=flag[2]
r9=arr[3]
r1^=r9
check3(r1==0x95)
r1=flag[3]
r9=arr[2]
r1^=r9
check3(r1==0x106)
r1=flag[4]
r9=arr[1]
r1^=r9
check3(r1==0x7d)
r1=flag[5]
r9=arr[3]
r1^=r9
check3(r1==0xad)
r1=flag[6]
r9=arr[0]
r1^=r9
check3(r1==0x12f)
r1=flag[7]
r9=arr[2]
r1^=r9
check3(r1==0x165)
r1=flag[8]
r9=arr[0]
r1^=r9
check3(r1==0x12d)
r1=flag[9]
r9=arr[0]
r1^=r9
check3(r1==0x12f)
r1=flag[10]
r9=arr[2]
r1^=r9
check3(r1==0x139)
r1=flag[11]
r9=arr[2]
r1^=r9
check3(r1==0x10d)
r1=flag[12]
r9=arr[3]
r1^=r9
check3(r1==0xbb)
r1=flag[13]
r9=arr[1]
r1^=r9
check3(r1==0x8)
r1=flag[14]
r9=arr[2]
r1^=r9
check3(r1==0x10d)
r1=flag[15]
r9=arr[0]
r1^=r9
check3(r1==0x13f)
r1=flag[16]
r9=arr[2]
r1^=r9
check3(r1==0x13a)
r1=flag[17]
r9=arr[2]
r1^=r9
check3(r1==0x161)
r1=flag[18]
r9=arr[1]
r1^=r9
check3(r1==0x57)
r1=flag[19]
r9=arr[0]
r1^=r9
check3(r1==0x120)
r1=flag[20]
r9=arr[2]
r1^=r9
check3(r1==0x10d)
r1=flag[21]
r9=arr[0]
r1^=r9
check3(r1==0x13f)
r1=flag[22]
r9=arr[2]
r1^=r9
check3(r1==0x13f)
r1=flag[23]
r9=arr[3]
r1^=r9
check3(r1==0xb5)
r1=flag[24]
r9=arr[0]
r1^=r9
check3(r1==0x113)
r1=flag[25]
r9=arr[3]
r1^=r9
check3(r1==0xa0)
r1=flag[26]
r9=arr[0]
r1^=r9
check3(r1==0x121)
r1=flag[27]
r9=arr[2]
r1^=r9
check3(r1==0x10d)
r1=flag[28]
r9=arr[1]
r1^=r9
check3(r1==0xb)
r1=flag[29]
r9=arr[2]
r1^=r9
check3(r1==0x139)
r1=flag[30]
r9=arr[0]
r1^=r9
check3(r1==0x173)
r1=flag[31]
r9=arr[1]
r1^=r9
check3(r1==0x46)
exit

根据上半部分的分析写出爆破脚本把这个数字爆破出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <cmath>
using namespace std;
int main(){
for(unsigned long long i=0;i<pow(2,128);i++){
unsigned long long x=i;
if(((((((((((x>>0xd)^x)<<0x9)&0x78f39600)^(x>>0xd)^x)<<0x11)&0x85d40000)^((((x>>0xd)^x)<<0x9)&0x78f39600)^(x>>0xd)^x)>>0x13)^((((((((x>>0xd)^x)<<0x9)&0x78f39600)^(x>>0xd)^x)<<0x11)&0x85d40000)^((((x>>0xd)^x)<<0x9)&0x78f39600)^(x>>0xd)^x))==653840640){
cout<<i<<endl;
break;
}
}
}
// 其实有很多地方不必加括号的,这里为了保险就全加了
// output:4293442714

可以爆出这个数是4293442714,输入,发现果然出现了输入flag的提示:

接下来是flag的部分,逻辑很清楚啦,就是用这个数字生成一个arr数组,然后用flag[i] xor arr中的某一个等于已知值,基操xor不用多说。

这里为了更快处理就直接把check2下边的部分复制到code.txt中了,直接进行自动化计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# x=4293442714
# arr=[0,0,0,0]
# arr[0]=(x&0xff)*2+0x18
# arr[1]=((x>>0x8)&0xff)//0x7+0x21
# arr[2]=(((x>>0x10)&0xff)^0xbb)+0xff
# arr[3]=(((x>>0x18)&0xff)-0xa0)+0x77
# print(arr)
arr=[332,59,338,214]
with open("code.txt","r") as f:
for i in range(32*4):
line=f.readline().replace("\n","")
if i%4==1:
s=line[3:]+"^"
if i%4==3:
s+=line[11:-1]
print(chr(eval(s)),end='')

flag就出来啦~

flag

GACTF{c7ack_m3_sh3ll_smc_vm_0k?}


TBC…

本文作者: c10udlnk
本文链接: https://c10udlnk.top/p/logFor-RealProbs-in-adworld-XCTF/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 c10udlnk_Log (https://c10udlnk.top)!