Posts Marcados Linux

Running 64bit binaries on 32bit userland with 64bit kernel

The problem

Some time ago I wrote about how my Gentoo Linux is installed (blogpost in pt_BR) on my laptop. Long story short: I have a hybrid system, 32bit userland running on a 64bit kernel. I opted to do this type of install because I still have an impression that a full 64bit system still has some key problems (in my past adventures flash was one of them). Anyway, this setup works very well. I can use all 8GB (not by one same process) for example. But not long after the installation I discovered one thing that got me confused.

How come even with a 64bit kernel I still coudn’t run any 64bit ELF binary?

I thought that just having a 64bit kernel was enough to run 32 and 64bit binaries, in fact it is! But is not so trivial do run a 64bit binary in a 32bit userland.

The first attempt, no luck

The first thing I tried was obviously call the binary directly on the command line:

daltonmatos@jetta ~/src [158]$ ./m64
-bash: ./m64: No such file or directory
daltonmatos@jetta ~/src [159]$

This is, indeed, a very cryptic error message.

Enter the dynamic loader

All binaries (at least the massive majority) on your system is a dynamically linked ELF. And you can check this with the file command.

daltonmatos@jetta ~/src [179]$ file /bin/ls
/bin/ls: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), 
for GNU/Linux 2.6.9, stripped
daltonmatos@jetta ~/src [180]$

Remember, 32bit userland. A bit further and we will see this for 64 bit binaries.

By dynamic linked you can understand that all libraries that the binary depends on are loaded at runtime. You can check which are these libraries with the ldd command.

daltonmatos@jetta ~/src [180]$ ldd /bin/ls
 linux-gate.so.1 => (0xffffe000)
 librt.so.1 => /lib/librt.so.1 (0xf776c000)
 libacl.so.1 => /lib/libacl.so.1 (0xf7763000)
 libc.so.6 => /lib/libc.so.6 (0xf7605000)
 libpthread.so.0 => /lib/libpthread.so.0 (0xf75eb000)
 /lib/ld-linux.so.2 (0xf7796000)
 libattr.so.1 => /lib/libattr.so.1 (0xf75e4000)
daltonmatos@jetta ~/src [181]$

Note: Thee ldd command is just a wrapper around the real dynamic loader, usually /lib/ld.so or /lib/ld-<version>.so. I’m running glibc-2.13 so here I have /lib/ld-2.13.so. Running ldd <some-binary> is the same as running /lib/ld-2.13.so --list <some-binary>. So for now on we will be using the second.

Here begins the differencies. The 32 bit ld.so (or the ldd wrapper) tool does not know how to handle a 64 bit binary, as we can see:

daltonmatos@jetta ~/src [229]$ /lib/ld-2.13.so --list ./m64
./m64: error while loading shared libraries: ./m64: wrong ELF class: ELFCLASS64
daltonmatos@jetta ~/src [230]$

And the ldd command:

 
daltonmatos@jetta ~/src [190]$ ldd ./m64
 not a dynamic executable

which is clearly not true, since I compiled m64 myself using a regular gcc that outputs 64bit binaries. So we need a 64bit-capable ld.so. Since I have two compilers (one that generates 32bit binaries and another that generates 64bit) I need to have two complete tool chains (basically: binutils, gcc, gdb, glibc). So my second tool chain has what I need.

Specifically on Gentoo Linux, the cross compiling infrastructure creates /usr/<arch>-pc-linux-gnu folder and stores all files under it so I have a /usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so, that is, the dynamic loader for 64bit binaries. Yes, I have a different version of the glibc for 64bit (2.14.1).

So let’s re-check our binaries, but now with this new tool:

daltonmatos@jetta ~/src [191]$ file ./m64 
./m64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), 
for GNU/Linux 2.6.9, not stripped
daltonmatos@jetta ~/src [192]$

Alright, our brand new 64bit program. Now, the shared libraries:

daltonmatos@jetta ~/src [194]$ /usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so --list ./m64
 linux-vdso.so.1 => (0x00007fffa90fb000)
 libc.so.6 => /usr/x86_64-pc-linux-gnu/lib/libc.so.6 (0x00007f103f367000)
 /lib64/ld-linux-x86-64.so.2 => /usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so (0x00007f103f6f7000)
daltonmatos@jetta ~/src [195]$

That’s awesome! You maybe asking yourself: “How is it possible to run the ld.so binary directly but not other 64bit binaries?” And you’re right, because the ld.so itself is a 64bit binary:

daltonmatos@jetta ~/src [197]$ file /usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so 
/usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), 
dynamically linked, stripped
daltonmatos@jetta ~/src [198]$

If you know this, let me know because I don’t! =) I just discovered that the dynamic loader itself is runnable directly from the command line and can be used to run other binaries.

daltonmatos@jetta ~/src [200]$ file ./m64
./m64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), 
for GNU/Linux 2.6.9, not stripped
daltonmatos@jetta ~/src [201]$ /usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so ./m64 
Hello World
daltonmatos@jetta ~/src [202]$

Like magic! =)

If you tried these exact steps I  just described you probably got an error like this one:

daltonmatos@jetta ~/src [202]$ /usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so ./m64 
./m64: error while loading shared libraries: libc.so.6: wrong ELF class: ELFCLASS32
daltonmatos@jetta ~/src [203]$

What does this mean? Remember that we have two glibc’s installed? Both binaries depend on a shared library with the name libc.so.6. So shouldn’t the SO handle this automatically? Yes, but it doesn’t and now I have to tell you that I’ve been hiding one piece of important information from you. Is the second key to be able to run 64bit binaries, being the first the discovery of ld.so as a generic binary runner.

Go back in the output of the ld.so --list command and see how the libc.so.6‘s path is resolved in both cases. See that they have different paths? 32 bit resloves to /lib/libc.so.6 and 64bit resolves to /usr/x86_64-pc-linux-gnu/lib/libc.so.6. So how do we handle this?

It turns out that we can change where the dynamic loader looks for shared objects with the LD_LIBRARY_PATH environment variable. So all commands using the 64bit loader need to be prepended with LD_LIBRARY_PATH=<new-path>, like this:

daltonmatos@jetta ~/src [203]$ LD_LIBRARY_PATH=/usr/x86_64-pc-linux-gnu/lib /usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so ./m64 
Hello World
daltonmatos@jetta ~/src [204]$

One of the key advantages of having the ability to do this is that you can compile some programs to 64bit code and this program will be able to use more that 4GB of RAM, if needed.

An easier, but not optimal way

There is another way to do this that will not require tweaking the library loading path or anythin. You can compile all your 64bit binaries as statically linked ELF’s, like this:

daltonmatos@jetta ~/src [218]$ x86_64-pc-linux-gnu-gcc -static -o m64s main.c
daltonmatos@jetta ~/src [219]$ file m64s 
m64s: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.9, not stripped
daltonmatos@jetta ~/src [220]$

This way you won’t need the dynamic loader and then you can run your binary directly from the command line:

daltonmatos@jetta ~/src [220]$ ./m64s
Hello World
daltonmatos@jetta ~/src [221]$

But this comes with a cost. Extra disk space:

daltonmatos@jetta ~/src [222]$ file m64*
m64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.9, not stripped
m64s: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.9, not stripped
daltonmatos@jetta ~/src [223]$ ls -lh m64*
-rwxr-xr-x 1 daltonmatos daltonmatos 7,7K Abr 8 17:15 m64
-rwxr-xr-x 1 daltonmatos daltonmatos 771K Abr 10 20:32 m64s
daltonmatos@jetta ~/src [224]$

The statically linked binary is 100 times bigger than the dynamic one. This is because you have all that’s needed bundled into the static binary. You can reduce the final size using the strip command but you won’t get a significant reduction.

That’s it! Thanks for reading!

, , , ,

6 Comentários

SSH bruteforce break attempts made to my personal server

This is just a quick post to show you why you must at least try to configure properly you ssh servers. Today, reading my server logs I realized a big number of login failures into my server and I thought about collecting some numbers about these attempts.

All login attempts have the same signature on the logs, it’s something like this:

Apr 19 23:33:20 li292-237 sshd[25190]: Invalid user <username> from <IP Address>

Each entry with a different username and possibly a different origin IP. A quick wc -l on the string Invalid user returns 197243 lines.

daltonmatos@jetta ~/ [10]$ grep -i "invalid user" messages | wc -l
197243
daltonmatos@jetta ~/ [11]$

From all these, there are 32175 unique username

daltonmatos@jetta ~/ [13]$ grep -i "invalid user" messages | awk '{print $8}' | sort | uniq | wc -l
32175
daltonmatos@jetta ~/ [14]$

and 669 unique IP Addresses.

daltonmatos@jetta ~/ [16]$ grep -i "invalid user" messages | awk '{print $10}' | sort | uniq | wc -l
669
daltonmatos@jetta ~/ [17]$

The 10 most common usernames were:

daltonmatos@jetta ~/ [25]$ grep -i "invalid user" messages | awk '{print $8}' | ./count.py | sort -k 2 -n -r | head -10
test 3099
oracle 2901
admin 2869
guest 1596
nagios 1459
postgres 1229
user 1174
mysql 1030
ftp 754
temp 592
daltonmatos@jetta ~/ [26]$

p.s. Here is the code for count.py, if you are curious. =) https://gist.github.com/2333049

The same approach cane be applied to know the IP’s involved in the attacks and here are the top 10 (just the number of hits from each IP):

daltonmatos@jetta ~/ [27]$ grep -i "invalid user" messages | awk '{print $10}' | ./count.py | sort -k 2 -n -r | head -10
19332
16498
10844
9109
7283
5860
5288
4482
4343
4270
daltonmatos@jetta ~/ [28]$

Running a whois on the IP’s show me that the attacks comes from many different places including: China, Japan, Nigeria and US.

All attempts were made between April 2011 and April 2012. What I want to show you is that you need to take care of your ssh server configuration because one of these attempts could be successful. The first thing I do with any new ssh server is to disable login/password authentication and only accept ssh keys authentication. This is already a good start. =)

Thanks for reading.

Update: My great friend Denilson just pointed out that count.py has the very same effect of uniq -c. Thanks for the tip!

, , , , ,

1 comentário

Gentoo com kernel land 64 bits e user land 32 bits

O problema

Recentemente comprei um novo  notebook e por isso tive a oportunidade de instalar um novo S.O do zero, afinal já estava com uma mesma instalação desde 2008 (quando comprei meu primeiro notebook). Até poderia transferir uma instalação de um notebook para o outro, mas daria um trabalho parecido (ou maior) do que instalar novamente.

Nesse momento surgiu a mesma dúvida de sempre: Instalar a versão 64 bits ou 32 bits? Em 2008 tentei instalar uma versão totalmente 64 bits e tive problemas com plugin do flash, cheguei a ter um firefox 32 bits só para poder ver vídeos usando flash. Pouco tempo depois desisti do sistema 64 bits por esses e outros motivos e acabei instalando 32 bits mesmo.

Dessa vez a mesma dúvida. Achei que 3 anos depois as coisas estariam melhores, mas já de cara tive problemas com o driver de vídeo. A tela simplesmente não dava refresh corretamente. Um ls no terminal não retornava nenhum resultado, a não ser que a janela do terminal fosse movida de lugar, aí sim acontecia o refresh e o resultado do ls (já executado) aparecia. Isso significa que estava simplesmente impossível de usar o sistema.

Percebi que no live-DVD que estava usando, o driver de vídeo funcionava perfeitamente, então comecei a estudar o que o sistema do live-DVD tinha de diferente. Percebi que ele era userland 32 bits, apesar de reconhecer os 4GB do novo notebook. Nesse momento tive a idéia de tentar uma instalação mista: Kernel 64 bits e userland 32 bits.

Crosscompiling

O termo crosscompiling é usado quando temos um compilador rodando em um sistema e gerando código para outro. Por exemplo, você tem um notebook (intel x86) e consegue compilar um código em C que roda no seu celular (processador ARM, por exemplo). Mas o crosscompiling não serve apenas para gerar código entre máquinas diferentes, pode ser usado também para gerar código para a mesma máquinas, apenas escolhendo se o resultado será um binário de 32 bits ou 64 bits.

Foi exatamente isso que fiz nesse caso. Para fazer o que estava querendo precisava ter dois compiladores. Primeiro usei o gcc que é instalado no gentoo para compilar um outro gcc, esse último gerando código 64 bits. Ou seja, tenho agora um compilador que roda em um ambiente 32 bits mas que gera código 64 bits. E esse compilador foi gerado por um outro compilador que roda em 32 bits e também gera código 32 bits, entendeu? Ainda comigo? Ok, vamos lá.

Toda a mágica desse processo é feita pelo pacote sys-devel/crossdev, que faz parte da árvore principal do Gentoo. O que esse script faz é construir todo o toolkit necessário para gerar o cross compiler que precisamos. O que precisamos fazer é muito simples:

# emerge crosdevv
# crossdev -t x86_64-pc-linux-gnu

E isso é suficiente. Quando vi que realmente funcionava lembrei de 2004, quando ainda estava usando slackware e tive que fazer isso “na unha. Cheguei até a postar no twitter um agradecimento a quem quer que tenha escrito esse script. Merecido!

Compilando o novo kernel

Agora, com tudo pronto, temos dois compiladores: O que gera código 32 bits, que podemos chamar apenas digitando gcc, e o que gera código 64 bits que podemos chamar com o comando x86_64-pc-linux-gnu-gcc. O que temos que fazer agora é configurar nosso kernel para que use o compilador correto. Felizmente alguém já precisou fazer o que estou fazendo e existe uma maneira fácil de escolher o compilador que vai gerar nosso kernel.

Escolhendo compilador customizado para nosso kernel

Para isso basta irmos ao menu General setup/ e escolher o valor correto para a opção Cross-compiler tool prefix. Isso fará com que nosso novo compilador seja usado no momento de compilar nosso kernel. Agora o que temos que fazer é compilar nosso kernel como fazemos normalmente, o de sempre:

# make
# make modules
# make modules_install

Agora é só copiar nosso novo 64 bit kernel para o lugar certo, adicionar uma nova entrada no boot loader e bootar nosso novo kernel. E isso é o que temos depois de um boot com sucesso:

Kernel 64 bits com userland 32 bits

Exatamente o que queríamos:

  • Kernel 64 bits (X86_64)
  • Userland 32 bits (todos os binários são ELF 32-bit)
  • Os 4 GB RAM corretamente sendo usados
Agora tenho o driver de vídeo funcionando perfeitamente, com aceleração 3D e o novo gnome3 rodando sem nenhum problema! Perfeito!

, ,

1 comentário

Acessando os sites que você quiser no trabalho

Esse post mostra uma (dentre várias) formas de burlar os diversos bloqueios impostos por algumas empresas no que diz respeito a acesso à internet. Veremos uma forma bem simples e eficiente de navegar em sites que supostamente não poderíamos estar vendo. =)

Leia o resto deste post »

, ,

14 Comentários

O poder do pipe e a importância de programas de linha de comando que sabem usar entrada/saída padrão

A interface de linha de comando, na marioria daz vezes, causa medo nas pessoas. Não sei se é pela aparência meio bruta ou se é pelo fato de raramente você usar o mouse (sim, tem gente que não consegue imaginar um computador útil sem um mouse!!). O fato é que apesar da aparência a linha de comando possui um poder quase infinito.

O grande poder dessa interface está na possibilidade de combinar quaisquer comandos para que seja possível executar uma tarefa maior. Nesse post proponho um desafio para um problema que parece complicado. Imagine um arquivo de 10GB compactado e dividido em 5 partes de 2GB. Como gravar esse arquivo, já descompactado, em um disco que só tem exatos 10GB de espaço? Com a linha de comando é possível!

Leia o resto deste post »

,

6 Comentários

Como burlar limitações de conectividade impostas por hoteis e afins

Muitas vezes estamos em locais públicos e que oferecem conexão com a internet. Em alguns locais essa conexão é paga, em outros é de graça mas possui limite de tempo de conexão e em outros é paga e limita o tempo de conexão. Veremos nesse post uma forma de extender nosso tempo de conexão quando necessário.

Leia o resto deste post »

, ,

10 Comentários

xargs: A ponte entre entrada padrão e a lista de parâmetros

Update: Apenas mudança na formatação. Texto justificado. Mudança no formato das figuras (jpg)

O xargs é um programa de linha de comando que transforma o conteúdo de sua entrada padrão na lista de parâmetros de um segundo comando, à sua escolha. Muito útil quando você tem um texto qualquer e, com cada parte desse texto, deseja executar um comando. Um exemplo clássico é o redimensionamento de uma quantidade muito grande imagens, ou a necessidade de baixar da rede uma lista de arquivos.

Leia o resto deste post »

, , ,

1 comentário