[Pwnable.kr writeups] An attempt to solve Toddler’s bottle

Hello, again. As you probably may know, one of the things that security people enjoy doing in their free time, is play CTFs for fun, or to widen their knowledge and sharpen their skills. I’ve had my share of CTFs too, and in this humble article, I’ll try to publish writeups for the challenges published in Pwnable.kr.

I’ll be updating this article every time i succeed in solving a challenge.

So here it goes.

fd – 1 pt:

In every challenge, there’s a small [somehow funny] hint. In this challenge that hint would be : “Mommy! what is a file descriptor in Linux?”. Evidently, the challenge would be something about a file descriptor: a mistake, vulnerability, a bug, or just something to see if we understand file descriptors.

Side note : A file descriptor in linux, is a handle used to access an input/output resource (file, pipe, network socket, stdin, stdout…) and it’s typed as an integer in C.

Alright, let’s connect to the console and check the source code :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
	if(argc<2){
		printf("pass argv[1] a number\n");
		return 0;
	}
	int fd = atoi( argv[1] ) - 0x1234;
	int len = 0;
	len = read(fd, buf, 32);
	if(!strcmp("LETMEWIN\n", buf)){
		printf("good job :)\n");
		system("/bin/cat flag");
		exit(0);
	}
	printf("learn about Linux file IO\n");
	return 0;

}

In the line int fd = atoi( argv[1] ) - 0x1234; , the variable fd  takes the first argument passed to the program, and substracts from it the hexadecimal value 0x1234 . Later on, the program reads data from the file descriptor fd  and puts it in the variable buf , and then it checks: if the value of buf  equals to “LETMEWIN”, the program will read for us the flag file.

So obviously, we need to control the content of the variable buf , and put in it the string “LETMEWIN”; remember that we do control the fd  variable, and we can make the read()  function, read data from our own file descriptor.

The easiest way to control fd , that instinctively comes to mind, is to make it read input from stdin (i.e the keyboard), and we know that the keyboard’s file descriptor is simply 0, hence all we have to do, is pass the hexadecimal value 0x1234 (equals to 4660 in decimal) as an argument, upon launching the program.

Let’s try that :

fd@ubuntu:~$ ./fd 4660

LETMEWIN

good job :)

And we got our flag πŸ™‚

collision – 3 pt

Moving on to the next level, and from the hint “Daddy told me about cool MD5 hash collision today.”, it looks like we should find a collision of some kind of hash.

Let’s connect to ssh and go straight to the source code :

#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
	int* ip = (int*)p;
	int i;
	int res=0;
	for(i=0; i<5; i++){
		res += ip[i];
	}
	return res;
}

int main(int argc, char* argv[]){
	if(argc<2){
		printf("usage : %s [passcode]\n", argv[0]);
		return 0;
	}
	if(strlen(argv[1]) != 20){
		printf("passcode length should be 20 bytes\n");
		return 0;
	}

	if(hashcode == check_password( argv[1] )){
		system("/bin/cat flag");
		return 0;
	}
	else
		printf("wrong passcode.\n");
	return 0;
}

We can see that the program takes an argument, passes it to the function check_password() , and then compares the result to hashcode , before reading us the flag.

Simply put, in order to read the flag, we need to give the program a passcode that, if hashed with the function check_password(), will be equal to the value 0x21DD09EC .

Let’s try to understand a bit the function  check_password() :

unsigned long check_password(const char* p){
	int* ip = (int*)p;
	int i;
	int res=0;
	for(i=0; i<5; i++){
		res += ip[i];
	}
	return res;
}

Walter cooking up the answer

First of all, we need to notice that the function takes a pointer to an array of chars, and then casts it or converts it to a pointer of an integers array. Why is that important ? Well, we know that a char’s size is one byte, and an int’s size is 4 bytes; so the variable ip  will hold a pointer to a block of 4 chars. For example, if p  originally points to an array of 20 chars, when casted to int*, the variable ip  will now hold a pointer to an array of 5 blocks of 4 “chars” or bytes, that will be interpreted as one integer.

Then, in the for loop, the program calculates the sum of those 5 integers, and returns the result.

So, if we want to pass this level, we need to provide the program with 20 bytes, which will be then converted to 5 blocks of integers, and their sim should be equal to  0x21DD09EC .

To accomplish that, here’s what I did :

I subtracted 4 from each byte, i.e 0x21DD09EC-0x04040404 = 0x1DD905E8

By doing this, I got 8 bytes that would sum up to our hashcode value, but we need 20 bytes… Well, I’ll just divide the 0x04040404  by 4, which will give me : 0x21DD09EC = (0x01010101 * 4) + 0x1DD905E8 .

Since the system is little endian, I need to inject the code inverted. Let’s try that :

col@ubuntu:~$ ./col $(python -c "print 4*'\x01\x01\x01\x01'+'\xe8\x05\xd9\x1d'")
daddy! I just managed to create a hash collision :)

And voila πŸ™‚ we got the flag. That was easy wasn’t it !

bof – 5 pt

The writeup of this level was a bit long, so I wrote it on its own article, check it out here.

flag – 7 pt

Apparently, in this level we have to reverse a binary to get our flag. The hint says “Papa brought me a packed present! let’s open it.”, the important keyword here is packed. So may be the binary is packed, which makes the challenge double layered, we need to unpack, and then try too reverse.

Side note : A packer is usually used to compress and/or encrypt a binary, to make the binary lightweight and more usually hard to reverse. I see packers used mostly on malwares, to make them hard for researchers to understand their internals, and hard for an antivirus to detect.

First of all, let’s check if it indeed packed with any of the known packing tools. We’ll use the strings command line, to extract the strings contained in the binary (duuh!) :

root@kali:~/workspace# strings flag 
UPX!
[...]

$Info: This file is packed with the UPX executable packer http://upx.sf.net $
$Id: UPX 3.08 Copyright (C) 1996-2011 the UPX Team. All Rights Reserved. $

[...]
UPX!
UPX!

If you scroll up and down through that garbage output, you’ll notice some strings that show that this thing is indeed packed with a UPX packer. Luckily, there’s a tool that can unpack this thing automatically, without us having to do any manual unpacking (which was the same tool used to pack it; Awesome!).

Let’s go ahead and unpack the present daddy gave us :

root@kali:~/workspace# upx -d flag

root@kali:~/workspace# ./flag 
I will malloc() and strcpy the flag there. take it.

Alright, after unpacking, we can now reverse the binary without much headaches. When you launch the executable as you can see, it’s nice enough to tell us where it’s putting the flag.

Let’s fire up gdb and hash this thing. Here’s a result of disassembling the main function :

(gdb) disas main
Dump of assembler code for function main:
   0x0000000000401164 <+0>:	push   %rbp
   0x0000000000401165 <+1>:	mov    %rsp,%rbp
   0x0000000000401168 <+4>:	sub    $0x10,%rsp
   0x000000000040116c <+8>:	mov    $0x496658,%edi
   0x0000000000401171 <+13>:	callq  0x402080 <puts>
   0x0000000000401176 <+18>:	mov    $0x64,%edi
   0x000000000040117b <+23>:	callq  0x4099d0 <malloc>
   0x0000000000401180 <+28>:	mov    %rax,-0x8(%rbp)
   0x0000000000401184 <+32>:	mov    0x2c0ee5(%rip),%rdx        # 0x6c2070 <flag>
   0x000000000040118b <+39>:	mov    -0x8(%rbp),%rax
   0x000000000040118f <+43>:	mov    %rdx,%rsi
   0x0000000000401192 <+46>:	mov    %rax,%rdi
   0x0000000000401195 <+49>:	callq  0x400320
   0x000000000040119a <+54>:	mov    $0x0,%eax
   0x000000000040119f <+59>:	leaveq 
   0x00000000004011a0 <+60>:	retq   
End of assembler dump.

We can see that there’s indeed a call to malloc() , and we know that malloc()  returns the address of the allocated memory and puts it in the register EAX, since we’re on a 64bit, it’ll be in the register RAX.

But the thing is, I don’t see where the call to strcpy()  is, so I will try and guess that it’s in the line callq 0x400320 . Let’s try that, and put a break point at the address  0x0000000000401195 :

(gdb) break *0x0000000000401195
Breakpoint 1 at 0x401195

Let’s run the program, and check what we’ll find in the register RAX:

(gdb) run
Starting program: /root/workspace/flag 
I will malloc() and strcpy the flag there. take it.

Breakpoint 1, 0x0000000000401195 in main ()
(gdb) x $rax
0x6c96b0:	0x00000000

So it does contain an address 0x6c96b0 , and the address points to a void part of the memory, that was initialized by malloc.

Let’s step to the next instruction, and check the contents of that address again :

(gdb) nexti
0x000000000040119a in main ()
(gdb) x/1s $rax
0x6c96b0:	"UPX...? sounds like a delivery service :)"
(gdb)

Tada, we got our flag πŸ™‚ It does actually sound like a delivery service hah !

passcode – 10 pt

The writeup of this level was a bit long, so I wrote it on its own article, check it out here.

random – 1 pt

I know, this is only one point, but bare with me. The writeup of this level was a bit long, so I wrote it on its own article, check it out here.

input – 4 pt

The writeup of this level was a bit long, so I wrote it on its own article, check it out here.

leg – 2 pt

In this level, we have two files to analyze so we can clear the level’s executable : a C file, and a complementary ASM file, containing the disassembly of the program in the ARM architecture.

Let’s start with the C file. We have 3 functions from key1()  to key3() that return values calculated in the inline assembly code. We’ll clear this level, if we provide a key that equals the sum of the values returned by the 3 functions.

[...]
	if( (key1()+key2()+key3()) == key ){
		printf("Congratz!\n");
		int fd = open("flag", O_RDONLY);
		char buf[100];
		int r = read(fd, buf, 100);
		write(0, buf, r);
	}
[...]

Let’s start with the first function key1()  :

int key1(){
	asm("mov r3, pc\n");
}

It seems like the register we’re gonna work with is r3 . In this function, the instruction executed moves the value of the pc  register to r3 . Let’s see in the docs what value pc  holds normally in an ARM architecture :

In ARM state, the value of the PC is the address of the current instruction plus 8 bytes.
In Thumb state:
  • For B, BL, CBNZ, and CBZ instructions, the value of the PC is the address of the current instruction plus 4 bytes.
  • For all other instructions that use labels, the value of the PC is the address of the current instruction plus 4 bytes, with bit[1] of the result cleared to 0 to make it word-aligned.

Let’s assume that we are in ARM state, the PC register in this case holds the address of the current instruction plus 8 bytes, which means the address of the instruction after the next instruction. We’re going to use the ASM file now to see where’s that, specifically the key1()  disassembly :

     0x00008cd4 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
     0x00008cd8 <+4>:	add	r11, sp, #0
=>   0x00008cdc <+8>:	mov	r3, pc
     0x00008ce0 <+12>:	mov	r0, r3
pc=> 0x00008ce4 <+16>:	sub	sp, r11, #0
     0x00008ce8 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
     0x00008cec <+24>:	bx	lr

From this, we can conclude that pc  holds the value 0x00008ce4 , which means r3  contains now 0x00008ce4 . Hence key1()  returns 0x00008ce4 .

Let’s see for key2() :

int key2(){
	asm(
	"push	{r6}\n"
	"add	r6, pc, $1\n"
	"bx	r6\n"
	".code   16\n"
	"mov	r3, pc\n"
	"add	r3, $0x4\n"
	"push	{r3}\n"
	"pop	{pc}\n"
	".code	32\n"
	"pop	{r6}\n"
	);
}

 

We can see that again, the value of pc  is moved to r3 , and then we add 0x4 to r3 . Let’s look at the disassembly :

...
     0x00008cfc <+12>:	add	r6, pc, #1
     0x00008d00 <+16>:	bx	r6
=>   0x00008d04 <+20>:	mov	r3, pc
     0x00008d06 <+22>:	adds	r3, #4
pc=> 0x00008d08 <+24>:	push	{r3}
     0x00008d0a <+26>:	pop	{pc}
...

We can conclude that pc  will hold the value 0x00008d08. In the end r3  will hold the value 0x00008d08+0x4 , which means key2()  will return the value 0x00008d0c .

Let’s move on to the final function key3()  :

int key3(){
	asm("mov r3, lr\n");
}

Let’s see what lr  hold normally :

LR, the Link Register
Register R14 is used to store the return address from a subroutine. At other times, LR can be used for other purposes.

As you can see from the definition, lr  holds the return address; since key3()  was called from the main()  function, let’s look at main()‘s disasembly, and see from where was key3() called :

     0x00008d78 <+60>:	add	r4, r4, r3
     0x00008d7c <+64>:	bl	0x8d20 <key3>
lr=> 0x00008d80 <+68>:	mov	r3, r0

We can conclude that lr  contains the value 0x00008d80 , so key3()  will return the same value.

Let’s calculate the some of these 3 values :

$ python -c "print 0x00008ce4+0x00008d0C+0x00008d80"
108400

Seems like the key expected from us is 108400 , let’s try it :

/ $ ./leg
Daddy has very strong arm! : 108400
Congratz!
My daddy has a lot of ARMv5te muscle!
/ $

It worked πŸ™‚ !

mistake – 1 pt

Well the hint says something about operator priority or precedence. It’s as easy as that, you just have to spot where that flow is, and it turns out it’s in line 17 :

if(fd=open("password",O_RDONLY,0400) < 0)

As you can see in the precedence table (http://www.difranco.net/compsci/C_Operator_Precedence_Table.htm) the relational operator < has more priority than than the assignment operator =

By that logic, we can conclude that in this line if(fd=open("password",O_RDONLY,0400) < 0) , comparison will be executed first, and then fd  will be assigned the value 0, which is in fact the file descriptor for the standard input stdin .

Since fd  now is the stdin file descriptor, later on, the read function will actually wait for an input from the keyboard before it even asks for the actual passwor. So all you have to do is enter 0000000000 before the program asks for the password. When the prompt asks you for the password, you just give it 1111111111. Tada you got your flag

2 thoughts on “[Pwnable.kr writeups] An attempt to solve Toddler’s bottle

  1. Hi, thanks for sharing, i’m try to understand collision puzzle, why did you substract 4 form hascode?

    Why if I do hascode/5 = 06c5cec8 and pass this 5 times to ./col got a wrong answer?

    Could please give me some advice?

    Thansk!

    1. Hello friend ! Thanks for reading this πŸ™‚

      As for your question, 4 is the remainder of the division, that why I subtracted 4.

      If you multiply 5 times 06c5cec8 you’ll get 06c5cec8*5 = 21dd09e8, notice that 21dd09e8 is 4 away from the hashcode i.e :
      (hashcode – 21dd09e8) = 4
      And that’s why your proposed answer didn’t quiet work πŸ™‚

      I hope my explanation was clear, feel free to ask me again if you still feel confused or stuck πŸ™‚

      Have a great one !

Leave a Reply

Your email address will not be published. Required fields are marked *