angr callable
Hello You, Today I’m going to explain the callable class in angr and how to use it to turn a c
binary function into a python function and run it in python.
Per angr documentation, “Callable is a representation of a function in the binary that can be interacted with like a native python function.
If you set perform_merge=True (the default), the result will be returned to you, and you can get the result state with callable.result_state.
Otherwise, you can get the resulting simulation manager at callable.result_path_group.”
- Let’s start. I wrote this code and compiled it.
#include <stdio.h>
#include <stdbool.h>
void print_flag(bool param1, bool param2) {
if (param1 && param2) {
printf("ctf{d41d8cd98f00b204e9800998ecf8427e}\n");
} else {
printf("Parameters are not both true.\n");
}
}
int main() {
printf("This code does nothing... except printing this line lol");
return 0;
}
$ file chall
chall: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=9b6548a96efce387d63d77c78b71c13d8bb61100, for GNU/Linux 3.2.0, not stripped
Of course you can solve this challenge in so many ways, like strings or using gdb, etc… , but we are here to learn how to do that using angr.callable
, in the future you may face a bigger problem where you have so many functions and you want to run them all, learning this tool would allow you to do that and much more.
As you can see in the code the function print_flag
is not being called in the main.
Since it’s not stripped
you can see the symbols using nm
command, like so:
$ nm chall
000000000000037c r __abi_tag
0000000000004020 B __bss_start
0000000000004020 b completed.0
w __cxa_finalize@GLIBC_2.2.5
0000000000004010 D __data_start
0000000000004010 W data_start
0000000000001090 t deregister_tm_clones
0000000000001100 t __do_global_dtors_aux
0000000000003dd8 d __do_global_dtors_aux_fini_array_entry
0000000000004018 D __dso_handle
0000000000003de0 d _DYNAMIC
0000000000004020 D _edata
0000000000004028 B _end
00000000000011ac T _fini
0000000000001140 t frame_dummy
0000000000003dd0 d __frame_dummy_init_array_entry
0000000000002188 r __FRAME_END__
0000000000003fe8 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
0000000000002088 r __GNU_EH_FRAME_HDR
0000000000001000 T _init
0000000000002000 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
U __libc_start_main@GLIBC_2.34
000000000000118b T main
U printf@GLIBC_2.2.5
0000000000001149 T print_flag # <----print_flag address----
U puts@GLIBC_2.2.5
00000000000010c0 t register_tm_clones
0000000000001060 T _start
0000000000004020 D __TMC_END__
but we’re not going to copy and paste the address and change its base, etc… we are programmers. we can do it using angr
, let’s start writing the angr
code:
import angr
p = angr.Project('/local/chall')
# Find the address of print_flag
print_flag_addr = p.loader.find_symbol("print_flag").rebased_addr
# In [8]: print_flag_addr
# Out[8]: 4198729
# In [9]: hex(print_flag_addr)
# Out[9]: '0x401149'
# Here we create a callable representation of the print_flag function
# at the specified address print_flag_addr.
print_flag = p.factory.callable(print_flag_addr)
# In [11]: print_flag
# Out[11]: <angr.callable.Callable at 0x7ff3bca12940>
# As you can see in the code it takes two booleans and passes two values here
# that are not zero to get the flag
print_flag(1,1)
# Get the result of print_flag
result = print_flag.result_state.posix.stdout.concretize()
# In [14]: print_flag.result_state.posix.stdout.concretize()
# Out[14]: [b'ctf{d41d8cd98f00b204e9800998ecf8427e}', b'\n']
concretize()
method is used to convert symbolic values to concrete values.
That’s it for today, thanks for reading.