Even-Odd *(Cursed version)*
Introduction
I was doomscrolling on Twitter1 and found a tweet about the interesting way about determining wheter the given number is even or odd.
Most well-known example (of course, for fun) is:
def is_odd(n):
match n:
case 1 | 3 | 5 | 7 | 9 | 11 | 13 | 15 | 17 | 19 | \
21 | 23 | 25 | 27 | 29 | 31 | 33 | 35 | 37 | 39 | \
41 | 43 | 45 | 47 | 49 | 51 | 53 | 55 | 57 | 59 | \
61 | 63 | 65 | 67 | 69 | 71 | 73 | 75 | 77 | 79 | \
81 | 83 | 85 | 87 | 89 | 91 | 93 | 95 | 97 | 99:
return True
case _:
return False
There were several brilliant ways to determine odd or even, so I decided to commit this one I came across.
Codes
I need 2 codes (evenodd.c
, evenoddstack.c
) to determine wheter the given number is even or odd.
evenodd.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
int main()
{
char number[0x10];
pid_t pid;
int status;
scanf("%s", number);
char *argv_list[3] = {"evenoddstack.out", (char *)number, NULL};
pid = fork();
if (pid == 0)
{
execv("evenoddstack.out", argv_list);
}
else
{
if (waitpid(pid, &status, 0) > 0)
{
if (status != 0)
{
printf("Odd\n");
}
}
}
return 0;
}
evenoddstack.c
#include <stdio.h>
#include <immintrin.h>
int main(int argc, char *argv[])
{
__uint64_t x = atoi(argv[1]);
asm(
"and rsp, 0xfffffffffffffff0 \n"
"mov rcx, QWORD PTR %[val] \n"
"compare: \n"
"test rcx, rcx \n"
"jz lab \n"
"push rcx \n"
"dec rcx \n"
"jmp compare \n"
"lab: \n"
"movaps xmmword ptr [rsp], xmm0 \n"
:
: [val] "m"(x)
: "rcx", "xmm0");
printf("Even\n");
return 0;
}
This requires AVX support for the processor. Determining odd or even requries quite modern CPU architecture
Build
gcc -o evenoddstack.out evenoddstack.c -masm=intel
gcc -o evenodd.out evenodd.c
Execution
Run the evenodd.out
with the number from 1 to 100. (any unsigned integer is allowed)
$ for i in `seq 100`; 02:09:42 Mar 14
do
echo -n $i ' '
./evenodd.out <<< $i
done
1 Odd
2 Even
3 Odd
4 Even
5 Odd
6 Even
7 Odd
8 Even
9 Odd
10 Even
11 Odd
12 Even
13 Odd
14 Even
15 Odd
16 Even
17 Odd
18 Even
19 Odd
20 Even
21 Odd
22 Even
23 Odd
24 Even
25 Odd
26 Even
27 Odd
28 Even
29 Odd
30 Even
31 Odd
32 Even
33 Odd
34 Even
35 Odd
36 Even
37 Odd
38 Even
39 Odd
40 Even
41 Odd
42 Even
43 Odd
44 Even
45 Odd
46 Even
47 Odd
48 Even
49 Odd
50 Even
51 Odd
52 Even
53 Odd
54 Even
55 Odd
56 Even
57 Odd
58 Even
59 Odd
60 Even
61 Odd
62 Even
63 Odd
64 Even
65 Odd
66 Even
67 Odd
68 Even
69 Odd
70 Even
71 Odd
72 Even
73 Odd
74 Even
75 Odd
76 Even
77 Odd
78 Even
79 Odd
80 Even
81 Odd
82 Even
83 Odd
84 Even
85 Odd
86 Even
87 Odd
88 Even
89 Odd
90 Even
91 Odd
92 Even
93 Odd
94 Even
95 Odd
96 Even
97 Odd
98 Even
99 Odd
100 Even
Explanation
Why do this code work? Think case-by-case.
Even case
Parent process:
evenodd.out
Inside of
evenodd.out
, the instructionexecv("evenoddstack.out", argv_list);
executed.evenoddstack.out
with the given number as an argument.Child process:
evenoddstack.out
- Inside of
evenoddstack.out
, the given number string is parsed and stored into 64-bit unsigned integerx
.- Run the inline assembly code.
- Aligns the stack pointer to 16-byte boundary.
- Looping through the assembly, stacking the number from the given number to 1.
- After the loop, store the
xmm0
register to the stack."Even"
and return 0.(Child process terminated with status 0)
- Parent process terminates.
Odd case
Parent process:
evenodd.out
Inside of
evenodd.out
, the instructionexecv("evenoddstack.out", argv_list);
executed.evenoddstack.out
with the given number as an argument.Child process:
evenoddstack.out
- Inside of
evenoddstack.out
, the given number string is parsed and stored into 64-bit unsigned integerx
.- Run the inline assembly code.
- Aligns the stack pointer to 16-byte boundary.
- Looping through the assembly, stacking the number from the given number to 1.
After the loop, store theRaises a segmentation fault!xmm0
register to the stack.(Child process terminated with non-zero status)
- Parent process prints
"Odd"
.- Parent process terminates.
Deep dive into assembly and AVX
Exploiting an x64 system with ROP (Return Oriented Programming) is simple - just chaining the gadgets to jump to the desired function.
However, if the desired function is system-related something, We should align the stack pointer to 16-byte boundary. (To satisfy this, additional ret
gadget for 8-byte excessive padding is sometimes put.)
Seperately run the evenoddstack.out
and check the stack pointer alignment to check the stack pointer alignment.
$ ./evenoddstack.out <<< 33
$rsp = 0x7fffffffe248
Program stops at the main+0x4e
, reason is SIGSEGV
. AVX-extension (like movaps
in this case) requires the stack pointer to be aligned to 16-byte boundary,
but the odd number pushes number times, final $rsp
would be decrease bytes.
So the stack pointer would never aligned to 16-byte boundary, raising a segmentation fault.
$ ./evenoddstack.out <<< 34
$rsp = 0x7fffffffe240
To compare the case between odd and even, I set the breakpoint at the main+0x4e
and the check the stack pointer alignment.
Even number pushes number times, final $rsp
would be decrease bytes.
So the stack pointer would be aligned to 16-byte boundary, and the rest of the program would be executed without any error.
Conclustion
Nobody would use this code in the real world, but we can generalize it by using ymm
(256-bits) and zmm
(512-bits) to determine the given number is multiplier of 4 or 8.
Signal-driven programming using property of AVX was selected from the original author, and I’d mentioned from them!
x から 1 までを 64bit でスタックにつみ、SSE 命令で16バイト(=128bit)境界のセグメンテーションフォールトが起きるかどうかで判定😂😂😂この手のは、例外処理のスマートさも求められますね❣ https://t.co/YHc3JaCuyr
— みゆ🌹ฅ^•ω•^ฅ @X68KBBS / MSXBBS / FANKS (@arith_rose) March 13, 2025
In the original thread, there are more brilliant ways so I recommend to check the original tweet threads!
Footnotes
-
Formerly Twitter - now X; who cares? ↩