(редактировать: если под «физическим адресом» вы подразумеваете уровень «в котором хранятся мои модули в ОЗУ», то следующий ответ неуместен.)
Вам не нужны привилегии root для этого. Вместо этого вам нужен отладчик. И здесь мы идем (используя систему Linux на x86_64):
Для начала нам нужна небольшая программа для игры. Он обращается к глобальной переменной и печатает ее два раза подряд. У него есть две глобальные переменные, которые мы найдем в памяти позже.
#include <stdio.h>
int a, b = 0;
int main(void)
{
printf("a: ");
if (fscanf("%d", &a) < 1)
return 0;
printf("a = %d\n", myglobal);
printf("b: ");
if (fscanf("%d", &b) < 1)
return 0;
printf("a = %d, b = %d\n", a, b);
return 0;
}
Шаг 1. Скомпилируйте программу и удалите из нее всю отладочную информацию, чтобы мы не получали от отладчика никаких подсказок, которых бы не было в реальной жизни.
$ gcc -s -W -Wall -Os -o ab ab.c
Шаг 2: Запустите программу и введите одно из двух чисел.
$ ./ab
a: 123
a = 123
b: _
Шаг 3: Найдите процесс.
$ ps aux | grep ab
roland 21601 0.0 0.0 3648 456 pts/11 S+ 15:17 0:00 ./ab
roland 21665 0.0 0.0 5132 672 pts/12 S+ 15:18 0:00 grep ab
Шаг 4. Присоедините к процессу отладчик (21601).
$ gdb
...
(gdb) attach 21601
...
(gdb) where
#0 0x00007fdecfdd2970 in read () from /lib/libc.so.6
#1 0x00007fdecfd80b40 in _IO_file_underflow () from /lib/libc.so.6
#2 0x00007fdecfd8230e in _IO_default_uflow () from /lib/libc.so.6
#3 0x00007fdecfd66903 in _IO_vfscanf () from /lib/libc.so.6
#4 0x00007fdecfd7245c in scanf () from /lib/libc.so.6
#5 0x0000000000400570 in ?? ()
#6 0x00007fdecfd2f1a6 in __libc_start_main () from /lib/libc.so.6
#7 0x0000000000400459 in ?? ()
#8 0x00007fffd827da48 in ?? ()
#9 0x000000000000001c in ?? ()
#10 0x0000000000000001 in ?? ()
#11 0x00007fffd827f9a2 in ?? ()
#12 0x0000000000000000 in ?? ()
Интересный кадр - номер 5, поскольку он находится между кодом, вызывающим функцию main
, и функцией scanf
, поэтому это должна быть наша функция main
. Продолжаем сеанс отладки:
(gdb) frame 5
...
(gdb) disassemble $pc $pc+50
...
0x0000000000400570 : test %eax,%eax
0x0000000000400572 : jle 0x40058c <scanf@plt+372>
0x0000000000400574 : mov 0x2003fe(%rip),%edx # 0x600978 <scanf@plt+2098528>
0x000000000040057a : mov 0x2003fc(%rip),%esi # 0x60097c <scanf@plt+2098532>
0x0000000000400580 : mov $0x40068f,%edi
0x0000000000400585 : xor %eax,%eax
0x0000000000400587 : callq 0x4003f8 <printf@plt>
...
Теперь мы знаем, что функция printf
получит три параметра, и два из них находятся всего в четырех байтах друг от друга. Это хороший признак того, что эти две переменные являются нашими a
и b
. Таким образом, адрес a
является 0x600978 или 0x60097c. Давайте узнаем, попробовав:
(gdb) x/w 0x60097c
0x60097c <scanf@plt+2098532>: 0x0000007b
(gdb) x/w 0x600978
0x600978 <scanf@plt+2098528>: 0x00000000
Итак, a
, переменная, которая читается первой, находится по адресу 0x60097c (потому что 0x0000007b - это шестнадцатеричное представление для 123, которое мы ввели), а b
- в 0x600978.
Еще в отладчике мы можем изменить переменную a
, а затем продолжить программу.
(gdb) set *(int *)0x60097c = 1234567
(gdb) continue
Вернитесь в программу, которая попросила нас ввести два числа:
$ ./ab
a: 123
a = 123
b: 5
a = 1234567, b = 5
$