Csaw 2015 Wyvern
Goal of this challenge is to get the flag, not pop a shell.
Let's take a look at the binary:
$ file wyvern
wyvern: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=45f9b5b50d013fe43405dc5c7fe651c91a7a7ee8, not stripped
$ ./wyvern
+-----------------------+
| Welcome Hero |
+-----------------------+
[!] Quest: there is a dragon prowling the domain.
brute strength and magic is our only hope. Test your skill.
Enter the dragon's secret: 15935728
[-] You have failed. The dragon's power, speed and intelligence was greater.
So we are dealing with a 64
bit binary, that prompts us for input via stdin. It looks like a normal crackme which scans in data, and checks it.
Reversing
When we take a look at the main function, we see this:
undefined8 main(void)
{
int dragonBattle;
basic_string local_148 [8];
basic_string local_140 [24];
allocator<char> local_128 [8];
basic_string<char,std--char_traits<char>,std--allocator<char>> local_120 [8];
allocator input [268];
operator<<<std--char_traits<char>>((basic_ostream *)cout,"+-----------------------+\n");
operator<<<std--char_traits<char>>((basic_ostream *)cout,"| Welcome Hero |\n");
operator<<<std--char_traits<char>>((basic_ostream *)cout,"+-----------------------+\n\n");
operator<<<std--char_traits<char>>
((basic_ostream *)cout,"[!] Quest: there is a dragon prowling the domain.\n");
operator<<<std--char_traits<char>>
((basic_ostream *)cout,
"\tbrute strength and magic is our only hope. Test your skill.\n\n");
operator<<<std--char_traits<char>>((basic_ostream *)cout,"Enter the dragon\'s secret: ");
fgets((char *)input,0x101,stdin);
allocator();
/* try { // try from 0040e217 to 0040e230 has its CatchHandler @ 0040e2ee */
basic_string((char *)local_120,input);
~allocator(local_128);
/* try { // try from 0040e242 to 0040e254 has its CatchHandler @ 0040e30e */
basic_string(local_140);
/* try { // try from 0040e25a to 0040e265 has its CatchHandler @ 0040e322 */
dragonBattle = start_quest((basic_string)0xc0);
/* try { // try from 0040e27f to 0040e2c1 has its CatchHandler @ 0040e30e */
~basic_string((basic_string<char,std--char_traits<char>,std--allocator<char>> *)local_140);
if (dragonBattle == 0x1337) {
basic_string(local_148);
/* try { // try from 0040e2c7 to 0040e2d2 has its CatchHandler @ 0040e347 */
reward_strength((basic_string)0xb8);
/* try { // try from 0040e2d8 to 0040e2e3 has its CatchHandler @ 0040e30e */
~basic_string((basic_string<char,std--char_traits<char>,std--allocator<char>> *)local_148);
}
else {
/* try { // try from 0040e36c to 0040e37e has its CatchHandler @ 0040e30e */
operator<<<std--char_traits<char>>
((basic_ostream *)cout,
"\n[-] You have failed. The dragon\'s power, speed and intelligence was greater.\n");
}
~basic_string(local_120);
return 0;
}
So we can see that it prompts us for input here:
fgets((char *)input,0x101,stdin);
Looking through the code, we can see that it really doesn't do much input checking. It just passes our input to start_quest
, and checks to see if it's output is 0x1337
(which we will need to figure out how to make that happen to solve this challenge). Also the disassembly shows that our input isn't passed, however that is wrong. We can see that in gdb our input is passed:
Breakpoint 1, 0x000000000040e261 in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x0
$rbx : 0x0
$rcx : 0xa38323735333935 ("5935728\n"?)
$rdx : 0x0
$rsp : 0x00007fffffffdd90 → 0x0000000000000000
$rbp : 0x00007fffffffdf50 → 0x000000000040e5b0 → <__libc_csu_init+0> push r15
$rsi : 0x00007fffffffde38 → 0x00000000006236a8 → "15935728"
$rdi : 0x00007fffffffde18 → 0x00000000006236a8 → "15935728"
$rip : 0x000000000040e261 → <main+321> call 0x404350 <_Z11start_questSs>
$r8 : 0x00000000006236a8 → "15935728"
$r9 : 0x00007ffff7a7ff40 → 0x00007ffff7a7ff40 → [loop detected]
$r10 : 0x6
$r11 : 0x00007ffff7ebd150 → <std::basic_string<char,+0> push rbx
$r12 : 0x00000000004013bb → <_start+0> xor ebp, ebp
$r13 : 0x00007fffffffe030 → 0x0000000000000001
$r14 : 0x0
$r15 : 0x0
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdd90│+0x0000: 0x0000000000000000 ← $rsp
0x00007fffffffdd98│+0x0008: 0x0000000000000000
0x00007fffffffdda0│+0x0010: 0x0000000000000000
0x00007fffffffdda8│+0x0018: 0x0000000000000000
0x00007fffffffddb0│+0x0020: 0x0000000000000000
0x00007fffffffddb8│+0x0028: 0x00007fffffffde30 → 0x0000000000000000
0x00007fffffffddc0│+0x0030: 0x00007fffffffde40 → "15935728"
0x00007fffffffddc8│+0x0038: 0x00007fffffffde40 → "15935728"
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x40e250 <main+304> call 0x400f20 <_ZNSsC1ERKSs@plt>
0x40e255 <main+309> jmp 0x40e25a <main+314>
0x40e25a <main+314> lea rdi, [rbp-0x138]
→ 0x40e261 <main+321> call 0x404350 <_Z11start_questSs>
↳ 0x404350 <start_quest(std::string)+0> push rbp
0x404351 <start_quest(std::string)+1> mov rbp, rsp
0x404354 <start_quest(std::string)+4> push r15
0x404356 <start_quest(std::string)+6> push r14
0x404358 <start_quest(std::string)+8> push rbx
0x404359 <start_quest(std::string)+9> sub rsp, 0x78
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
_Z11start_questSs (
$rdi = 0x00007fffffffde18 → 0x00000000006236a8 → "15935728",
$rsi = 0x00007fffffffde38 → 0x00000000006236a8 → "15935728"
)
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "wyvern", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x40e261 → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤
start_quest
So that brings us to the start_quest
function:
/* start_quest(std::basic_string<char, std::char_traits<char>, std::allocator<char>>) */
ulong start_quest(basic_string param_1)
{
undefined *puVar1;
uint *puVar2;
long inputLength;
undefined *this;
undefined *puVar3;
undefined auStack152 [8];
undefined8 local_90;
uint local_50;
bool lenCheck;
puVar3 = auStack152;
puVar1 = auStack152;
if ((x25 * (x25 + -1) & 1U) == 0 || y26 < 10) goto LAB_004043a4;
do {
puVar3 = puVar1;
*(undefined8 *)(puVar3 + -8) = 0x404c2c;
push_back(hero,&secret_100,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404c45;
push_back(hero,&secret_214,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404c5e;
push_back(hero,&secret_266,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404c77;
push_back(hero,&secret_369,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404c90;
push_back(hero,&secret_417,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404ca9;
push_back(hero,&secret_527,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404cc2;
push_back(hero,&secret_622,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404cdb;
push_back(hero,&secret_733,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404cf4;
push_back(hero,&secret_847,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404d0d;
push_back(hero,&secret_942,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404d26;
push_back(hero,&secret_1054,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404d3f;
push_back(hero,&secret_1106,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404d58;
push_back(hero,&secret_1222,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404d71;
push_back(hero,&secret_1336,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404d8a;
push_back(hero,&secret_1441,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404da3;
push_back(hero,&secret_1540,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404dbc;
push_back(hero,&secret_1589,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404dd5;
push_back(hero,&secret_1686,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404dee;
push_back(hero,&secret_1796,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404e07;
push_back(hero,&secret_1891,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404e20;
push_back(hero,&secret_1996,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404e39;
push_back(hero,&secret_2112,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404e52;
push_back(hero,&secret_2165,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404e6b;
push_back(hero,&secret_2260,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404e84;
push_back(hero,&secret_2336,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404e9d;
push_back(hero,&secret_2412,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404eb6;
push_back(hero,&secret_2498,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404ecf;
push_back(hero,&secret_2575,puVar3[-8]);
*(undefined8 *)(puVar3 + -8) = 0x404ed8;
local_90 = length(puVar3[-8]);
LAB_004043a4:
puVar2 = (uint *)(puVar3 + -0x10);
this = puVar3 + -0x20;
*(undefined8 *)(puVar3 + -0x48) = 0x4043f5;
push_back(hero,&secret_100,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40440e;
push_back(hero,&secret_214,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404427;
push_back(hero,&secret_266,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404440;
push_back(hero,&secret_369,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404459;
push_back(hero,&secret_417,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404472;
push_back(hero,&secret_527,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40448b;
push_back(hero,&secret_622,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4044a4;
push_back(hero,&secret_733,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4044bd;
push_back(hero,&secret_847,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4044d6;
push_back(hero,&secret_942,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4044ef;
push_back(hero,&secret_1054,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404508;
push_back(hero,&secret_1106,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404521;
push_back(hero,&secret_1222,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40453a;
push_back(hero,&secret_1336,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404553;
push_back(hero,&secret_1441,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40456c;
push_back(hero,&secret_1540,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404585;
push_back(hero,&secret_1589,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40459e;
push_back(hero,&secret_1686,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4045b7;
push_back(hero,&secret_1796,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4045d0;
push_back(hero,&secret_1891,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4045e9;
push_back(hero,&secret_1996,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404602;
push_back(hero,&secret_2112,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40461b;
push_back(hero,&secret_2165,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404634;
push_back(hero,&secret_2260,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40464d;
push_back(hero,&secret_2336,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404666;
push_back(hero,&secret_2412,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40467f;
push_back(hero,&secret_2498,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404698;
push_back(hero,&secret_2575,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4046a1;
inputLength = length(puVar3[-0x48]);
lenCheck = inputLength + -1 != (long)(legend >> 2);
puVar1 = puVar3 + -0x40;
} while ((x25 * (x25 + -1) & 1U) != 0 && 9 < y26);
if (lenCheck) {
if ((x25 * (x25 + -1) & 1U) == 0 || y26 < 10) goto LAB_00404760;
do {
*puVar2 = legend >> 2;
LAB_00404760:
*puVar2 = legend >> 2;
} while ((x25 * (x25 + -1) & 1U) != 0 && 9 < y26);
}
else {
if ((x25 * (x25 + -1) & 1U) == 0 || y26 < 10) goto LAB_004047fb;
do {
*(undefined8 *)(puVar3 + -0x48) = 0x404f06;
basic_string(this,puVar3[-0x48]);
LAB_004047fb:
*(undefined8 *)(puVar3 + -0x48) = 0x404808;
basic_string(this,puVar3[-0x48]);
} while ((x25 * (x25 + -1) & 1U) != 0 && 9 < y26);
/* try { // try from 0040484b to 00404853 has its CatchHandler @ 004048fb */
*(undefined8 *)(puVar3 + -0x48) = 0x404854;
local_50 = sanitize_input((char)this,puVar3[-0x48]);
if ((x25 * (x25 + -1) & 1U) == 0 || y26 < 10) goto LAB_0040489f;
do {
*puVar2 = local_50;
*(undefined8 *)(puVar3 + -0x48) = 0x404f1d;
~basic_string(this,puVar3[-0x48]);
LAB_0040489f:
*puVar2 = local_50;
*(undefined8 *)(puVar3 + -0x48) = 0x4048b1;
~basic_string(this,puVar3[-0x48]);
} while ((x25 * (x25 + -1) & 1U) != 0 && 9 < y26);
}
do {
} while ((x25 * (x25 + -1) & 1U) != 0 && 9 < y26);
return (ulong)*puVar2;
}
So looking at this code, it becomes apparant that it has been obfuscated. Obfuscating code means that it has essentially been made harder to reverse and understand what it does. Throughout this code, we see a lot of code segments like this:
((x25 * (x25 + -1) & 1U) == 0 || y26 < 10)
and this:
while ((x25 * (x25 + -1) & 1U) != 0 && 9 < y26)
This is a part of the obfuscation. Thing is, in these statements they reference variables like x25
and y26
. The thing is, these variables are never given a non-zero value. That way their value is 0
. As a result this expression:
((x25 * (x25 + -1) & 1U) == 0 || y26 < 10)
really means this:
((0 * (0 + -1) & 1U) == 0 || 0 < 10)
So realistically, these statements are just a complicated way of stating things like if (true)
. These statements evaluate to the following:
((x25 * (x25 + -1) & 1U) == 0 || y26 < 10)
^ evaluates to true
((x25 * (x25 + -1) & 1U) != 0 && 9 < y26)
^ evaluates to false
So going through and editing the code (I just did this in a text editor) to remove some of the obfuscation, we are left with this:
/* start_quest(std::basic_string<char, std::char_traits<char>, std::allocator<char>>) */
ulong start_quest(basic_string param_1)
{
undefined *puVar1;
uint *puVar2;
long inputLength;
undefined *this;
undefined *puVar3;
undefined auStack152 [8];
undefined8 local_90;
uint local_50;
bool lenCheck;
puVar3 = auStack152;
puVar1 = auStack152;
LAB_004043a4:
puVar2 = (uint *)(puVar3 + -0x10);
this = puVar3 + -0x20;
*(undefined8 *)(puVar3 + -0x48) = 0x4043f5;
push_back(hero,&secret_100,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40440e;
push_back(hero,&secret_214,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404427;
push_back(hero,&secret_266,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404440;
push_back(hero,&secret_369,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404459;
push_back(hero,&secret_417,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404472;
push_back(hero,&secret_527,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40448b;
push_back(hero,&secret_622,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4044a4;
push_back(hero,&secret_733,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4044bd;
push_back(hero,&secret_847,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4044d6;
push_back(hero,&secret_942,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4044ef;
push_back(hero,&secret_1054,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404508;
push_back(hero,&secret_1106,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404521;
push_back(hero,&secret_1222,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40453a;
push_back(hero,&secret_1336,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404553;
push_back(hero,&secret_1441,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40456c;
push_back(hero,&secret_1540,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404585;
push_back(hero,&secret_1589,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40459e;
push_back(hero,&secret_1686,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4045b7;
push_back(hero,&secret_1796,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4045d0;
push_back(hero,&secret_1891,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4045e9;
push_back(hero,&secret_1996,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404602;
push_back(hero,&secret_2112,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40461b;
push_back(hero,&secret_2165,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404634;
push_back(hero,&secret_2260,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40464d;
push_back(hero,&secret_2336,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404666;
push_back(hero,&secret_2412,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x40467f;
push_back(hero,&secret_2498,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x404698;
push_back(hero,&secret_2575,puVar3[-0x48]);
*(undefined8 *)(puVar3 + -0x48) = 0x4046a1;
inputLength = length(puVar3[-0x48]);
lenCheck = inputLength + -1 != (long)(legend >> 2);
puVar1 = puVar3 + -0x40;
if (lenCheck) {
}
else {
/* try { // try from 0040484b to 00404853 has its CatchHandler @ 004048fb */
*(undefined8 *)(puVar3 + -0x48) = 0x404854;
local_50 = sanitize_input((char)this,puVar3[-0x48]);
if ((x25 * (x25 + -1) & 1U) == 0 || y26 < 10) goto LAB_0040489f;
*puVar2 = local_50;
*(undefined8 *)(puVar3 + -0x48) = 0x404f1d;
~basic_string(this,puVar3[-0x48]);
LAB_0040489f:
*puVar2 = local_50;
*(undefined8 *)(puVar3 + -0x48) = 0x4048b1;
~basic_string(this,puVar3[-0x48]);
}
return (ulong)*puVar2;
}
This looks much readable. Starting off we see 28
calls to push_back
. Looking at the calls in gdb tell us roughly what they do:
Before the call:
Breakpoint 1, 0x0000000000404409 in start_quest(std::string) ()
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────── registers ────
$rax : 0x0
$rbx : 0x0
$rcx : 0x0
$rdx : 0xffffffff
$rsp : 0x00007fffffffdcd0 → 0x0000000000000000
$rbp : 0x00007fffffffdda0 → 0x00007fffffffdf70 → 0x000000000040e5b0 → <__libc_csu_init+0> push r15
$rsi : 0x0000000000610140 → 0x0000010a000000d6
$rdi : 0x00000000006102f8 → 0x00000000006236c0 → 0x0000000000000064 ("d"?)
$rip : 0x0000000000404409 → <start_quest(std::string)+185> call 0x405750 <_ZNSt6vectorIiSaIiEE9push_backERKi>
$r8 : 0x0
$r9 : 0xffffffff
$r10 : 0x1
$r11 : 0xffffff01
$r12 : 0x00000000004013bb → <_start+0> xor ebp, ebp
$r13 : 0x00007fffffffe050 → 0x0000000000000001
$r14 : 0x0
$r15 : 0x0
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
──────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdcd0│+0x0000: 0x0000000000000000 ← $rsp
0x00007fffffffdcd8│+0x0008: 0x0000000000000000
0x00007fffffffdce0│+0x0010: 0x0000000000000000
0x00007fffffffdce8│+0x0018: 0x0000000000000000
0x00007fffffffdcf0│+0x0020: 0x0000000000000000
0x00007fffffffdcf8│+0x0028: 0x0000000000000000
0x00007fffffffdd00│+0x0030: 0x0000000000000000
0x00007fffffffdd08│+0x0038: 0x0000000000000000
────────────────────────────────────────────────────────────── code:x86:64 ────
0x4043f0 <start_quest(std::string)+160> call 0x405750 <_ZNSt6vectorIiSaIiEE9push_backERKi>
0x4043f5 <start_quest(std::string)+165> movabs rdi, 0x6102f8
0x4043ff <start_quest(std::string)+175> movabs rsi, 0x610140
→ 0x404409 <start_quest(std::string)+185> call 0x405750 <_ZNSt6vectorIiSaIiEE9push_backERKi>
↳ 0x405750 <std::vector<int,+0> push rbp
0x405751 <std::vector<int,+0> mov rbp, rsp
0x405754 <std::vector<int,+0> push r15
0x405756 <std::vector<int,+0> push r14
0x405758 <std::vector<int,+0> push rbx
0x405759 <std::vector<int,+0> sub rsp, 0x38
────────────────────────────────────────────────────── arguments (guessed) ────
_ZNSt6vectorIiSaIiEE9push_backERKi (
$rdi = 0x00000000006102f8 → 0x00000000006236c0 → 0x0000000000000064 ("d"?),
$rsi = 0x0000000000610140 → 0x0000010a000000d6
)
────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "wyvern", stopped, reason: BREAKPOINT
──────────────────────────────────────────────────────────────────── trace ────
[#0] 0x404409 → start_quest(std::string)()
[#1] 0x40e266 → main()
───────────────────────────────────────────────────────────────────────────────
gef➤
With the next call, we see this:
0x0000000000404422 in start_quest(std::string) ()
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────── registers ────
$rax : 0x0
$rbx : 0x0
$rcx : 0x0
$rdx : 0xffffffff
$rsp : 0x00007fffffffdcd0 → 0x0000000000000000
$rbp : 0x00007fffffffdda0 → 0x00007fffffffdf70 → 0x000000000040e5b0 → <__libc_csu_init+0> push r15
$rsi : 0x0000000000610144 → 0x000001710000010a
$rdi : 0x00000000006102f8 → 0x00000000006236e0 → 0x000000d600000064 ("d"?)
$rip : 0x0000000000404422 → <start_quest(std::string)+210> call 0x405750 <_ZNSt6vectorIiSaIiEE9push_backERKi>
$r8 : 0x0
$r9 : 0xffffffff
$r10 : 0x1
$r11 : 0xffffff01
$r12 : 0x00000000004013bb → <_start+0> xor ebp, ebp
$r13 : 0x00007fffffffe050 → 0x0000000000000001
$r14 : 0x0
$r15 : 0x0
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
──────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdcd0│+0x0000: 0x0000000000000000 ← $rsp
0x00007fffffffdcd8│+0x0008: 0x0000000000000000
0x00007fffffffdce0│+0x0010: 0x0000000000000000
0x00007fffffffdce8│+0x0018: 0x0000000000000000
0x00007fffffffdcf0│+0x0020: 0x0000000000000000
0x00007fffffffdcf8│+0x0028: 0x0000000000000000
0x00007fffffffdd00│+0x0030: 0x0000000000000000
0x00007fffffffdd08│+0x0038: 0x0000000000000000
────────────────────────────────────────────────────────────── code:x86:64 ────
0x404409 <start_quest(std::string)+185> call 0x405750 <_ZNSt6vectorIiSaIiEE9push_backERKi>
0x40440e <start_quest(std::string)+190> movabs rdi, 0x6102f8
0x404418 <start_quest(std::string)+200> movabs rsi, 0x610144
→ 0x404422 <start_quest(std::string)+210> call 0x405750 <_ZNSt6vectorIiSaIiEE9push_backERKi>
↳ 0x405750 <std::vector<int,+0> push rbp
0x405751 <std::vector<int,+0> mov rbp, rsp
0x405754 <std::vector<int,+0> push r15
0x405756 <std::vector<int,+0> push r14
0x405758 <std::vector<int,+0> push rbx
0x405759 <std::vector<int,+0> sub rsp, 0x38
────────────────────────────────────────────────────── arguments (guessed) ────
_ZNSt6vectorIiSaIiEE9push_backERKi (
$rdi = 0x00000000006102f8 → 0x00000000006236e0 → 0x000000d600000064 ("d"?),
$rsi = 0x0000000000610144 → 0x000001710000010a
)
────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "wyvern", stopped, reason: SINGLE STEP
──────────────────────────────────────────────────────────────────── trace ────
[#0] 0x404422 → start_quest(std::string)()
[#1] 0x40e266 → main()
───────────────────────────────────────────────────────────────────────────────
gef➤
So we can see that it is essentially writing one byte of data to an array. Each byte is written to the lowest byte of a four byte segment. We can also see that the byte being written matches the secret
value with the call. So essentially this is just making an array of 28
bytes, where each byte is stored in a 4
byte segment.
After that we have a check for the length of our input:
inputLength = length(puVar3[-0x48]);
lenCheck = inputLength + -1 != (long)(legend >> 2);
puVar1 = puVar3 + -0x40;
if (lenCheck) {
}
We can see that the value of legend
is 0x73
:
legend XREF[5]: Entry Point(*),
sanitize_input:00401ece(R),
start_quest:004046a7(R),
start_quest:00404760(R),
start_quest:00404ee4(R)
00610138 73 00 00 00 undefined4 00000073h
0x73 >> 2 = 28
, which also corresponds to the number of push_back
calls made earlier. So our input has to be 28
bytes (not counting the null byte). The final portion of the code runs the sanitize_input
function, and essentially just returns the value of it. The rest of the checks will happen in that function:
local_50 = sanitize_input((char)this,puVar3[-0x48]);
Transfers data:
*puVar2 = local_50;
Returns it:
return (ulong)*puVar2;
Sanitize Input
Looking at sanitize_input
function initially, we see this:
/* sanitize_input(std::basic_string<char, std::char_traits<char>, std::allocator<char>>) */
ulong sanitize_input(basic_string param_1)
{
uint uVar1;
uint *puVar2;
undefined4 *this;
undefined4 *puVar3;
undefined4 *puVar4;
undefined7 in_register_00000039;
bool bVar5;
undefined auStack392 [24];
undefined4 *local_170;
uint local_144;
basic_ostream *local_140;
bool local_136;
bool local_135;
bool local_134;
bool local_133;
bool local_132;
uint *local_108;
long local_100;
uint local_f8;
bool local_f2;
bool local_f1;
int local_f0;
bool local_e9;
int local_e8;
bool local_e1;
int *local_e0;
long local_d8;
bool local_ca;
bool local_c9;
undefined8 local_c8;
bool local_b9;
long local_b8;
bool local_a9;
char *local_a8;
bool local_99;
long local_98;
bool local_8a;
bool local_89;
undefined4 *local_88;
uint *local_80;
undefined4 *local_78;
undefined4 *local_70;
int *local_68;
uint *i;
puVar3 = (undefined4 *)auStack392;
puVar4 = (undefined4 *)auStack392;
do {
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
if ((x17 * (x17 + -1) & 1U) == 0 || y18 < 10) goto LAB_00401da6;
do {
puVar3 = puVar4 + -0x10;
*(undefined8 *)(puVar4 + -0x12) = 0x403db1;
local_170 = puVar3;
vector(puVar4 + -0xc,*(undefined *)(puVar4 + -0x12));
*local_170 = 0;
LAB_00401da6:
puVar2 = puVar3 + -4;
this = puVar3 + -0xc;
i = puVar3 + -0x10;
local_68 = puVar3 + -0x14;
local_78 = puVar3 + -0x1c;
local_80 = puVar3 + -0x20;
local_88 = puVar3 + -0x28;
puVar4 = puVar3 + -0x2c;
*(undefined8 *)(puVar3 + -0x2e) = 0x401e2c;
local_70 = puVar4;
vector(this,*(undefined *)(puVar3 + -0x2e));
*i = 0;
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
while( true ) {
do {
local_89 = (int)*i < legend >> 2;
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
if (!local_89) goto LAB_00403729;
do {
local_8a = (x17 * (x17 + -1) & 1U) == 0 || y18 < 10;
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
do {
do {
local_98 = (long)(int)*i;
bVar5 = (x17 * (x17 + -1) & 1U) == 0;
local_99 = bVar5 || y18 < 10;
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
} while (!bVar5 && y18 >= 10);
do {
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
/* try { // try from 0040217d to 00402879 has its CatchHandler @ 00402e05 */
*(undefined8 *)(puVar3 + -0x2e) = 0x40218d;
local_a8 = (char *)operator[](CONCAT71(in_register_00000039,param_1),local_98,
*(undefined *)(puVar3 + -0x2e));
if ((x17 * (x17 + -1) & 1U) == 0 || y18 < 10) goto LAB_004021dc;
do {
if ((x3 * (x3 + -1) & 1U) == 0 || y4 < 10) goto LAB_00403e10;
do {
*local_68 = (int)*local_a8;
LAB_00403e10:
*local_68 = (int)*local_a8;
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
LAB_004021dc:
*local_68 = (int)*local_a8;
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
do {
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
*(undefined8 *)(puVar3 + -0x2e) = 0x4022c4;
push_back(this,local_68,*(undefined *)(puVar3 + -0x2e));
do {
local_a9 = (x17 * (x17 + -1) & 1U) == 0 || y18 < 10;
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
if (local_a9) goto LAB_0040239d;
do {
*local_80 = *i;
LAB_0040239d:
if ((x3 * (x3 + -1) & 1U) == 0 || y4 < 10) goto LAB_004023e0;
do {
*local_80 = *i;
LAB_004023e0:
*local_80 = *i;
local_b8 = (long)(int)*local_80;
bVar5 = (x17 * (x17 + -1) & 1U) == 0;
local_b9 = bVar5 || y18 < 10;
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
} while (!bVar5 && y18 >= 10);
*(undefined8 *)(puVar3 + -0x2e) = 0x40249a;
local_c8 = length(*(undefined *)(puVar3 + -0x2e));
do {
local_c9 = (x17 * (x17 + -1) & 1U) == 0 || y18 < 10;
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
uVar1 = (uint)((ulong)local_c8 >> 0x20);
if (local_c9) goto LAB_0040257a;
do {
*local_80 = (uint)local_b8 & uVar1 >> 8 | 0x1c;
LAB_0040257a:
*local_80 = (uint)local_b8 & uVar1 >> 8 | 0x1c;
local_ca = *local_80 != 0;
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
do {
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
if (local_ca) {
do {
local_d8 = (long)(int)*i;
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
*(undefined8 *)(puVar3 + -0x2e) = 0x402739;
local_e0 = (int *)operator[](hero,local_d8,*(undefined *)(puVar3 + -0x2e));
do {
local_e1 = (x17 * (x17 + -1) & 1U) == 0 || y18 < 10;
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
do {
local_e8 = *local_e0;
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
*(undefined8 *)(puVar3 + -0x2e) = 0x40287a;
vector(local_88,this,*(undefined *)(puVar3 + -0x2e));
do {
local_e9 = (x17 * (x17 + -1) & 1U) == 0 || y18 < 10;
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
do {
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
do {
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
/* try { // try from 00402a1c to 00402a24 has its CatchHandler @ 00402f44 */
*(undefined8 *)(puVar3 + -0x2e) = 0x402a25;
local_f0 = transform_input((int)local_88,*(undefined *)(puVar3 + -0x2e));
if ((x17 * (x17 + -1) & 1U) == 0 || y18 < 10) goto LAB_00402a73;
do {
do {
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
LAB_00402a73:
local_f1 = local_e8 == local_f0;
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
do {
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
/* try { // try from 00402b58 to 00402d49 has its CatchHandler @ 00402e05 */
*(undefined8 *)(puVar3 + -0x2e) = 0x402b61;
~vector(local_88,*(undefined *)(puVar3 + -0x2e));
do {
local_f2 = (x17 * (x17 + -1) & 1U) == 0 || y18 < 10;
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
do {
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
if ((local_f1 & 1U) != 0) {
do {
local_f8 = *local_80;
local_100 = (long)(int)*i;
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
*(undefined8 *)(puVar3 + -0x2e) = 0x402d4a;
local_108 = (uint *)operator[](hero,local_100,*(undefined *)(puVar3 + -0x2e));
if ((x17 * (x17 + -1) & 1U) == 0 || y18 < 10) goto LAB_00402d99;
do {
*local_80 = (uint)((int)(local_f8 & *local_108) < 0);
LAB_00402d99:
*local_80 = (uint)((int)(local_f8 & *local_108) < 0);
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
}
if ((x17 * (x17 + -1) & 1U) == 0 || y18 < 10) goto LAB_0040315a;
do {
do {
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
LAB_0040315a:
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
}
do {
do {
local_132 = *local_80 != 0;
bVar5 = (x17 * (x17 + -1) & 1U) == 0;
local_133 = bVar5 || y18 < 10;
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
} while (!bVar5 && y18 >= 10);
do {
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
if (local_132) break;
do {
local_135 = (x17 * (x17 + -1) & 1U) == 0 || y18 < 10;
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
do {
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
do {
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
if ((x17 * (x17 + -1) & 1U) == 0 || y18 < 10) goto LAB_0040368e;
do {
*i = *i + 1;
LAB_0040368e:
*i = *i + 1;
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
}
do {
local_134 = (x17 * (x17 + -1) & 1U) == 0 || y18 < 10;
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
if (local_134) goto LAB_0040343d;
do {
*puVar2 = (*i & 1) << 8;
*local_70 = 1;
LAB_0040343d:
*puVar2 = (*i & 1) << 8;
*local_70 = 1;
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
LAB_004038bd:
if ((x17 * (x17 + -1) & 1U) == 0 || y18 < 10) goto LAB_00403900;
do {
*(undefined8 *)(puVar3 + -0x2e) = 0x40411c;
~vector(this,*(undefined *)(puVar3 + -0x2e));
LAB_00403900:
*(undefined8 *)(puVar3 + -0x2e) = 0x403909;
~vector(this,*(undefined *)(puVar3 + -0x2e));
local_144 = *puVar2;
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
do {
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
return (ulong)local_144;
LAB_00403729:
do {
local_136 = (x17 * (x17 + -1) & 1U) == 0 || y18 < 10;
} while ((x3 * (x3 + -1) & 1U) != 0 && 9 < y4);
do {
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
/* try { // try from 004037fd to 0040380f has its CatchHandler @ 00402e05 */
*(undefined8 *)(puVar3 + -0x2e) = 0x403810;
local_140 = operator<<<std--char_traits<char>>(cout,"success\n",*(undefined *)(puVar3 + -0x2e));
if ((x17 * (x17 + -1) & 1U) == 0 || y18 < 10) goto LAB_0040385f;
do {
*puVar2 = 0x1337;
*local_70 = 1;
LAB_0040385f:
*puVar2 = 0x1337;
*local_70 = 1;
} while ((x17 * (x17 + -1) & 1U) != 0 && 9 < y18);
goto LAB_004038bd;
}
So let's start going through this. First we can see that there is an iteration counter, which is initialized here:
*i = 0;
You can see it checked here. It checks to see if it is greater than 28
:
lenCheck = (int)*i < legend >> 2;
if (!lenCheck) goto LAB_00403729;
And it is incremented here:
*i = *i + 1;
Checking LAB_00403729
, we see that it is probably the code path we want to take in order to solve the challenge:
LAB_00403729:
*(undefined8 *)(puVar3 + -0x2e) = 0x403810;
local_140 = operator<<<std--char_traits<char>>(cout,"success\n",*(undefined *)(puVar3 + -0x2e));
if ((x17 * (x17 + -1) & 1U) == 0 || y18 < 10) goto LAB_0040385f;
*puVar2 = 0x1337;
*local_70 = 1;
LAB_0040385f:
*puVar2 = 0x1337;
*local_70 = 1;
goto LAB_004038bd;
In order to execute that code path, we will need to run this loop 28
times.
Later on, we can see that the actual check it performs is here:
passedCheck = heroValue == transformedValue;
The first time we hit the check, it looks like it just checking the first character of our input against the first hero
value:
gef➤ b *0x402a7f
Breakpoint 1 at 0x402a7f
gef➤ r
Starting program: /Hackery/pod/modules/obfuscated_reversing/csaw15_wyvern/wyvern
+-----------------------+
| Welcome Hero |
+-----------------------+
[!] Quest: there is a dragon prowling the domain.
brute strength and magic is our only hope. Test your skill.
Enter the dragon's secret: d000000000000000000000000000
Breakpoint 1, 0x0000000000402a7f in sanitize_input(std::string) ()
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────── registers ────
$rax : 0x64
$rbx : 0x0
$rcx : 0x64
$rdx : 0xffffffff
$rsp : 0x00007fffffffda70 → 0x0000000000000000
$rbp : 0x00007fffffffdca0 → 0x00007fffffffdd80 → 0x00007fffffffdf50 → 0x000000000040e5b0 → <__libc_csu_init+0> push r15
$rsi : 0xffffff01
$rdi : 0x1
$rip : 0x0000000000402a7f → <sanitize_input(std::string)+3519> cmp eax, ecx
$r8 : 0x1
$r9 : 0xffffffff
$r10 : 0x1
$r11 : 0x1
$r12 : 0x0000000000401301 → <_GLOBAL__sub_I_wyvern.cpp+81> mov eax, DWORD PTR ds:0x610420
$r13 : 0x00007fffffffe001 → 0x3000000000004013
$r14 : 0x0
$r15 : 0xffffffff
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
─────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffda70│+0x0000: 0x0000000000000000 ← $rsp
0x00007fffffffda78│+0x0008: 0x0000000000000000
0x00007fffffffda80│+0x0010: 0x00000000006236f0 → 0x0000000000000064 ("d"?)
0x00007fffffffda88│+0x0018: 0x00000000006236f4 → 0x0000000000000000
0x00007fffffffda90│+0x0020: 0x00000000006236f4 → 0x0000000000000000
0x00007fffffffda98│+0x0028: 0x0000000000000000
0x00007fffffffdaa0│+0x0030: 0x000000000000001c
0x00007fffffffdaa8│+0x0038: 0x0000000000000000
───────────────────────────────────────────────────────────── code:x86:64 ────
0x402a6e <sanitize_input(std::string)+3502> jmp 0x403eb3 <_Z14sanitize_inputSs+8691>
0x402a73 <sanitize_input(std::string)+3507> mov eax, DWORD PTR [rbp-0xe0]
0x402a79 <sanitize_input(std::string)+3513> mov ecx, DWORD PTR [rbp-0xe8]
→ 0x402a7f <sanitize_input(std::string)+3519> cmp eax, ecx
0x402a81 <sanitize_input(std::string)+3521> sete dl
0x402a84 <sanitize_input(std::string)+3524> mov esi, DWORD PTR ds:0x610594
0x402a8b <sanitize_input(std::string)+3531> mov edi, DWORD PTR ds:0x610434
0x402a92 <sanitize_input(std::string)+3538> mov r8d, esi
0x402a95 <sanitize_input(std::string)+3541> sub r8d, 0x1
───────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "wyvern", stopped, reason: BREAKPOINT
─────────────────────────────────────────────────────────────────── trace ────
[#0] 0x402a7f → sanitize_input(std::string)()
[#1] 0x404854 → start_quest(std::string)()
[#2] 0x40e266 → main()
──────────────────────────────────────────────────────────────────────────────
gef➤ p $eax
$1 = 0x64
gef➤ p $ecx
$2 = 0x64
gef➤ x/g 0x6102f8
0x6102f8 <hero>: 0x623790
gef➤ x/g 0x623790
0x623790: 0xd600000064
However the second time around, it looks a bit different. It is still checking our input against the hero
value we would expect, however the value our input influences is different from what we would expect:
Breakpoint 1, 0x0000000000402a7f in sanitize_input(std::string) ()
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────── registers ────
$rax : 0xd6
$rbx : 0x0
$rcx : 0x94
$rdx : 0xffffffff
$rsp : 0x00007fffffffda70 → 0x0000000000000000
$rbp : 0x00007fffffffdca0 → 0x00007fffffffdd80 → 0x00007fffffffdf50 → 0x000000000040e5b0 → <__libc_csu_init+0> push r15
$rsi : 0xffffff01
$rdi : 0x1
$rip : 0x0000000000402a7f → <sanitize_input(std::string)+3519> cmp eax, ecx
$r8 : 0x1
$r9 : 0xffffffff
$r10 : 0x1
$r11 : 0x1
$r12 : 0x0000000000401301 → <_GLOBAL__sub_I_wyvern.cpp+81> mov eax, DWORD PTR ds:0x610420
$r13 : 0x00007fffffffe001 → 0x3000000000004013
$r14 : 0x0
$r15 : 0xffffffff
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
─────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffda70│+0x0000: 0x0000000000000000 ← $rsp
0x00007fffffffda78│+0x0008: 0x0000000000000000
0x00007fffffffda80│+0x0010: 0x00000000006236d0 → 0x0000003000000064 ("d"?)
0x00007fffffffda88│+0x0018: 0x00000000006236d8 → 0x0000000000000000
0x00007fffffffda90│+0x0020: 0x00000000006236d8 → 0x0000000000000000
0x00007fffffffda98│+0x0028: 0x0000000000000000
0x00007fffffffdaa0│+0x0030: 0x000000000000001c
0x00007fffffffdaa8│+0x0038: 0x0000000000000000
───────────────────────────────────────────────────────────── code:x86:64 ────
0x402a6e <sanitize_input(std::string)+3502> jmp 0x403eb3 <_Z14sanitize_inputSs+8691>
0x402a73 <sanitize_input(std::string)+3507> mov eax, DWORD PTR [rbp-0xe0]
0x402a79 <sanitize_input(std::string)+3513> mov ecx, DWORD PTR [rbp-0xe8]
→ 0x402a7f <sanitize_input(std::string)+3519> cmp eax, ecx
0x402a81 <sanitize_input(std::string)+3521> sete dl
0x402a84 <sanitize_input(std::string)+3524> mov esi, DWORD PTR ds:0x610594
0x402a8b <sanitize_input(std::string)+3531> mov edi, DWORD PTR ds:0x610434
0x402a92 <sanitize_input(std::string)+3538> mov r8d, esi
0x402a95 <sanitize_input(std::string)+3541> sub r8d, 0x1
───────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "wyvern", stopped, reason: BREAKPOINT
─────────────────────────────────────────────────────────────────── trace ────
[#0] 0x402a7f → sanitize_input(std::string)()
[#1] 0x404854 → start_quest(std::string)()
[#2] 0x40e266 → main()
──────────────────────────────────────────────────────────────────────────────
gef➤ p $eax
$3 = 0xd6
gef➤ p $ecx
$4 = 0x94
Let's see where it comes up with those values. For heroValue
we can see that it grabs it from the hero
array:
heroValueTransfer = (int *)operator[](hero,(long)(int)*puVar4,*(undefined *)(puVar5 + -0x2e));
heroValue = *heroValueTransfer;
In addition to that, when we stop at the check in the debugger, we see that it always has a value that corresponds to hero[i]
where i
is the iteration count. For transformedValue
we see that it is grabbed from here:
transformedValue = transform_input((int)this_00,*(undefined *)(puVar5 + -0x2e));
When we stop at this call in gdb, we see that it's argument is our input stored in the same style as the hero
array.
────────────────────────────────────────────────────────────── code:x86:64 ────
0x402a11 <sanitize_input(std::string)+3409> jne 0x402a1c <_Z14sanitize_inputSs+3420>
0x402a17 <sanitize_input(std::string)+3415> jmp 0x404298 <_Z14sanitize_inputSs+9688>
0x402a1c <sanitize_input(std::string)+3420> mov rdi, QWORD PTR [rbp-0x80]
→ 0x402a20 <sanitize_input(std::string)+3424> call 0x4014b0 <_Z15transform_inputSt6vectorIiSaIiEE>
↳ 0x4014b0 <transform_input(std::vector<int,+0> push rbp
0x4014b1 <transform_input(std::vector<int,+0> mov rbp, rsp
0x4014b4 <transform_input(std::vector<int,+0> push rbx
0x4014b5 <transform_input(std::vector<int,+0> sub rsp, 0x48
0x4014b9 <transform_input(std::vector<int,+0> mov eax, DWORD PTR ds:0x610368
0x4014c0 <transform_input(std::vector<int,+0> mov ecx, DWORD PTR ds:0x610558
────────────────────────────────────────────────────── arguments (guessed) ────
_Z15transform_inputSt6vectorIiSaIiEE (
$rdi = 0x00007fffffffda80 → 0x00000000006236f0 → 0x0000000000000064 ("d"?),
$rsi = 0x0000000000000001,
$rdx = 0x00000000ffffffff,
$rcx = 0x0000000000000000
)
────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "wyvern", stopped, reason: BREAKPOINT
──────────────────────────────────────────────────────────────────── trace ────
[#0] 0x402a20 → sanitize_input(std::string)()
[#1] 0x404854 → start_quest(std::string)()
[#2] 0x40e266 → main()
───────────────────────────────────────────────────────────────────────────────
gef➤
output is 0x64
in eax
. For the second iteration, we have this:
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x402a11 <sanitize_input(std::string)+3409> jne 0x402a1c <_Z14sanitize_inputSs+3420>
0x402a17 <sanitize_input(std::string)+3415> jmp 0x404298 <_Z14sanitize_inputSs+9688>
0x402a1c <sanitize_input(std::string)+3420> mov rdi, QWORD PTR [rbp-0x80]
→ 0x402a20 <sanitize_input(std::string)+3424> call 0x4014b0 <_Z15transform_inputSt6vectorIiSaIiEE>
↳ 0x4014b0 <transform_input(std::vector<int,+0> push rbp
0x4014b1 <transform_input(std::vector<int,+0> mov rbp, rsp
0x4014b4 <transform_input(std::vector<int,+0> push rbx
0x4014b5 <transform_input(std::vector<int,+0> sub rsp, 0x48
0x4014b9 <transform_input(std::vector<int,+0> mov eax, DWORD PTR ds:0x610368
0x4014c0 <transform_input(std::vector<int,+0> mov ecx, DWORD PTR ds:0x610558
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
_Z15transform_inputSt6vectorIiSaIiEE (
$rdi = 0x00007fffffffda80 → 0x00000000006236d0 → 0x0000003000000064 ("d"?),
$rsi = 0x0000000000000001,
$rdx = 0x00000000ffffffff,
$rcx = 0x0000000000000000
)
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "wyvern", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x402a20 → sanitize_input(std::string)()
[#1] 0x404854 → start_quest(std::string)()
[#2] 0x40e266 → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤
Output is 0x94
in the eax
register. We can see a pattern here. The input to this function is a single QWORD that stores two bytes. It then adds those two values and returns whatever the sum is. In the first case that was 0x64 + 0 = 0x64
. For the second case that was 0x64 + 0x30 = 0x94
. So the value that is derived from our input in the compare is essentially inp[i] + inp[i - 1]
(with inp[-1]
being 0
).
So now that we know how exactly our input is influencing the check, we can figure out what input we need to give it to pass everything. Since it is adding our values together, we can just subtract the hero
values in the same manner to undo it. First here is all of the hero
values:
gef➤ x/14g 0x623790
0x623790: 0xd600000064 0x1710000010a
0x6237a0: 0x20f000001a1 0x2dd0000026e
0x6237b0: 0x3ae0000034f 0x4520000041e
0x6237c0: 0x538000004c6 0x604000005a1
0x6237d0: 0x69600000635 0x76300000704
0x6237e0: 0x840000007cc 0x8d400000875
0x6237f0: 0x96c00000920 0xa0f000009c2
When we subtract it:
0x64 - 0x00 = 0x64 'd'
0xd6 - 0x64 = 0x72 'r'
0x10a - 0xd6 = 0x34 '4'
0x171 - 0x10a = 0x67 'g'
0x1a1 - 0x171 = 0x30 '0'
So we can see that this is starting to give us something that looks like a solution. When we script this out, we get this:
hero = [0x0, 0x64, 0xd6, 0x10a, 0x171, 0x1a1, 0x20f, 0x26e, 0x2dd, 0x34f, 0x3ae, 0x41e, 0x452, 0x4c6, 0x538, 0x5a1, 0x604, 0x635, 0x696, 0x704, 0x763, 0x7cc, 0x840, 0x875, 0x8d4, 0x920, 0x96c, 0x9c2, 0xa0f]
flag = ""
for i in range(1, len(hero)):
flag += chr(hero[i] - hero[i - 1])
print "We fought off the dragon: " + flag
When we run it:
$ ./wyvern
+-----------------------+
| Welcome Hero |
+-----------------------+
[!] Quest: there is a dragon prowling the domain.
brute strength and magic is our only hope. Test your skill.
Enter the dragon's secret: dr4g0n_or_p4tric1an_it5_LLVM
success
[+] A great success! Here is a flag{dr4g0n_or_p4tric1an_it5_LLVM}
Just like that, we solved the challenge!