In conjunction with the 11th International Erlang User Conference, an
Erlang Obfuscated Programming Competition was held. The goal of this competition
was to write the most obfuscated Erlang program, providing a safe forum
for poor coding practices and programming styles. Through this competition,
we hope to illustrate some of the subtleties of Erlang and how they can
best be used and abused.
A report from the judges
This year's "Subject Matter Experts" were Mike Williams, Ericsson, Sweden Thomas Arts, IT University, Sweden Francesco Cesarini, Erlang Training and Consulting, UK A call for participation to the competition was made at
the Erlang workshop in Tallinn. The rules of the competition are available
here. A total
of 8 submissions were handed in on time. All were ingenious, making it
very hard to pick a winner. The judges individually gave every entry a
grade between 1 - 5 in the following categories: - Obfuscation
- Style
- Innovation
- Functionality
When adding up the grades, 90% of the submissions were just
points from each other! Luckily, there were two distinct winners. We had
to take it a step further, however, and introduce a third prize, The Judge's
Prize, as one of the submissions had actually been used in a live system!
When submissions from the competitions started coming in,
we quickly realized we had omitted an important rule. To tell us what
the code actually does!!!! In most cases, we were able to figure it out,
but it was not easy. We will not do the same mistake next year. Winners
First Prize: Martin Björklund First prize goes to Martin for his program which "exemplifies
the worst mis-design in Erlang". This program really is counter intuitive,
even after reading it several times. It demonstrates that Erlang has indeed
an error in its design, namely the binding order of logical operands.
Here is the source code:
-module(obf2).
-export([g/0]).
g() -> h(str()).
str() -> "'erlang is da most beautiful language there is'".
h(X) ->
ListIsInteger = is_integer([]),
AnAtom = an_atom(),
One = one(),
P = (1 == 1 orelse is_integer(1)),
if
P == (1 == 1 or is_integer(1)) ->
erlang:display("orelse works as or"),
erlang:display(X);
1 == 1 or is_integer(1) ->
erlang:display("is_integer"),
erlang:display(X);
%% whoops, compiler didn't like this one
% 1 == 1 orelse is_integer(1) ->
% erlang:display("is_integer"),
% erlang:display(X);
ListIsInteger == true, is_integer(AnAtom) ->
erlang:display("ListIsInteger"),
erlang:display(X);
ListIsInteger == true and is_integer(AnAtom) ->
erlang:display(f(3,"sa|tde_h{rwdjgdeqauzshihu",X));
1 == One; is_integer(One) ->
erlang:display("1 == 1 or 1 is_integer"),
erlang:display(X);
true ->
erlang:display("one of them _must_ be true!?!")
end.
an_atom() -> foo.
one() -> 1.
f(3,[],_) ->
f(9,"akf","?hdd>-[ic]hsj!uryskeuih");
f(A,Q,X) when erlang:is_integer(Q) ->
if
- Q < - 1 ->
f(A + 1,Q - 1,erlang:tl(X));
1 == 1; is_integer(1) ->
erlang:hd(X)
end;
f(9,[],_) ->
[];
f(A,Q,X) ->
[f(A - 1,erlang:HD(Q) - $a + A,X)|f(A,erlang:TL(Q),X)].
This is what happens when you run the program:
Eshell V5.4.8 (abort with ^G)
1> c(obf2).
./obf2.erl:22: Warning: this expression would cause a 'badarg'
exception at run-time
./obf2.erl:25: Warning: this expression would cause a 'badarg'
exception at run-time
{ok,obf2}
2> obf2:g().
"erlang's binding rules suck!"
true
Second Prize: Urban Boquist
Second prize goes to Urban Urban Boquist who wrote a solver for Sudoku.
Sudoku is a puzzle in which you are given a matrix with some numbers in
it. One needs to complete the matrix with numbers in such a way that all
rows, all columns and all 3x3 blocks contain the digits 1..9 exactly once.
We were impressed by the small program solving this task. One of the judges
found on open source contribution with 44 Java files, 0.5MB in it, doing
approximately the same!
: -module(e).
-export([main/1]).
-import(lists,[all/2]).
-define(t(A,B),B
andalso A).
f(E,R,L,A,N,G) ->
if E>G->{c,N};L>G->f(E+1,R,R,R,N,G);A>G->c(
R,N,G);true->case g(E,L,N)of g->f(E,R,L+1,R,N,G
);_->b(G,E,A,R,N,L)end end. d(A,B)->h(3#21,B,A). h(
A,B,C)->case f(C, C,C,C,[{C-1,C-1,a}|B],A
+2)of{c,D}->e(A+ 1,TL(lists:sort((D))));
_->fail end. main (A)->d(1,A). b(E,R,L,A,
N,G)when L>E->f( R,A,G,L,N,E);b(L,A,N,G,
E,R)->V=round(math:sqrt(L)),C=fun(I)->(I-1)div V*V end,D=fun
({_,_,Q})->N/=Q end,case?t(all(D,[{I,O,S}||{I,O,S}<-E,A==I]),
?t(all(D,[{I,S,O}||{I,S,O}<-E,R==S]),all(D,lists:filter(fun({
O,S,_})->?t(C(A)
<O,?t(O=<C(A)+V,
?t(C(R)<S,S=<C(R
)+V)))end,E))))of true
->f(A,G,R+1,G,[{A ,R,N}|E
],L);_->b(L,A,N+1, G,E,R)end.
a(D,{C,A})->[[B|| {_,_,B}<-C]|
e(D-1,A)]. e(_,[ ])->[];e(E,L)
->a(E+1,lists:split(E+1,L)). c(_,[{_,_,a}|_],_)->b
;c(B,[{R,K,N}|S],A)->f(R,B,K,N+1,S,A);c(_,[],_)
->b. g(B,A,[{B,A,_}|_])->g;g(_,_,[])->h;g(B
,A,[_|C])->g(B,A,C).
Judge's Prize: Mats Cronqvist A special mention goes to a one liner which "has actually
been used on a live system (Can't tell you which)! Mats Cronqvist has
a great example of a real live obfuscated, ugly, and non understandable
piece of code. We showed it at EUC just after ensuring that no one if
responsible for Quality Assurance in his project was present. It is a
one-line implementation of a safe(r) subset of dbg (it had to be pasted
into an erlang shell).
Pi = fun(P) when pid(P) -> case process_info(P, registered_name) of
[] -> case process_info(P, initial_call) of {_, {proc_lib,init_p,5}}
-> proc_lib:translate_initial_call(P); {_,MFA} -> MFA; undefined ->
unknown end; {_,Nam} -> Nam; undefined -> unknown end; (P) when
port(P) -> {name,N} = erlang:port_info(P,name), [Hd|_] =
string:tokens(N," "), Tl =
lists:reverse(hd(string:tokens(lists:reverse(Hd),"/"))),
list_to_atom(Tl); (R) when atom(R) -> R; ({R,Node}) when atom(R),
Node == node() -> R; ({R, Node}) when atom(R), atom(Node) -> {R,
Node} end, Ts = fun(Nw) -> {_,{H,M,S}} =
calendar:now_to_local_time(Nw), {H,M,S,element(3,Nw)} end, Munge =
fun(I) -> case string:str(I, "Return addr") of 0 -> case
string:str(I, "cp = ") of 0 -> []; _ -> [_, C|_] =
string:tokens(I,"()+"), list_to_atom(C) end; _ -> case string:str(I,
"erminate process normal") of 0 -> [_, C|_] =
string:tokens(I,"()+"), list_to_atom(C); _ -> [] end end end, Stack
= fun(Bin) -> L = string:tokens(binary_to_list(Bin),"\n"),
{stack,lists:flatten(lists:map(Munge,L))} end, Prc = fun(all) ->
all; (Pd) when pid(Pd) -> Pd; ({pid,P1,P2}) when integer(P1),
integer(P2) -> c:pid(0,P1,P2); (Reg) when atom(Reg) -> case
whereis(Reg) of undefined -> exit({rdbg, no_such_process, Reg}); Pid
when pid(Pid) -> Pid end end, MsF = fun(stack, [{Head,Cond,Body}])
-> [{Head,Cond,[{message,{process_dump}}|Body]}]; (return,
[{Head,Cond,Body}]) -> [{Head,Cond,[{return_trace}|Body]}]; (Head,
[{_,Cond,Body}]) when tuple(Head)-> [{Head,Cond,Body}]; (X,_) ->
exit({rdbg,bad_match_spec,X}) end, Ms = fun(Mss) -> lists:foldl(MsF,
[{'_',[],[]}], Mss) end, ChkTP = fun({M,F}) when atom(M), atom(F),
M/='_', F/='_' -> {{M,F,'_'},[],[global]}; ({M,F,MS}) when atom(M),
atom(F), M/='_', F/='_' -> {{M,F,'_'},Ms(MS),[global]};
({M,F,MS,local}) when atom(M), atom(F), M/='_', F/='_' ->
{{M,F,'_'},Ms(MS),[local]}; ({M,F,MS,global}) when atom(M), atom(F),
M/='_', F/='_' -> {{M,F,'_'},Ms(MS),[global]}; (X) ->
exit({rdbg,unrec_trace_pattern,X}) end, ChkTPs = fun(TPs) when
list(TPs) -> lists:map(ChkTP,TPs); (TP) -> [ChkTP(TP)] end, SetTPs =
fun({MFA,MS,Fs}) -> erlang:trace_pattern(MFA,MS,Fs) end, DoInitFun =
fun(Time) -> erlang:register(rdbg, self()),
erlang:start_timer(Time,self(),{die}),
erlang:trace_pattern({'_','_','_'},false,[local]),
erlang:trace_pattern({'_','_','_'},false,[global]) end, InitFun =
fun(Time,all,send) -> exit({rdbg,too_many_processes});
(Time,all,'receive') -> exit({rdbg,too_many_processes}); (Time,P,
send) -> DoInitFun(Time),
erlang:trace(Prc(P),true,[send,timestamp]); (Time,P,'receive') ->
DoInitFun(Time), erlang:trace(Prc(P),true,['receive',timestamp]);
(Time,P,TPs) -> CTPs = ChkTPs(TPs), DoInitFun(Time),
erlang:trace(Prc(P),true,[call,timestamp]),
lists:foreach(SetTPs,CTPs) end, LoopFun = fun(G,N,Out) when N < 1
-> erlang:trace(all,false,[call,send,'receive']),
erlang:trace_pattern({'_','_','_'},false,[local]),
erlang:trace_pattern({'_','_','_'},false,[global]), io:fwrite("**
rdbg, ~w msgs **~n", [length(Out)]),
io:fwrite("~p~n",[lists:reverse(Out)]), io:fwrite("~p~n",
[process_info(self(),message_queue_len)]); (G,Cnt,Out) -> case
process_info(self(),message_queue_len) of {_,N} when N > 100 ->
exit({rdbg,msg_queue,N}); _ -> ok end, receive {timeout,_,{die}} ->
G(G,0,Out); {trace_ts,Pid,send,Msg,To,TS} ->
G(G,Cnt-1,[{send,Ts(TS),Pi(To),Msg}|Out]);
{trace_ts,Pid,'receive',Msg,TS} ->
G(G,Cnt-1,[{'receive',Ts(TS),Msg}|Out]);
{trace_ts,Pid,return_from,MFA,V,TS} ->
G(G,Cnt-1,[{return,MFA,V}|Out]); {trace_ts,Pid,call,MFA,B,TS} when
binary(B) -> G(G,Cnt-1,[{Pi(Pid),Ts(TS),{Stack(B),MFA}}|Out]);
{trace_ts,Pid,call,MFA,TS} -> G(G,Cnt-1,[{Pi(Pid),Ts(TS),MFA}|Out])
end end, Rdbg = fun(Time,Msgs,Proc,Trc) when integer(Time),
integer(Msgs) -> Start = fun() ->
InitFun(Time,Proc,Trc),LoopFun(LoopFun,Msgs,[]) end,
erlang:spawn_link(Start) end.
|