Although this is the tenth crackme, I don’t intend to make it specialized as I don’t see a point, it will be a traditional (recent) crackme located here.
The user-defined entry point is located in _start
opposed to the standard libc-defined main
:
0x0000000000401000 <+0>: mov $0x1,%eax
0x0000000000401005 <+5>: mov $0x1,%edi
0x000000000040100a <+10>: movabs $0x402000,%rsi
0x0000000000401014 <+20>: mov $0x19,%edx
0x0000000000401019 <+25>: syscall
0x000000000040101b <+27>: mov $0x0,%eax
0x0000000000401020 <+32>: mov $0x0,%edi
0x0000000000401025 <+37>: movabs $0x402074,%rsi
0x000000000040102f <+47>: mov $0x20,%edx
0x0000000000401034 <+52>: syscall
0x0000000000401036 <+54>: cmp $0x0,%rax
0x000000000040103a <+58>: jl 0x401114 <_start.error>
0x0000000000401040 <+64>: mov %rax,%r14
0x0000000000401043 <+67>: add $0x6,%r14
0x0000000000401047 <+71>: mov 0x402019,%rax
0x000000000040104f <+79>: mov %rax,0x402094
0x0000000000401057 <+87>: mov 0x402074,%rax
0x000000000040105f <+95>: mov %rax,0x40209a
0x0000000000401067 <+103>: mov $0x1,%eax
0x000000000040106c <+108>: mov $0x1,%edi
0x0000000000401071 <+113>: movabs $0x402094,%rsi
0x000000000040107b <+123>: mov %r14,%rdx
0x000000000040107e <+126>: syscall
0x0000000000401080 <+128>: mov $0x1,%eax
0x0000000000401085 <+133>: mov $0x1,%edi
0x000000000040108a <+138>: movabs $0x402020,%rsi
0x0000000000401094 <+148>: mov $0x16,%edx
0x0000000000401099 <+153>: syscall
0x000000000040109b <+155>: mov $0x0,%eax
0x00000000004010a0 <+160>: mov $0x0,%edi
0x00000000004010a5 <+165>: movabs $0x402074,%rsi
0x00000000004010af <+175>: mov $0x20,%edx
0x00000000004010b4 <+180>: syscall
0x00000000004010b6 <+182>: mov %rax,%r15
0x00000000004010b9 <+185>: dec %r15
Since, fuck tracing syscalls, I’ll run strace
over the program as I run it and pinpoint the corresponding system-calls:
execve("./hello", ["./hello"], [/* 13 vars */]) = 0
write(1, "Please enter your name: \0", 25) = 25
read(0, "a\n", 32) = 2
write(1, "Hello a\n", 8) = 8
write(1, "Enter your Password: \0", 22) = 22
read(0, "b\n", 32) = 2
write(1, "Wrong Credentials, ", 24) = 24
exit(4202608) = ?
+++ exited with 112 +++
And so the prettified assembly:
write(1, "Please enter your name: \0", 25)
%rax = read(0, 0x402074, 32)
0x0000000000401036 <+54>: cmp $0x0,%rax
0x000000000040103a <+58>: jl 0x401114 <_start.error>
0x0000000000401040 <+64>: mov %rax,%r14
0x0000000000401043 <+67>: add $0x6,%r14
0x0000000000401047 <+71>: mov 0x402019,%rax
0x000000000040104f <+79>: mov %rax,0x402094
0x0000000000401057 <+87>: mov 0x402074,%rax
0x000000000040105f <+95>: mov %rax,0x40209a
0x0000000000401067 <+103>: mov $0x1,%eax
0x000000000040106c <+108>: mov $0x1,%edi
0x0000000000401071 <+113>: movabs $0x402094,%rsi
0x000000000040107b <+123>: mov %r14,%rdx
0x000000000040107e <+126>: syscall
0x0000000000401080 <+128>: mov $0x1,%eax
0x0000000000401085 <+133>: mov $0x1,%edi
0x000000000040108a <+138>: movabs $0x402020,%rsi
0x0000000000401094 <+148>: mov $0x16,%edx
0x0000000000401099 <+153>: syscall
0x000000000040109b <+155>: mov $0x0,%eax
0x00000000004010a0 <+160>: mov $0x0,%edi
0x00000000004010a5 <+165>: movabs $0x402074,%rsi
0x00000000004010af <+175>: mov $0x20,%edx
0x00000000004010b4 <+180>: syscall
0x00000000004010b6 <+182>: mov %rax,%r15
0x00000000004010b9 <+185>: dec %r15
Where _start.error
is actually mostly undetected by GDB and requires some manual input to disassemble the full function:
0x0000000000401114 <_start.error+0>: mov %rax,0x402070
0x000000000040111c <_start.exit+0>: mov $0x3c,%eax
0x0000000000401121 <_start.exit+5>: movabs $0x402070,%rdi
0x000000000040112b <_start.exit+15>: syscall
Since it leads into the _start.exit
function it appears, though this seems faulty since _start.exit+0
immediately overwrites %rax
as writing to %eax
will clear the upper-half as defined by the Intel manual, also note the read
syscall implementation as ksys_read
:
ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
{
struct fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
if (f.file) {
loff_t pos = file_pos_read(f.file);
ret = vfs_read(f.file, buf, count, &pos);
if (ret >= 0)
file_pos_write(f.file, pos);
fdput_pos(f);
}
return ret;
}
The vfs_read
function is the lowest kernel-scope function for reading a file (AFAIK) as shown here, its declaration is as such:
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos);
It takes a file
structure, the pointer to the user’s out-buffer, how much to read and the offset from the beginning I presume is what the pos
is:
ssize_t __vfs_read(struct file *file, char __user *buf, size_t count,
loff_t *pos)
{
if (file->f_op->read)
return file->f_op->read(file, buf, count, pos);
else if (file->f_op->read_iter)
return new_sync_read(file, buf, count, pos);
else
return -EINVAL;
}
Where struct file
is defined as:
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
/*
* Protects f_ep_links, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
enum rw_hint f_write_hint;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
struct mutex f_pos_lock;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
errseq_t f_wb_err;
} __randomize_layout
__attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
And the only relevant field is const struct file_operations *f_op;
which is defined as:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;
And our relevant function prototype is:
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
Which was quite a waste of time as we could’ve been able to derive this from the first call to the file->f_op->read
but that’s fine since now we need to delve into the implementation of this on the file structures that we’re working with. Eventually, we get to the point where we have to figure out what __fget_light
does, as it has to retrieve the corresponding file structure for the stdout/stdin file-descriptors:
static unsigned long __fget_light(unsigned int fd, fmode_t mask)
{
struct files_struct *files = current->files;
struct file *file;
if (atomic_read(&files->count) == 1) {
file = __fcheck_files(files, fd);
if (!file || unlikely(file->f_mode & mask))
return 0;
return (unsigned long)file;
} else {
file = __fget(fd, mask);
if (!file)
return 0;
return FDPUT_FPUT | (unsigned long)file;
}
}
And the __to_fd
function:
static inline struct fd __to_fd(unsigned long v)
{
return (struct fd){(struct file *)(v & ~3),v & 3};
}
Which is much simpler, it uses the GNU extension where you may unpack multiple literals into a structure, otherwise zeroing the excess out, the we take some file-descriptor v
, mask out the 2 LSBs, and put it in a struct fd
:
struct fd {
struct file *file;
unsigned int flags;
};
I.e., the flags
is the lower 2 bits of the file-descriptor, and the struct file
is equivalent to the structure given above, but with:
struct llist_node {
struct llist_node *next;
};
Or:
struct callback_head {
struct callback_head *next;
void (*func)(struct callback_head *head);
} __attribute__((aligned(sizeof(void *))));
#define rcu_head callback_head
Where the structure generated is struct callback_head
such that the next
element of both aggregate structures is going to be the file-descriptor with the 2 LSBs cleared, so in terms of stdin
= 1, and stdout
= 0, I see that this really doesn’t matter since all we get is simply a struct fd
structure whose file->next
member is 0
in both cases except the flags
for stdin
will have a set lower-bit, and stdout
will not.
The current
macro returns the following:
#define current ((struct task_struct *) ia64_getreg(_IA64_REG_TP))
Where ia64_getreg
is another macro:
#define ia64_getreg IA64_INTRINSIC_MACRO(getreg)
And IA64_INTRINSIC_MACRO
is another macro:
#define IA64_INTRINSIC_MACRO(name) ia64_native_ ## name
Which gives us another fucking macro ia64_native_getreg
which is defined twice because the Linux kernel source is heavily obfuscated, and in general programmed very well!!!
arch/ia64/include/uapi/asm/gcc_intrin.h, line 62 (as a macro)
arch/ia64/include/uapi/asm/intel_intrin.h, line 19 (as a macro)
In intel_intrin.h
it is defined as #define ia64_native_getreg __getReg
, and in gcc_intrin.h
it is defined as:
#define ia64_native_getreg(regnum) \
({ \
__u64 ia64_intri_res; \
\
switch (regnum) { \
case _IA64_REG_GP: \
asm volatile ("mov %0=gp" : "=r"(ia64_intri_res)); \
break; \
case _IA64_REG_IP: \
asm volatile ("mov %0=ip" : "=r"(ia64_intri_res)); \
break; \
case _IA64_REG_PSR: \
asm volatile ("mov %0=psr" : "=r"(ia64_intri_res)); \
break; \
case _IA64_REG_TP: /* for current() */ \
ia64_intri_res = ia64_r13; \
break; \
case _IA64_REG_AR_KR0 ... _IA64_REG_AR_EC: \
asm volatile ("mov %0=ar%1" : "=r" (ia64_intri_res) \
: "i"(regnum - _IA64_REG_AR_KR0)); \
break; \
case _IA64_REG_CR_DCR ... _IA64_REG_CR_LRR1: \
asm volatile ("mov %0=cr%1" : "=r" (ia64_intri_res) \
: "i" (regnum - _IA64_REG_CR_DCR)); \
break; \
case _IA64_REG_SP: \
asm volatile ("mov %0=sp" : "=r" (ia64_intri_res)); \
break; \
default: \
ia64_bad_param_for_getreg(); \
break; \
} \
ia64_intri_res; \
})
So finally, the last macro that we care about getting is __getReg
, it appears that it isn’t defined anywhere!!! because fuck shit bitch ass etc., so we Google it further and discover that the manual for the Intel C++ Compiler for Linux
has more information on what it does here on page 350:
Gets the value from a hardware register based on the index passed in. Produces a corresponding mov = r instruction. Provides access to the following registers: See Register Names for getReg() and setReg().
Where the registers are defined on page 353 as some odd variety of registers that I have barely studied, there appears to be no GPRs like %rax
, or %rbp
which I am familiar with, rather some other names like:
_IA64_REG_AR_RSC
_IA64_REG_AR_BSP
_IA64_REG_AR_BSPSTORE
_IA64_REG_AR_RNAT
_IA64_REG_AR_FCR
_IA64_REG_AR_EFLAG
_IA64_REG_AR_CSD
_IA64_REG_AR_SSD
_IA64_REG_AR_CFLAG
_IA64_REG_AR_FSR
_IA64_REG_AR_FIR
_IA64_REG_AR_FDR
_IA64_REG_AR_CCV
_IA64_REG_AR_UNAT
_IA64_REG_AR_FPSR
_IA64_REG_AR_ITC
_IA64_REG_AR_PFS
_IA64_REG_AR_LC
_IA64_REG_AR_EC
Of which, I recognize the EFLAG
; you may think I’m diving into the Linux internals a bit too far, by a long stretch, but I disagree as I believe it is quite educational in order to FULLY understand the return value of read
:
RETURN VALUE
On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number. It is not an error if this
number is smaller than the number of bytes requested; this may happen for example because fewer bytes are actually available right now (maybe because we
were close to end-of-file, or because we are reading from a pipe, or from a terminal), or because read() was interrupted by a signal. See also NOTES.
On error, -1 is returned, and errno is set appropriately. In this case, it is left unspecified whether the file position (if any) changes.
Yeah, it returns a subzero value if it errors, alright onto the rest of the decompilation:
write(1, "Please enter your name: \0", 25)
if (read(0, 0x402074, 32) < 0)
{
_start_error ();
}
0x0000000000401040 <+64>: mov %rax,%r14
0x0000000000401043 <+67>: add $0x6,%r14
0x0000000000401047 <+71>: mov 0x402019,%rax
0x000000000040104f <+79>: mov %rax,0x402094
0x0000000000401057 <+87>: mov 0x402074,%rax
0x000000000040105f <+95>: mov %rax,0x40209a
0x0000000000401067 <+103>: mov $0x1,%eax
0x000000000040106c <+108>: mov $0x1,%edi
0x0000000000401071 <+113>: movabs $0x402094,%rsi
0x000000000040107b <+123>: mov %r14,%rdx
0x000000000040107e <+126>: syscall
0x0000000000401080 <+128>: mov $0x1,%eax
0x0000000000401085 <+133>: mov $0x1,%edi
0x000000000040108a <+138>: movabs $0x402020,%rsi
0x0000000000401094 <+148>: mov $0x16,%edx
0x0000000000401099 <+153>: syscall
0x000000000040109b <+155>: mov $0x0,%eax
0x00000000004010a0 <+160>: mov $0x0,%edi
0x00000000004010a5 <+165>: movabs $0x402074,%rsi
0x00000000004010af <+175>: mov $0x20,%edx
0x00000000004010b4 <+180>: syscall
0x00000000004010b6 <+182>: mov %rax,%r15
0x00000000004010b9 <+185>: dec %r15
Starting with:
0x0000000000401040 <+64>: mov %rax,%r14
0x0000000000401043 <+67>: add $0x6,%r14
0x0000000000401047 <+71>: mov 0x402019,%rax
0x000000000040104f <+79>: mov %rax,0x402094
0x0000000000401057 <+87>: mov 0x402074,%rax
0x000000000040105f <+95>: mov %rax,0x40209a
0x0000000000401067 <+103>: mov $0x1,%eax
0x000000000040106c <+108>: mov $0x1,%edi
0x0000000000401071 <+113>: movabs $0x402094,%rsi
0x000000000040107b <+123>: mov %r14,%rdx
0x000000000040107e <+126>: syscall
%rax
is the length of the buffer, we add 0x6
to account for the eventual "Hello "
we’re going to prepend, 0x402019
contains "Hello "
, +87
was quite a weird instruction as the AT&T syntax doesn’t make obvious that it’s moving the whole byte-string into %rax
, that is, our input into %rax
, afterwards saving it at 0x40209a
, which is 6 bytes ahead of 0x402094
, hence appending it. Then we simply write it to the stdout:
0x0000000000401080 <+128>: mov $0x1,%eax
0x0000000000401085 <+133>: mov $0x1,%edi
0x000000000040108a <+138>: movabs $0x402020,%rsi
0x0000000000401094 <+148>: mov $0x16,%edx
0x0000000000401099 <+153>: syscall
We then print "Enter your password: "
:
0x000000000040109b <+155>: mov $0x0,%eax
0x00000000004010a0 <+160>: mov $0x0,%edi
0x00000000004010a5 <+165>: movabs $0x402074,%rsi
0x00000000004010af <+175>: mov $0x20,%edx
0x00000000004010b4 <+180>: syscall
Afterwards saving 32 bytes of password into 0x402074
which is interestingly also the username buffer. I notice at this point that the disassembly was cut short due to again the same thing that happened with the _start.error
thing, the programmer of the crackme apparently didn’t know how to make local labels:
0x0000000000401000 <_start+0>: mov $0x1,%eax
0x0000000000401005 <_start+5>: mov $0x1,%edi
0x000000000040100a <_start+10>: movabs $0x402000,%rsi
0x0000000000401014 <_start+20>: mov $0x19,%edx
0x0000000000401019 <_start+25>: syscall
0x000000000040101b <_start+27>: mov $0x0,%eax
0x0000000000401020 <_start+32>: mov $0x0,%edi
0x0000000000401025 <_start+37>: movabs $0x402074,%rsi
0x000000000040102f <_start+47>: mov $0x20,%edx
0x0000000000401034 <_start+52>: syscall
0x0000000000401036 <_start+54>: cmp $0x0,%rax
0x000000000040103a <_start+58>: jl 0x401114 <_start.error>
0x0000000000401040 <_start+64>: mov %rax,%r14
0x0000000000401043 <_start+67>: add $0x6,%r14
0x0000000000401047 <_start+71>: mov 0x402019,%rax
0x000000000040104f <_start+79>: mov %rax,0x402094
0x0000000000401057 <_start+87>: mov 0x402074,%rax
0x000000000040105f <_start+95>: mov %rax,0x40209a
0x0000000000401067 <_start+103>: mov $0x1,%eax
0x000000000040106c <_start+108>: mov $0x1,%edi
0x0000000000401071 <_start+113>: movabs $0x402094,%rsi
0x000000000040107b <_start+123>: mov %r14,%rdx
0x000000000040107e <_start+126>: syscall
0x0000000000401080 <_start+128>: mov $0x1,%eax
0x0000000000401085 <_start+133>: mov $0x1,%edi
0x000000000040108a <_start+138>: movabs $0x402020,%rsi
0x0000000000401094 <_start+148>: mov $0x16,%edx
0x0000000000401099 <_start+153>: syscall
0x000000000040109b <_start+155>: mov $0x0,%eax
0x00000000004010a0 <_start+160>: mov $0x0,%edi
0x00000000004010a5 <_start+165>: movabs $0x402074,%rsi
0x00000000004010af <_start+175>: mov $0x20,%edx
0x00000000004010b4 <_start+180>: syscall
0x00000000004010b6 <_start+182>: mov %rax,%r15
0x00000000004010b9 <_start+185>: dec %r15
0x00000000004010bc <_start.l1+0>: mov %r15,%r14
0x00000000004010bf <_start.l1+3>: add $0x5,%r14
0x00000000004010c3 <_start.l1+7>: mov 0x402094(%r14),%al
0x00000000004010ca <_start.l1+14>: add $0x5,%al
0x00000000004010cc <_start.l1+16>: cmp 0x402073(%r15),%al
0x00000000004010d3 <_start.l1+23>: jne 0x4010f7 <_start.wrong>
0x00000000004010d5 <_start.l1+25>: dec %r15
0x00000000004010d8 <_start.l1+28>: jne 0x4010bc <_start.l1>
0x00000000004010da <_start.l1+30>: mov $0x1,%eax
0x00000000004010df <_start.l1+35>: mov $0x1,%edi
0x00000000004010e4 <_start.l1+40>: movabs $0x402053,%rsi
0x00000000004010ee <_start.l1+50>: mov $0x18,%edx
0x00000000004010f3 <_start.l1+55>: syscall
0x00000000004010f5 <_start.l1+57>: jmp 0x40111c <_start.exit>
0x00000000004010f7 <_start.wrong+0>: mov $0x1,%eax
0x00000000004010fc <_start.wrong+5>: mov $0x1,%edi
0x0000000000401101 <_start.wrong+10>: movabs $0x402036,%rsi
0x000000000040110b <_start.wrong+20>: mov $0x18,%edx
0x0000000000401110 <_start.wrong+25>: syscall
0x0000000000401112 <_start.wrong+27>: jmp 0x40111c <_start.exit>
0x0000000000401114 <_start.error+0>: mov %rax,0x402070
0x000000000040111c <_start.exit+0>: mov $0x3c,%eax
0x0000000000401121 <_start.exit+5>: movabs $0x402070,%rdi
0x000000000040112b <_start.exit+15>: syscall
So, let’s start with:
0x00000000004010b6 <_start+182>: mov %rax,%r15
0x00000000004010b9 <_start+185>: dec %r15
0x00000000004010bc <_start.l1+0>: mov %r15,%r14
0x00000000004010bf <_start.l1+3>: add $0x5,%r14
0x00000000004010c3 <_start.l1+7>: mov 0x402094(%r14),%al
0x00000000004010ca <_start.l1+14>: add $0x5,%al
0x00000000004010cc <_start.l1+16>: cmp 0x402073(%r15),%al
0x00000000004010d3 <_start.l1+23>: jne 0x4010f7 <_start.wrong>
0x00000000004010d5 <_start.l1+25>: dec %r15
0x00000000004010d8 <_start.l1+28>: jne 0x4010bc <_start.l1>
0x00000000004010da <_start.l1+30>: mov $0x1,%eax
0x00000000004010df <_start.l1+35>: mov $0x1,%edi
0x00000000004010e4 <_start.l1+40>: movabs $0x402053,%rsi
0x00000000004010ee <_start.l1+50>: mov $0x18,%edx
0x00000000004010f3 <_start.l1+55>: syscall
We initialize %r15
to the length of the password and decrement it by 1 to account for the \n
, afterwards we add 5 to the length and index the string 0x402094
which is the full "Hello <username>"
, so essentially we index the last characters and palce them in %al
, hereafter adding 5 to their ordinal, and comparing it to some string at 0x402073
which is a null-byte, but is proceeded by the password we are given.
Hence 0x402073(%r15)
where %r15
is the length of the string minus one, is going to be the last non-newline character, and so if the username isn’t the same as the last character of the password:
char username[32];
char password[32];
fgets (username, sizeof (username), stdin);
fgets (password, sizeof (password), stdin);
size_t pw_length = strlen (password) - 1;
char hello[15];
sprintf (hello, "Hello %s", username);
username[8] = password[8] = 0; // the strings are shortsized due to the %rax moves
do {
if ( (hello[pw_length + 5] + 5) != password[pw_length-1])
{
puts ("\033[31mWrong Credentials, GTFO");
_start_error ();
}
--pw_length;
} while (pw_length);
And so, we have to craft a username buffer whose characters are rotated by 5 characters to the right of the equivalent password character, or something along those lines:
def generate_password(username):
return ''.join(chr(ord(char)+5) for char in username)
For example, let’s try "una3ed"
:
>>> gen_pw("una3ed")
'zsf8ji'
[...]
>./hello
Please enter your name: una3ed
Hello una3ed
Enter your Password: zsf8ji
Great H4x0r Skillz!
Tada!
Thanks for reading.