This page has been machine-translated from the original page.
I participated in dvCTF, which started on 3/12, so I wrote a brief writeup.
Mini Game(Rev)
I tried to run the downloaded file, but it failed with a no such file or directory error.
According to the following article, binaries built on alpine may fail to run on glibc-based distributions such as Ubuntu because the required libraries are not available.
So I installed musl libc with the following command.
sudo apt install musl-devThis allowed me to run the binary.
Next, I decompiled it with Ghidra.
For dynamic analysis, I changed Ghidra’s image base to 0x555555554000.
Below is an excerpt from the decompiled main function.
// main関数
check_input(input_text);
for (i = 0; i < 0xac; i = i + 1) {
iVar1 = *(int *)(&DAT_555555558420 + (long)i * 4);
change_value((long)iVar1 / (long)0xe & 0xffffffff,(long)iVar1 % (long)0xe & 0xffffffff,
(long)iVar1 % (long)0xe & 0xffffffff);
}
iVar1 = is_clear?();The function names were renamed manually.
When I looked at the is_clear function, which ultimately determines whether you get the flag, it looked like this.
Since DAT_0010409c stores 0xe, the code above appears to check the values in DAT_555555558100 196 times (0xe * 0xe), four bytes at a time from the beginning. You only get the flag if all of those values are either 0x2d or 0x2a.
However, user input cannot manipulate the values in DAT_555555558100 directly.
Tracing backward through the main function, I found the following code where the user’s input changes the values in DAT_555555558420.
for (; (pcVar2 = token, token != (char *)0x0 && (i < 0xac)); i = i + 1) {
*(undefined8 *)(acStack120 + lVar1 + -8) = 0x5555555559d2;
iVar3 = atoi(pcVar2);
*(int *)(&DAT_555555558420 + (long)i * 4) = iVar3;
*(undefined8 *)(acStack120 + lVar1 + -8) = 0x5555555559fe;
token = strtok((char *)0x0,";");
}In the code above, the input is split on ;, and up to 0xac values are checked.
It then stores those ;-separated numbers as 4-byte integers, sequentially, in the first 0xac entries of DAT_555555558420.
for (i = 0; i < 0xac; i = i + 1) {
iVar1 = *(int *)(&DAT_555555558420 + (long)i * 4);
change_value((long)iVar1 / (long)0xe & 0xffffffff,(long)iVar1 % (long)0xe & 0xffffffff,
(long)iVar1 % (long)0xe & 0xffffffff);
}The values stored in this DAT_555555558420 region can then indirectly rewrite DAT_555555558100 through the following code.
for (local_c = 0; local_c < 0xac; local_c = local_c + 1) {
iVar1 = *(int *)(&DAT_00104420 + (long)local_c * 4);
FUN_00101a29((long)iVar1 / (long)DAT_00104098 & 0xffffffff,
(long)iVar1 % (long)DAT_0010409c & 0xffffffff,
(long)iVar1 % (long)DAT_00104098 & 0xffffffff);
}In this code, the DAT_00104420 region is traversed 0xac times in 4-byte steps, and transformed versions of those values are passed into FUN_00101a29.
Inside FUN_00101a29, as shown below, the address in DAT_555555558100 indicated by the incoming values is rewritten to 0x2d only if it is not already 0x2a.
if ((((-1 < div) && (div < 0xe)) && (-1 < mod)) && (mod < 0xe)) {
if (*(int *)(&DAT_555555558100 + ((long)div * 0xe + (long)mod) * 4) == 0x2a) {
BOOM();
}
else {
*(undefined4 *)(&DAT_555555558100 + ((long)div * 0xe + (long)mod) * 4) = 0x2d;
}
return;
}At initialization, the contents of 0x555555558100 looked like this.
$ x/197w 0x555555558100
0x555555558100: 0x0000002d 0x00000001 0x00000001 0x00000000
0x555555558110: 0x00000001 0x00000001 0x00000001 0x00000001
0x555555558120: 0x00000001 0x00000001 0x00000001 0x00000001
0x555555558130: 0x00000001 0x00000000 0x00000001 0x0000002a
0x555555558140: 0x00000001 0x00000000 0x00000001 0x0000002a
0x555555558150: 0x00000001 0x00000001 0x0000002a 0x00000001
0x555555558160: 0x00000001 0x0000002a 0x00000001 0x00000000
0x555555558170: 0x00000001 0x00000002 0x00000002 0x00000002From there, if you extract every address other than the ones that originally contain 0x2a and 0x2d, then pass their relative offsets joined with ; as the input, all 196 four-byte entries in 0x555555558100 become either 0x2a or 0x2d. That clears the check and gives you the flag.
The final input that produced the flag was as follows.
0;1;2;3;4;5;6;7;8;9;10;11;12;13;14;172;16;17;18;173;20;21;174;23;24;175;26;27;28;29;30;31;32;33;34;35;36;37;38;39;40;41;42;43;177;45;178;47;48;49;179;51;52;53;180;55;56;57;58;181;60;61;62;63;64;65;66;67;68;69;183;71;72;73;74;75;76;185;78;79;80;81;82;83;84;85;186;87;88;89;90;91;92;93;188;95;96;97;98;99;100;189;102;103;104;105;106;107;108;109;110;190;112;113;114;191;116;117;118;119;120;121;122;192;124;125;126;127;128;129;130;131;132;133;134;135;136;137;138;139;140;141;193;143;144;145;194;147;148;149;150;151;152;153;154;155;156;157;158;159;160;161;195;0;164;165;166;167;168;169;170;171I generated this input with the following solver.
init_table = [0x00000001,0x00000001,0x00000001,0x00000000,
0x00000001,0x00000001,0x00000001,0x00000001,
0x00000001,0x00000001,0x00000001,0x00000001,
0x00000001,0x00000000,0x00000001,0x0000002a,
0x00000001,0x00000000,0x00000001,0x0000002a,
0x00000001,0x00000001,0x0000002a,0x00000001,
0x00000001,0x0000002a,0x00000001,0x00000000,
0x00000001,0x00000002,0x00000002,0x00000002,
0x00000002,0x00000002,0x00000001,0x00000002,
0x00000002,0x00000002,0x00000001,0x00000002,
0x00000002,0x00000001,0x00000000,0x00000001,
0x0000002a,0x00000003,0x0000002a,0x00000001,
0x00000000,0x00000001,0x0000002a,0x00000001,
0x00000000,0x00000001,0x0000002a,0x00000002,
0x00000001,0x00000002,0x00000002,0x0000002a,
0x00000002,0x00000001,0x00000001,0x00000002,
0x00000002,0x00000001,0x00000000,0x00000001,
0x00000001,0x00000002,0x0000002a,0x00000002,
0x00000002,0x00000002,0x00000001,0x00000000,
0x00000001,0x0000002a,0x00000001,0x00000001,
0x00000001,0x00000001,0x00000000,0x00000001,
0x00000001,0x00000002,0x0000002a,0x00000002,
0x00000001,0x00000000,0x00000001,0x00000001,
0x00000001,0x00000001,0x0000002a,0x00000001,
0x00000001,0x00000001,0x00000001,0x00000001,
0x00000003,0x0000002a,0x00000002,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000001,
0x00000002,0x00000002,0x00000002,0x0000002a,
0x00000001,0x00000000,0x00000002,0x0000002a,
0x00000002,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000001,0x0000002a,
0x00000002,0x00000001,0x00000001,0x00000001,
0x00000002,0x00000002,0x00000001,0x00000001,
0x00000001,0x00000001,0x00000000,0x00000000,
0x00000001,0x00000001,0x00000001,0x00000000,
0x00000000,0x00000001,0x0000002a,0x00000001,
0x00000000,0x00000001,0x0000002a,0x00000002,
0x00000002,0x00000002,0x00000001,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000001,
0x00000001,0x00000001,0x00000000,0x00000001,
0x00000001,0x00000003,0x0000002a,0x0000002a,
0x00000001,0x00000000,0x00000000,0x00000001,
0x00000001,0x00000002,0x00000001,0x00000001]
arr = []
other = [172, 173, 174, 175, 177, 178, 179, 180, 181, 183,
185, 186, 188, 189, 190, 191, 192, 193, 194, 195, 0]
j = 0
for i, data in enumerate(init_table):
if data != 0x0000002a:
arr.append(str(i))
else:
arr.append(str(other[j]))
j += 1
result = ";".join(arr)
print(result)Summary
Sorry this was a rough writeup.