0%

beginner

file文件

1
2
winter@winter-ubuntu16:~/googlectf/reverse-beginner$ file a.out 
a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e3a5d8dc3eee0e960c602b9b2207150c91dc9dff, for GNU/Linux 3.2.0, not strippd

ELF二进制文件,64位,x86-64架构,LSB说明是小端的(MSB是大端的)

执行文件

1
2
3
winter@winter-ubuntu16:~/googlectf/reverse-beginner$ ./a.out 
Flag: winter
FAILURE

一个输入点,判断对错

查看字符串

image-20200905153614666

给定的字符串里面有CTF{,很可能是flag的一部分

程序流程

image-20200905160629829

__isoc99_scanf("%15s", &v5);规定了最多输入15个字符,flag很有可能就是15个。

image-20200905160714290

simd指令

全称single instruction multiple data,即单指令多数据运算

其目的就在于帮助CPU实现数据并行,提高运算效率。

shuffle

选取源寄存器的任意字节重新排布到目的寄存器。

通俗来讲:就是将原来寄存器里面字节排列,按你的要求打乱顺序,最后将打乱顺序的存入目的寄存器

网上可以搜到它的伪代码描述

1
2
3
4
5
6
char a[16]; // input a
  char b[16]; // input b
  char r[16]; // output r

  for (i=0; i < 16; i++)
  r[i] = (b[i] < 0) ? 0 : a[b[i] % 16];

https://www.cnblogs.com/celerychen/archive/2013/03/29/2989254.html

image-20200905162233511

13 12 10 08 04 15 03 14 09 11 05 01 07 06 02

假设我们输入的值存放在a[16]数组里面

1
2
索引:  1     2     3     4     5     6     7     8     9     10     11     12     13     14     15 
目的:a[13] a[12] a[10] a[08] a[04] a[15] a[03] a[14] a[09] a[11] a[05] a[01] a[07] a[06] a[02]
add32

每32位(4个字节)做整形加法运算,但是进位不会从一个4字节的包传输到另一个:

image-20200905162639108

xor

做异或操作

image-20200905162718446

因为程序是小端的,所以实际要倒过来,可以通过右键array变过来。

image-20200906230824340

拼凑flag

假设前四个字符就是CTF{,我们根据以上规则进行实验。

1
shuffle = [2, 6, 7, 1, 5, 0xB, 9, 0xE, 3, 0xF, 4, 8, 0xA, 0xC,0xD, 0]

根据shuffle的规则,打乱顺序后的字符串如下

1
2
a[2] a[6] a[7] a[1] a[5] a[11] a[9] a[14] a[3] a[15] a[4] a[8] a[10] a[12] a[13] a[0]
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

所以,一开始我们知道第三个(也就是a[3])是’{‘。

a[3]被放在了索引8的位置,我们继续计算add和xor,就可以得到a[8]了,因为操作后的要和之前的一样。

1
2
3
0x7B + ADD[8] = 0x7B + 0x37 = 0xB2
0xB2 ^ XOR[8] = 0xB2 ^ 0xD4 = 0x66 => flag[8] = 'f'
CTF{ _ _ _ _ f _ _ _ _ _ }\0

所以,就有了

3 => 8 => 11 => 5 => 4 => 10 => 12 => 13 => 14 => 7,依次类推出来所有的flag

但是,这里还有注意的是进位。

11 => 5

1
2
3
0x4D + ADD[5] = 0x4D + 0xDE = 0x12B
0x2B ^ XOR[5] = 0x2B ^ 0x1A = 0x31 => flag[5] = '1'
CTF{ _ 1 _ _ f _ _ M _ _ }\0

这里发生了进位,需要将进位添加到add[6]里面去

其他的也一样。

image-20200906235018243

出现单个数字的,说明进位到了什么索引。

分别add[6]、add[7]、add[8]都发生了进位,但是add[6]加上进位是在第六位计算之前,所以没事。add[8]是add[7]进位,add[7]在四字节分割中是第二个四字节的末尾,所以他的进位无法添加到下一个有效字节,不用管它。

所以,最后只有add[7]在结束后都需要重新计算。

1
2
3
a[14] + ADD[7] = 0x7D + 0xFF = 0x17C
0x7C ^ XOR[7] = 0x7C ^ 0x38 = 0x44 => flag[7] = 'D'
CTF{ S 1 M D f 0 r M 3 ! }\0

由于a[7]变了,相应的a[2]也会变

add[1]有进位(0x4d + 0xbe == 0x10b),add[2]=0xae

1
2
3
a[7] + ADD[7] = 0x44 + 0xae = 0xf2
0x7C ^ XOR[7] = 0xf2 ^ 0xb4 = 0x46 => flag[7] = 'F'
CTF{ S 1 M D f 0 r M 3 ! }\0

我们知道前面三个,这个也算是验证吧。

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
import binascii
flag = ['C','T','F','{',0,0,0,0,0,0,0,0,0,0,0,'\0']

add = [0xEF, 0xBE, 0xAD, 0xDE, 0xAD, 0xDE, 0xE1, 0xFE, 0x37,0x13, 0x37, 0x13, 0x66, 0x74, 0x63, 0x67]
xor = [0x76, 0x58, 0xB4, 0x49, 0x8D, 0x1A, 0x5F, 0x38, 0xD4,0x23, 0xF8, 0x34, 0xEB, 0x86, 0xF9, 0xAA]
shuffle = [2, 6, 7, 1, 5, 0xB, 9, 0xE, 3, 0xF, 4, 8, 0xA, 0xC,0xD, 0]
index = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
start = 3


def bianli(number):
for i in range(16):#0-15
if(shuffle[i] == number):
return i
for i in range(14):
h = binascii.b2a_hex(flag[start])
start = bianli(start)
a = chr(((eval("0x"+h) +add[start])&0xff)^xor[start])
if(((eval("0x"+h) +add[start])&0xff00)!=0):
if((start+1)%4 != 0):
print(start+1)
add[start+1]+=(eval("0x"+h) +add[start])>>8
print("number:",start)
print("\nsymbol:",a)
flag[start]=a
if(i==9):
start = 15
if(i==11):
start = 14

flag[2]='F'#flag[2]通过重新计算就是'F',由于a[7]变了

print("add:")
for i in range(15):
print(hex(add[i]))

print("flag:",flag)

参考

https://github.com/Moji99/googleCTF2020-BEGINNER

Q:如果阅读本文需要付费,你是否愿意为此支付1元?