0

我什至没有一个问题,我认为这是某种确认,我正确理解了这个主题。
我正在做一些逆向工程研究,这就是我所拥有的。假设我们有结构/类,如下所示:

struct {
   char str[n]
   int x
   float a
}

在我们正在查看的进程的内存中,我们有一个这些结构的数组。

所以,我所拥有的是指向结构指针数组的指针。
如果我错了,现在请你纠正我。要读取该数组第一个元素的 x 值(实际结构,而不是指针),我必须按照以下步骤操作:

  1. 读取指针指向的值(4 个字节)。
  2. 没有任何偏移,读取先前读取的值指向的值,也是 4 个字节(这将引导我到结构开始的地址)
  3. 现在我必须将偏移量添加到等于 n 的值上。并从步骤 2 (step2result+n+1) 的地址中读取值。

我对吗?我会得到第一个结构包含的实际 X 吗?要从第二个获取 X 值,我只需在 step2 中添加偏移量(+4 个字节)?

我认为我做对了,但实际上我无法从指针中访问结构。我会说,指向数组的指针是 100% 正确的。

感谢阅读,期待答案。如果您需要更多信息,请询问。

ps 没有任何东西被破解或任何东西只是为了教育目的

补充:
好的,我试图简化它只是让解释和理解变得更加困难。现在我要尝试修复它。
一种结构描述了游戏中的 NPC 参数。整个结构的大小为 0x1200。前 16 个字节只是 ID 信息,然后在这个信息变成 64 个字节的字符串之后,就是名称。然后去坐标 X/Y/Z。这些之后的一切都无关紧要。
它并不难找到,这是一个屏幕截图:
结构体 /
所以我可以通过在该结构开始的地址上添加或减去 0x1200 来找到其他结构。
我搜索了结构开始的地址并找到了指向该地址的指针。然后我扫描了对找到的指针的访问并得到了类似的东西:

mov [eax+edx*4+00320], ecx

然后我搜索该eax值并找到指向的指针这eax
就是为什么我认为这是指针数组的原因。
希望我只是更具体地解释了这一点。

4

2 回答 2

3

您的问题实际上充满了地雷,这将不幸地证明为什么装配的准确性如此重要。

所以,我所拥有的是...

你显示语法不正确的匿名结构,突然你有一些指针?它不是那样工作的。你有匿名结构和几个语法错误,仅此而已。

现在在这里,我真的很乐意停止回答,因为如果没有数据的实际定义,您的问题的其余部分就毫无意义。但是,假设您有这样的想法:

struct STRUCT_A {
   char     str[17];
   int      x;
   float    a;
};

STRUCT_A testA[3]{
  {"a1", 1111, 1.111},
  {"a2", 2222, 2.222},
  {"a3", 3333, 3.333}
};

int foo(unsigned index) {
  return testA[index].x;
}

所以,我在这里拥有的是 array testA。数组不仅仅是指针,在编译期间它在 C++ 中还有一点点,虽然它会很高兴地“衰减”成指针,但它并不完全相同。

当我将testA用作指针时,它不会指向任何进一步的指针,它直接指向数据。

所以你在 OP 中没有一层,而是两层额外的间接。要阅读x第一个元素,您只需执行mov eax,[testA + 20]. 未加载指针(示例来自 x86 32b 目标,在其他目标上+20可能不同)。

你会不会:

STRUCT_A* testA_alias = testA;  
   // now this ^^ alias is no more array, it's just pointer
   // (array silently decays into pointer during compilation, when asked to)
STRUCT_A** testA_indirect = &testA_alias;

然后获取x第二个元素:

mov  eax,[testA_indirect]   ; eax = &testA_alias
mov  eax,[eax]              ; eax = testA (or &testA .. not sure how to write it, "address of data")
mov  eax,[eax + 28*1 + 20]  ; eax = testA[1].x

我设法创建了两个间接级别(实际上我不得不在这部分编辑答案,因为我从 C++ 错误地读取了程序集,不完整的 Intel 语法让我感到困惑)。

我仍然不确定你从哪里得到所有这些指针,为什么?它不是 Java,而是 C++,您只需将数据直接保存在内存中。如您所见,我必须付出相当多的努力才能获得两个间接级别。

现在您可能想知道为什么x是 at+20而不是 at +17。因为padding,C++会根据结构成员的类型来对齐结构成员,int喜欢对齐,就是这样。

这也应该解释28,这是该结构的大小。

在您的问题中,您还有:

step2结果+n+1

那是+1从哪里来的?也许你对此感到困惑:

char str[]{"abc"};              // str[4]

但那是因为"abc"is like db 'a', 'b', 'c', 0= 定义了四个字节。当您仅将 a、b、c 定义为三个字节时,数组将是 3 个字节:

char str2[]{'a', 'b', 'c'};     // str2[3]

当您通过 定义该char数组时[n],不涉及 +1,该数组完全具有n字符。如果将 C 字符串文字放入其中,它们最多可能有 (n-1) 个字符长,因为第 n 个字节将被零终止符占据。


您可以在此处查看该源代码在编译后的外观。

这可能会以最好的方式回答您的问题。

您可能需要特别注意内存内容定义,我在testA数组定义的初始行添加了一些注释:

testA:
        .string "a1"       ; three bytes 'a', '1', 0 defined
        .zero   14         ; remaining 14 zeroed to have char[17]
        .zero   3          ; padding for "int x"
        .long   1111       ; int x
        .long   1066284351 ; float a
        .string "a2"       ; char[17] of second element
...
于 2017-01-19T05:16:22.287 回答
0

哈哈哈哈.......对不起,我无法停止我的笑声,因为这是我在 Stackoverflow 的第二天,我问了哪个问题可以回答这个困境。我不太明白你在做什么,但我很确定你没有考虑填充。好吧,我昨天学习了填充,所以我会在这里帮助你。

好吧,每个数组都有一个指向其第一个元素的指针作为数组的名称。所以你有默认指针,或者你可以自己制作。在指针数组中取消对结构的引用是非常容易的任务。您面临的主要问题是访问结构成员。

//This answer is architecture and compiler dependent 
//My settings are TDM GCC 4.9.2 64bit and Windows 10
const int n = 5;

#pragma pack(push, 1)
struct A{
    char str[n];
    int x;
    float a;
};
#pragma pack(pop)

struct B{
    char str[n];
    int x;
    float a;
};

int main(){

    printf("Size of A is %d\n", sizeof(A));
    printf("Size of B is %d\n", sizeof(B));
    B k;

    for(int i=0; i<n; i++)
        printf("Address of str[%d] in k is %x\n",i, &(k.str[0]));

    printf("Address of int x in k is %x\n", &(k.x));
    printf("Address of float a in k is %x\n", &(k.a));  

}
/*
Result -

Size of A is 13
Size of B is 16
Address of str[0] in k is 9ffe30 Address of array
Address of str[1] in k is 9ffe30 Address of str[1] in k is 9ffe31
Address of str[2] in k is 9ffe30 Address of str[2] in k is 9ffe32
Address of str[3] in k is 9ffe30 And so on..
Address of str[4] in k is 9ffe30
Address of int x in k is 9ffe38 Address of Array + 8Bytes
Address of float a in k is 9ffe3c //Address of Array + 2*8Bytes

n -- padding
4k+1 -- 3
4k+2 -- 2
4k+3 -- 1
4k -- 0 */

查看代码。结构 A 已打包,因此没有进行填充。结构 B 是 A 的填充版本。B 是您正在使用的版本。填充随着 n 的变化而变化。

在这里,我将 n=5 用于大多数填充。这里前 5 个字节分配给数组 str。现在接下来的 3 个字节被分配给填充。这样做是为了使 RAM 可以一次访问 8 个字节,而不是像打包结构那样一次访问一个字节。这提高了性能。填充没有标准,因此它因架构和编译器而异。在 64 位架构上,一次访问 8 个字节。这就是为什么 64 位比 32 位更快而游戏不支持 32 位的原因。为了访问 int x,您需要将数组的地址偏移 8 个字节而不是 5。要再次访问浮点数,将偏移量增加 8 个字节。注意 - 这里只输出数组的地址,而不是数组的单独成员。您可以通过增加一来实现相同的目的。

如果你没有得到它,请阅读 C++ 中的内存对齐。

https://en.wikipedia.org/wiki/Data_structure_alignment

于 2017-01-19T05:04:08.557 回答