Date: 2023-03-20
This lab aims to play with PLT/GOT table. Your mission is to ask our challenge server to output the lyrics for the well-known pop song Keep the Faith by Michael Jackson (Watch the Video).
Please read the instructions carefully before you implement this lab. You may solve the challenge locally on Apple chip-based machines, but the files you submit to the challenge server must be compiled for x86_64 architecture.
The challenge server can be accessed using the nc
command:
nc up23.zoolab.org 10281
Upon connecting to the challenge server, you must first solve the Proof-of-Work challenge (ref: pow-solver). Then you can follow the instructions to upload your solver implementation, which must be compiled as a shared object (.so
) file. Our challenge server will use LD_PRELOAD
to load your uploaded solver along with the challenge. Therefore, the behavior of the challenge can be controlled by your solver.
Suppose your solver is named libsolver.so
. Once your solver has been uploaded to the server, it will run your solver in a clean Linux runtime environment using the following command.
LD_LIBRARY_PATH=. LD_PRELOAD=./libsolver.so ./chals
To simplify the uploading process, you can use our provided pwntools
python script to solve the pow and upload your solver binary executable. The upload script is available here (view | download). You have to place the pow.py
file in the same directory and invoke the script by passing the path of your solver as the first parameter to the submission script.
This lab is a more complicated one (compared to previous ones), and, therefore, we provide a number of hints for you. We have prepared a modified sample challenge for you to solve locally, and then you can verify your solution on the challenge server. The directions of this lab are listed as follows.
The local challenge only outputs a lot of "A" characters and verifies the checksum. It does not output the lyrics on the server.
-
The source code of the local challenge is available here -
chals
(view) and the required runtime librarylibpoem.so
(header and source). Alternatively, you can download everything from the zip file. You may have to install thelibunwind-dev
package manually to use theMakefile
in the zip directly. -
The
chals
program calls aninit()
function first and then calls thecode_*
functions in the library. Eachcode_*
(or coding) function has a unique ID. It prints a short piece of a message, and combining all the pieces printed from the coding functions can form the whole message. At the end of thechals
, it also verifies if the summation of checksum values returned from the coding functions is correct. -
The implementation of the
chals
somehow calls incorrect coding functions. The objective of your solver is to ensure that thechals
calls to the correct functions. For example, the first coding function called inchals
iscode_498
, but all function calls tocode_498
should becode_44
. To find the correct mappings of coding functions, please refer to thendat
array defined in shuffle.h. It is obvious that the index value of498
in thendat
array is44
. -
It is intuitively that the preloaded solver may hijack some functions to solve this challenge. For example, you can implement
code_498
in your solver and let it actually callcode_44
instead. However, we have the following restrictions to prohibit you from doing this.- Your solver cannot have any
code_*
functions. It means that you cannot do what we just mentioned above. - The
code_*
functions must be called from the main function ofchals
.
- Your solver cannot have any
-
To ensure a function call to
code_498
actually calls tocode_44
, one effective alternative approach is to fill the GOT table entry forcode_498
with the actual address ofcode_44
. Since the server preloads yourlibsolver.so
, you can implement theinit()
function, which is called by thechals
at the beginning, to modify values filled in the GOT table. -
Locating the address of the GOT table in the current running process is tricky. But since we have provided the binary of the
chals
in the zip file, you should be able to find the relative address of themain
function and eachgot
table entries from the binary. The relative addresses can be retrieved bypwntools
using the script.from pwn import * elf = ELF('./chals') print("main =", hex(elf.symbols['main'])) print("{:<12s} {:<8s} {:<8s}".format("Func", "GOT Offset", "Symbol Offset")) for g in elf.got: if "code_" in g: print("{:<12s} {:<8x} {:<8x}".format(g, elf.got[g], elf.symbols[g]))
Once you have the addresses, you can calculate the actual addresses of GOT table entries based on the runtime address of the process. The runtime address can be obtained from
/proc/self/maps
. You may simply read and parse the content of the file to obtain the addresses. One sample snapshot is shown below. Given that the relative address of the main function is0x107a9
and the base address of the/chals
is at0x55c487240000
. We can calculate the actual address of the main function as0x55c4872507a9
. The actual address of the GOT entries can also be obtained similarly.** main = 0x55c4872507a9 55c487240000-55c48724b000 r--p 00000000 00:65 2630559 /chals 55c48724b000-55c487256000 r-xp 0000b000 00:65 2630559 /chals 55c487256000-55c487257000 r--p 00016000 00:65 2630559 /chals 55c487257000-55c487259000 r--p 00016000 00:65 2630559 /chals 55c487259000-55c48725a000 rw-p 00018000 00:65 2630559 /chals
Note that the main function address and the got table addresses of the sample
chals
are exactly the same as the one running on the server. -
If you have pwntools installed, you can use the command
checksec
to inspect thechals
program. The output should beArch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
Note the
Full RELRO
message, which means that the address of coding functions will be resolved upon the execution of the challenge. Therefore, your solver may have to make the region writable by using the mprotect(2) function before you modify the values in the GOT table. -
For obtaining the actual addresses of coding functions, you may also consider using dlopen(3) and dlsym(3) functions. Because the symbol offset on the challenge server may be different from your local one.
-
Not sure if the point is useful for you, but if you are interested in how this challenge is designed, you may read the LZW algorithm.
If you do not have a working x86_64 machine, you can still solve this challenge. Here are some hints for solving this challenge on Apple chip Macs.
-
You have to work in a Linux docker.
-
The executables we packed in the zip file are compiled for x86_64. You can recompile them as
aarch64
binaries. Simply typemake
in the unpackedlab03_pub
directory would work for you. -
Once you have solved the challenge on your machine, you have to compile your solver implementation for x86_64 machines. To do this, install the two additional packages
gcc-multilib-x86-64-linux-gnu
andg++-multilib-x86-64-linux-gnu
, and replace thegcc
(org++
) command withx86_64-linux-gnu-gcc
(orx86_64-linux-gnu-g++
). Sample commands for installing the packages and compilinglibsolver.c
is given below.apt install gcc-multilib-x86-64-linux-gnu g++-multilib-x86-64-linux-gnu x86_64-linux-gnu-gcc -o libsolver.so -shared -fPIC libsolver.c
-
Note that for the solver uploaded to the challenge server, the GOT table entry addresses should be retrieved from the x86_64 version of the
libpoem.so
binary, not your recompiled one. You can always unpack thelibpoem.so
file from the zip file.
-
[30 pts] Your solver works with the local sample challenge. That is, running the command locally would get the
** Good Job
message.LD_LIBRARY_PATH=. LD_PRELOAD=./libsolver.so ./chals
-
[70 pts] Your solver works with the challenge server. That is, submitting your solver implementation (
libsolver.so
) to the remote server can output the correct lyrics on the remote server.
We have an execution time limit for your challenge. You have to solve the challenge within 60s.