summaryrefslogtreecommitdiffstats
path: root/docs/writeups/2023/lactf/rev/switcheroo.txt
blob: c7cd4e50546adba2bfd380bfb3b54ea003b5d02d (plain) (blame)
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
Never fear, the flag shall not appear (in memory)!  I've switched it out for a
more secure system!

Note: The actual flag consists purely of non-whitespace printable characters
(aka ascii values 0x21 through 0x7e).




RE
--
The switcheroo binary is compiled from assembly language, rather than C or
anything else higher-level.  However, it is still very useful to use Ghidra to
analyze strings and add additional comments and markup in the code listing view.

Given the above, the file is actually quite small and simple.  We have just a
couple boilerplate sections in the ELF, .data contains 3 strings and an array of
64-bit integers, and all of the logic lies sequentially under .text.

The program is a flag checker.  When run, it first asks you to input the flag,
then asserts that the size of input == 64, so we know the length of the expected
flag.  To validate the content of the proposed flag, we enter a loop to iterate
over each input character (this is denoted by the `loop_start` label below).

For each input character, we start by using the value of that character (ascii
as an int) as an index into the 64-bit integer array found in the program.  The
value we lookup will encode what position(s) the character index may correctly
occupy in the real flag.  We enter an inner loop to decode the 64-bit value.

At each step, an 8-bit left bit rotation is performed on the value, we then look
at the resulting lowest-order byte in the 64-bit value.  The first value we
extract is a count value.

The code asserts that the value of the current flag character position (as a
byte) exists in the looked-up value within the next `count` byte rotations.
This area is denoted by the `inner_loop_start` label below.  If we hit a
matching position value, the outer loop advances.  However, if we exhaust
`count` many rotations before that occurs, we increment another register that
records how many failed characters were given, and the outer loop continues
regardless.

If the outer loop finishes without incrementing the failure register, the
correct flag was given.  See the full .text logic here, with some markup from
me:

```
                     undefined __stdcall entry(void)
     undefined         AL:1           <RETURN>
     undefined1        Stack[0x0]:1   local_res0                              XREF[1]:     00401040(*)  
                     entry                                           XREF[4]:     Entry Point(*), 00400018(*), 
                                                                                  00400088(*), 
                                                                                  _elfSectionHeaders::00000090(*)  
00401000 48 8d 34        LEA        RSI,[s_Give_me_the_flag:_00402000]               = "Give me the flag: "
         25 00 20 
         40 00
00401008 bf 01 00        MOV        EDI,0x1
         00 00
0040100d ba 12 00        MOV        EDX,0x12
         00 00
00401012 b8 01 00        MOV        EAX,0x1
         00 00
                     write(stdout, "Give me the flag", size)
00401017 0f 05           SYSCALL
00401019 31 ff           XOR        EDI,EDI
0040101b 48 89 e6        MOV        RSI,RSP
0040101e ba 64 00        MOV        EDX,0x64
         00 00
00401023 31 c0           XOR        EAX,EAX
                     read(stdin, &stack, 100)
00401025 0f 05           SYSCALL
00401027 48 83 f8 40     CMP        RAX,0x40
                     if read() != 64: fail
0040102b 75 5e           JNZ        fail
0040102d 4d 31 e4        XOR        R12,R12
00401030 4c 8d 1c        LEA        R11,[static_data]
         25 3c 20 
         40 00
00401038 4d 31 d2        XOR        R10,R10
                     loop_start                                      XREF[1]:     00401071(j)  
0040103b 49 39 c2        CMP        R10,RAX
0040103e 7d 33           JGE        loop_break
                     R9 = stack[R10] // get current char
00401040 4e 0f b6        MOVZX      R9,byte ptr [RSP + R10*0x1]=>local_res0
         0c 14
                     R8 = static_data[R9] // lookup long by current char
00401045 4e 8b 04        MOV        R8,qword ptr [static_data + R9*0x8]
         cd 3c 20 
         40 00
                     rotate left by 8-bits (1-byte)
0040104d 49 c1 c0 08     ROL        R8,0x8
                     R13 = (char)(static_data[stack[i]] >> 56)
00401051 4d 0f b6 e8     MOVZX      R13,R8B
00401055 4d 31 f6        XOR        R14,R14
                     while R14 < R13
                     inner_loop_start                                XREF[1]:     00401069(j)  
00401058 4d 39 ee        CMP        R14,R13
0040105b 7d 0e           JGE        inner_loop_break_bad
0040105d 49 c1 c0 08     ROL        R8,0x8
00401061 45 38 d0        CMP        R8B,R10B
00401064 74 08           JZ         inner_loop_break_good
00401066 49 ff c6        INC        R14
00401069 eb ed           JMP        inner_loop_start
                     inner_loop_break_bad                            XREF[1]:     0040105b(j)  
0040106b 49 ff c4        INC        R12
                     inner_loop_break_good                           XREF[1]:     00401064(j)  
0040106e 49 ff c2        INC        R10
00401071 eb c8           JMP        loop_start
                     loop_break                                      XREF[1]:     0040103e(j)  
00401073 4d 85 e4        TEST       R12,R12
00401076 75 13           JNZ        fail
00401078 eb 02           JMP        success
0040107a eb              ??         EBh
0040107b 00              ??         00h
                     success                                         XREF[1]:     00401078(j)  
0040107c 48 8d 34        LEA        RSI,[s_That_was_the_flag!_0040202a]              = "That was the flag!"
         25 2a 20 
         40 00
00401084 ba 12 00        MOV        EDX,0x12
         00 00
00401089 eb 0f           JMP        exit
                     fail                                            XREF[2]:     0040102b(j), 00401076(j)  
0040108b 48 8d 34        LEA        RSI,[s_That_was_not_the_flag_:(_00402012]        = "That was not the flag :("
         25 12 20 
         40 00
00401093 ba 18 00        MOV        EDX,0x18
         00 00
00401098 eb 00           JMP        exit
                     exit                                            XREF[2]:     00401089(j), 00401098(j)  
0040109a bf 01 00        MOV        EDI,0x1
         00 00
0040109f b8 01 00        MOV        EAX,0x1
         00 00
                     write(stdout, "That was [not] the flag", size)
004010a4 0f 05           SYSCALL
004010a6 6a 0a           PUSH       0xa
004010a8 48 89 e6        MOV        RSI,RSP
004010ab ba 01 00        MOV        EDX,0x1
         00 00
004010b0 b8 01 00        MOV        EAX,0x1
         00 00
                     write(stdout, "\n", 1)
004010b5 0f 05           SYSCALL
004010b7 31 ff           XOR        EDI,EDI
004010b9 48 c7 c0        MOV        RAX,0x3c
         3c 00 00 00
                     exit(0)
004010c0 0f 05           SYSCALL
```




Solution
--------
To extract the flag, I extracted the 64-bit integer array to attempt to fully
decode it.  With the rules we learned during RE, we know that each entry can be
used to validate whether or not it resides at any given position in the flag
string.

I wrote a C program to iterate over a flag string (aka position from 0 -> 64)
and print any character value (aka array entry at an ascii index) that _would_
pass validation.  This initially gave some ambiguous results, however when my
search was restricted per the problem description (0x21 -> 0x7e), a unique flag
appears.

```
#include <stdio.h>

static const unsigned char A[] = {
    0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x07, 0x7e, 0xfc, 0x3c, 0x78,
    0xfb, 0xf4, 0x04, 0x3e, 0x03, 0x87, 0x30, 0x04, 0x3d, 0x81, 0x70, 0x9d,
    0x4d, 0xa7, 0x3d, 0x09, 0xf8, 0xa1, 0x51, 0x4a, 0xfc, 0x02, 0x8b, 0xa8,
    0xb2, 0x50, 0x49, 0x0c, 0x4b, 0xfd, 0xf0, 0x62, 0x92, 0x1d, 0xd2, 0x02,
    0xa9, 0x46, 0xbe, 0xaa, 0xbe, 0x81, 0xe7, 0x88, 0x94, 0x19, 0xbb, 0x85,
    0x0e, 0x0f, 0x09, 0x77, 0xfb, 0x49, 0x3f, 0x35, 0x24, 0xb8, 0x4e, 0x56,
    0x47, 0x9b, 0x62, 0xeb, 0x9d, 0x7a, 0x0b, 0x7f, 0xd1, 0x18, 0x6e, 0x14,
    0x30, 0xb1, 0x3f, 0x01, 0xed, 0x65, 0xd4, 0x4d, 0x7e, 0xe1, 0x50, 0xe6,
    0xe7, 0xab, 0x3d, 0x80, 0x04, 0x67, 0xd3, 0x81, 0x7a, 0x8c, 0x71, 0x73,
    0xa3, 0x80, 0xce, 0x02, 0x73, 0x48, 0x25, 0x30, 0x09, 0x51, 0x2c, 0x82,
    0x4e, 0x68, 0x70, 0x50, 0x47, 0x37, 0x08, 0x57, 0xb5, 0x14, 0x10, 0x32,
    0xec, 0x34, 0xd7, 0x3b, 0xb3, 0x98, 0xdd, 0x7e, 0xe3, 0x1e, 0x5e, 0x97,
    0xfd, 0x4c, 0x0d, 0xb6, 0xfe, 0x44, 0x81, 0x15, 0x91, 0x42, 0x15, 0xac,
    0x6c, 0x7c, 0x40, 0x88, 0x1b, 0xfc, 0x0d, 0x92, 0x2b, 0x19, 0xae, 0x0f,
    0x80, 0x26, 0x76, 0x7c, 0x6e, 0x67, 0x0c, 0xfc, 0x27, 0xef, 0x94, 0x6d,
    0xd3, 0xb8, 0x50, 0x48, 0xa2, 0x4d, 0x88, 0x0d, 0x72, 0x66, 0x64, 0x08,
    0xdc, 0x48, 0x4f, 0xc1, 0x53, 0x04, 0x7e, 0xac, 0xd8, 0xdf, 0x61, 0x24,
    0xad, 0x68, 0x25, 0x7c, 0x08, 0x1f, 0x91, 0xde, 0x5d, 0x06, 0x3b, 0x47,
    0x71, 0x58, 0xe6, 0x42, 0xd7, 0x1f, 0xed, 0x1e, 0x48, 0x25, 0xb8, 0xc1,
    0xc3, 0xed, 0x81, 0x6c, 0xff, 0x02, 0x18, 0xb6, 0x71, 0x9f, 0x16, 0x03,
    0xe0, 0x3f, 0xae, 0x92, 0x67, 0x39, 0xa7, 0xe4, 0xca, 0xb7, 0x10, 0x6b,
    0xac, 0x08, 0xcf, 0x90, 0xa0, 0x81, 0xf0, 0x31, 0x09, 0xa1, 0x4c, 0x00,
    0x3b, 0x68, 0x89, 0x8f, 0xf1, 0x3d, 0xf5, 0x00, 0x49, 0x8f, 0xde, 0xb1,
    0xbc, 0x42, 0x42, 0x00, 0xca, 0x84, 0xac, 0xce, 0x00, 0x59, 0x7e, 0x00,
    0xa4, 0x0e, 0x11, 0xe8, 0x0f, 0x6f, 0x70, 0x00, 0x4e, 0x50, 0x62, 0x20,
    0xb4, 0x78, 0x22, 0x00, 0x9c, 0x2b, 0xa5, 0x5d, 0xa9, 0x05, 0x2a, 0x00,
    0x49, 0x0e, 0xdf, 0xb9, 0xd0, 0xc5, 0x4d, 0x00, 0xc4, 0xf0, 0x6c, 0xe0,
    0x41, 0x36, 0x6c, 0x00, 0x6e, 0x4a, 0x37, 0x8e, 0xa6, 0x47, 0x79, 0x00,
    0x02, 0x68, 0x33, 0x2b, 0x63, 0xd2, 0xd8, 0x00, 0x2e, 0x02, 0xb5, 0x2f,
    0xc4, 0x56, 0x38, 0x01, 0x18, 0x39, 0x08, 0x1a, 0xdb, 0x14, 0x3b, 0x00,
    0xbb, 0xe1, 0x2f, 0xb1, 0x3a, 0x67, 0xf8, 0x00, 0x24, 0x12, 0xed, 0x14,
    0xb0, 0x08, 0xa1, 0x00, 0x04, 0x3b, 0xef, 0x6a, 0x2e, 0x89, 0xb2, 0x00,
    0xd6, 0x24, 0x00, 0x18, 0xd3, 0x3c, 0x2c, 0x02, 0x34, 0x5f, 0xfc, 0xd0,
    0xaa, 0x11, 0x39, 0x02, 0xe6, 0x4e, 0x4b, 0x71, 0xc9, 0x07, 0x08, 0x02,
    0x27, 0x23, 0x30, 0x09, 0x1c, 0x33, 0x1a, 0x07, 0x45, 0xd2, 0xa9, 0x21,
    0x29, 0x06, 0x18, 0x04, 0x53, 0x92, 0x0f, 0x2b, 0x34, 0x16, 0x1f, 0x05,
    0x7c, 0xe9, 0x0a, 0xda, 0xbf, 0x19, 0x3b, 0x01, 0x1e, 0x2f, 0x19, 0x35,
    0x25, 0x17, 0x12, 0x07, 0x95, 0xd2, 0x10, 0xc9, 0x0b, 0x2a, 0x32, 0x03,
    0x94, 0x63, 0x19, 0xfa, 0xff, 0x70, 0xc0, 0x00, 0xdd, 0x99, 0x49, 0xa8,
    0x72, 0xb0, 0x9a, 0x00, 0xac, 0xaa, 0xa1, 0x74, 0xe1, 0x82, 0x4e, 0x00,
    0xb9, 0x84, 0x95, 0x41, 0x05, 0x52, 0xaa, 0x00, 0xd5, 0xe3, 0x1e, 0x8c,
    0x2b, 0x8a, 0x67, 0x00, 0xd0, 0xe7, 0xd8, 0xc9, 0xac, 0xbc, 0x45, 0x00,
    0xf9, 0xd4, 0xb0, 0x84, 0xc3, 0x19, 0x3a, 0x00, 0x1b, 0xc5, 0x82, 0xa5,
    0x60, 0x19, 0x8b, 0x00, 0xcd, 0x2a, 0x2a, 0x6c, 0x50, 0xc4, 0x4e, 0x00,
    0xe1, 0x86, 0x57, 0x60, 0xc8, 0x73, 0x74, 0x00, 0x7f, 0xb3, 0xba, 0x6b,
    0x53, 0x16, 0x13, 0x01, 0xef, 0xde, 0xdb, 0x7f, 0x24, 0x0d, 0x0f, 0x00,
    0x76, 0x1e, 0xde, 0xa9, 0x08, 0xf6, 0xad, 0x00, 0xe3, 0x82, 0xcf, 0x68,
    0x08, 0x54, 0x9f, 0x00, 0x1d, 0x80, 0x2b, 0x31, 0xfc, 0xf5, 0x14, 0x00,
    0xfe, 0x24, 0x32, 0xc4, 0x14, 0x6f, 0x10, 0x00, 0xc5, 0x37, 0x8b, 0x5d,
    0xad, 0x5b, 0xb8, 0x00, 0x0d, 0x99, 0x8c, 0x84, 0x17, 0xce, 0xed, 0x00,
    0xc4, 0xbc, 0x11, 0xdf, 0x13, 0xdc, 0xcc, 0x00, 0x9b, 0x55, 0xa0, 0x76,
    0x67, 0x2d, 0x0c, 0x02, 0xf7, 0x21, 0x75, 0xea, 0x64, 0x1b, 0x0a, 0x02,
    0x82, 0xbe, 0x90, 0xd9, 0x24, 0x1d, 0x39, 0x00, 0xa8, 0x3c, 0xd7, 0x9d,
    0x71, 0x00, 0x75, 0x00, 0x37, 0x2a, 0xa8, 0xb2, 0xad, 0x2e, 0xef, 0x00,
    0x60, 0x14, 0x96, 0x40, 0x0a, 0xf1, 0x8e, 0x00, 0xa5, 0x67, 0x3f, 0xfe,
    0x2c, 0x9a, 0x33, 0x00, 0x9d, 0x87, 0x65, 0x63, 0x30, 0x71, 0xe0, 0x00,
    0xf0, 0xdc, 0x75, 0x2a, 0x3d, 0x6e, 0x6e, 0x00, 0xf0, 0x73, 0x82, 0xc1,
    0x54, 0x72, 0x2e, 0x01, 0x7f, 0x66, 0x9b, 0xcd, 0xad, 0x08, 0x48, 0x00,
    0x95, 0x63, 0xea, 0x30, 0x12, 0x80, 0x10, 0x01, 0x38, 0xfc, 0xf1, 0xf1,
    0xc7, 0x98, 0xfa, 0x00, 0xd9, 0x7b, 0xfe, 0xe6, 0x0f, 0x40, 0x0d, 0x01,
    0x4d, 0xb9, 0x08, 0x00, 0x15, 0x9e, 0x47, 0x00, 0x95, 0x48, 0x13, 0x38,
    0xcf, 0x80, 0xf9, 0x00, 0xac, 0x5d, 0xcb, 0x7b, 0xfe, 0x0f, 0x2a, 0x00,
    0x99, 0x9b, 0xce, 0x0f, 0x4b, 0x0b, 0x75, 0x00, 0x58, 0x77, 0xb3, 0x79,
    0xfe, 0x6e, 0xbd, 0x00, 0x28, 0x36, 0x15, 0x31, 0x24, 0x0e, 0x20, 0x07,
    0x02, 0xbc, 0x32, 0x40, 0xf1, 0xaa, 0x7c, 0x00, 0xbb, 0x95, 0x83, 0x61,
    0x1f, 0x01, 0x3d, 0x02, 0x9e, 0x6f, 0xfe, 0x34, 0x78, 0x80, 0x53, 0x00,
    0xc8, 0x70, 0x86, 0xe8, 0x3d, 0x30, 0x02, 0x01, 0xf8, 0xd8, 0x08, 0xc7,
    0x3a, 0x0c, 0xf5, 0x00, 0xbc, 0x75, 0x9d, 0xa9, 0xf8, 0x5e, 0xff, 0x00,
    0x7e, 0x51, 0x0e, 0x10, 0x12, 0x3a, 0x04, 0x02, 0x06, 0xd7, 0x49, 0xd0,
    0x19, 0x2d, 0xe8, 0x00, 0x14, 0x42, 0x62, 0xdb, 0x31, 0x26, 0x14, 0x02,
    0x0b, 0xa6, 0xff, 0xb0, 0x63, 0x13, 0xa9, 0x00, 0xf9, 0xb2, 0x7d, 0x13,
    0xa1, 0x8e, 0x85, 0x00, 0xec, 0x18, 0xb0, 0x06, 0xfa, 0x4f, 0x6f, 0x00,
    0x34, 0xf0, 0x80, 0x76, 0xe7, 0x57, 0x00, 0x01, 0x72, 0x72, 0x8c, 0x8b,
    0xb8, 0x5a, 0x5a, 0x00, 0x82, 0x1d, 0x08, 0x34, 0x68, 0x19, 0x1d, 0x01,
    0xac, 0xea, 0xfa, 0x56, 0x07, 0xee, 0x6d, 0x00, 0x78, 0x00, 0x16, 0x0c,
    0xe8, 0x61, 0x16, 0x00, 0x13, 0xc3, 0x58, 0x1a, 0x7a, 0xa6, 0xcc, 0x00,
    0xe8, 0x13, 0x1b, 0xb8, 0x83, 0x36, 0x22, 0x01, 0x8d, 0xd6, 0x45, 0x03,
    0xc2, 0xca, 0x65, 0x00, 0xdc, 0xec, 0x50, 0x60, 0x1a, 0x1c, 0x03, 0x01,
    0x05, 0x19, 0x2c, 0x97, 0xec, 0x04, 0x37, 0x01, 0x25, 0x0b, 0x6c, 0x8b,
    0xe4, 0xc9, 0x4e, 0x00, 0xdd, 0xe1, 0xa5, 0x12, 0x0d, 0xc4, 0x55, 0x00,
    0x0e, 0xe4, 0x20, 0xc4, 0xfc, 0xd4, 0xf0, 0x00, 0xf3, 0x27, 0x8b, 0x99,
    0xc1, 0xb8, 0x9b, 0x00, 0xf6, 0x1a, 0x04, 0x04, 0x5d, 0xc8, 0xca, 0x00,
    0x05, 0xba, 0x8e, 0x89, 0xaf, 0x41, 0x05, 0x02, 0x95, 0x10, 0x67, 0x3d,
    0x26, 0xb0, 0x2c, 0x00, 0x43, 0x36, 0xac, 0xe7, 0xff, 0x42, 0x3e, 0x02,
    0x87, 0x60, 0x09, 0x4e, 0x62, 0xf3, 0x19, 0x00, 0xec, 0x88, 0xa7, 0x5a,
    0x99, 0xd3, 0xf8, 0x26 };

static const unsigned long *B = (unsigned long *)A;

static void rol(long *v) {
    long tmp = *v << 8;
    tmp |= *v >> 56;
    *v = tmp;
}

int check_char_in_pos(int c, int pos) {
    long value = B[c];
    rol(&value);

    for (int count = value & 0xff; count > 0; count--) {
        rol(&value);

        if ((value & 0xff) == pos)
            return 1;
    }

    return 0;
}

int main(void) {
    for (int pos = 0; pos < 0x40; pos++) {
        for (int c = 0x21; c <= 0x7e; c++) {
            if (check_char_in_pos(c, pos)) {
                printf("%c", (char)c);
                break;
            }
        }
    }

    printf("\n");
    return 0;
}
```

lactf{4223M8LY_5W17Ch_57473M3n75_4r3_7h3_4850LU73_8357_u+1f60a}

I still don't know where the switch statement was...