某厂APT样本分析师的面试总结

@lzeroyuee  June 4, 2020

面试某厂的APT样本分析师职位,做一次总结
因过去了有段时间外加当时临时发挥不是很好,只记下一些记忆比较深刻的问题


一面

一面技术面,主要是了解基本情况、问些简历上的东西

怎么在内核层遍历进程信息?

EPROCESS结构中有ActiveProcessLinks字段,该字段为双向链表的结点,通过此双向列表节点可以遍历系统进程列表的EPROCESS结构。在EPROCESS结构中有PEB结构,PEB结构中存着进程的相关信息

写的远控程序是怎么实现控制对方鼠标和键盘?

使用keybd_eventmouse_event

内存断点是怎么实现的?

对某内存分页下内存断点的原理就是使用VirtualProtectEx来修改该内存分页的访问属性(将访问属性全部清除),若之后访问了该分页,则会触发内存访问异常

二面

二面依然技术面,主要问些技术细节问题,偏向于基础知识点,需要举一反三

调用约定有哪些?

__cdecl__stdcall__fastcall__thiscall

__cdecl__stdcall有什么区别?

__cdecl是在调用者caller上平栈,而__stdcall是在被调用者callee上平栈

一个结构体,有一个char成员、一个short int成员和一个int成员,此结构按4字节对齐,每个成员相对于结构体首地址的偏移分别为多少?

此结构体如下

struct A {
  char a;        // +0
  short int b;   // +2
  int c;         // +4
};               // size: 8

偏移计算规则为:

  • 设编译选项设定的对齐值为Zp,每个结构体成员的偏移量为Offset
  • 满足Offset % min(sizeof(member type), Zp) == 0
x86下,类A有两个虚函数和两个int成员,类B继承自类A并添加了一个虚函数,则这两个类对象的大小分别是多少?

类A与类B如下

class A {
public:
  virtual void func1() {}
  virtual void func2() {}
  
  int a;
  int b;
};

class B : public A {
public:
  virtual void func3() {}
};

类A对象的大小为12字节(虚表指针vt_ptr4个字节,两个int成员8个字节,一共12个字节),类B对象的大小也为12字节

一个类对象会不会有两张虚表?

可能会有,若是多继承(菱形继承)或虚继承就可能存在多张虚表

在PE中怎么从FOV转RVA?

设FOV为x

  1. 判断x是否位于整个PE头部部分,若是则直接为RVA
  2. 遍历节表,得到每个节表的PointerToRawDataSizeofRawData,判断x是否在此范围内,以此定位x位于哪个节上
  3. 定位到目标节区之后,利用x - PointerToRawData算出x所在节的节内偏移offset
  4. 利用节内偏移与目标节的虚拟地址offset + VirtuallAddress算出RVA
__stdcallprintf怎么实现?如果__stdcallprintf的格式化字符串为"%d%d\n",但传入的int类型参数只有一个会怎么样?

此题的正确解法我确实不知道,只有个思路:实现函数内平栈,以传入两个int类型对象为例

// 由于使用了可变参,故__stdcall在这里其实是被忽略的,依然会在函数外有一次平栈动作(多余的)
// 此函数实现了在函数内平栈
__declspec(naked) void __stdcall my_printf(size_t args_size, const char* format, ...)
{
    int a, b;
    va_list args;
    va_start(args, format);

    a = va_arg(args, int);
    b = va_arg(args, int);
    printf(format, a, b);

    va_end(args);

    __asm {
        // 还原环境
        mov esp, ebp
        pop ebp

        // 获取返回地址
        pop eax

        // 函数内平栈
        add esp, args_size

        // 跳转返回地址
        jmp eax
    }
}

如果只传入一个int类型,那么会造成栈不平衡

在VS的Debug版本程序中,编译器会在栈上填充什么值?有什么用?

编译器会在栈上填充0xcc,此值代表指令int 3软件断点,如果执行到此处,则会触发软件断点异常

inline hook怎么实现?

在Windows的大部分API中,开头的5个字节的指令为

8B FF     mov edi, edi    # 此指令没有什么实际意义
55        push ebp
8B EC     mov ebp, esp

将此5字节的指令序列替换为同为5字节的jmp xxx指令,跳转到自己的shellcode上,而在shellcode功能执行完毕后,为API再次还原这5字节的序列,而后跳转到API正确的流程上

jmp指令的偏移怎么计算?往上跳和往下跳是怎么样的?

jmp xxx指令的偏移计算公式遵循目标地址 - 源地址的原则

  • 目标地址:要跳转的目标的地址
  • 源地址:当前指令的下一条指令的地址处

不管是往上跳还是往下跳,都是一样的遵循这条规则,例如(设此处jmp指令均为5字节):

A:
    ...
    
A':
    jmp A    # 此处上跳到A处,偏移为 A - (A' + 5)
    ...

B':
    jmp B    # 此处下跳到B处,偏移为 B - (B' + 5)
    ...
    
B:
    ...
远程线程注入怎么实现?
  1. 获取LoadLibrary的地址
  2. 在目标进程空间中申请内存VirtualAllocEx并写入DLL路径WriteProcessMemory
  3. 创建远程线程执行相关操作CreateremoteThread(一般调用LoadLibrary加载DLL)
x86下函数开头第一个字节码是什么?函数开头指令序列是什么?分别是什么作用?

第一个字节码为0x55
函数开头的指令序列为

push ebp        # 保存原来的ebp
mov ebp, esp    # 使用ebp作为当前栈帧的栈基址
sub esp, xxx    # 抬高栈顶,为局部变量分配空间
有一个函数拥有两个int类型的参数,此函数以ebp作为寻址基址,那么此函数的第二个参数该如何寻址?第一个参数呢?ebp的位置上是什么?

第一个参数:[ebp + 0x8],第二个参数:[ebp + 0xc]
ebp的位置上是先前保存的ebp的值

IDA怎么添加结构体?

shift + F9打开Struct窗口,insert键插入一个结构体

三面

三面hr面(最终面),主要由hr来了解些基本情况,压力较前两面轻松一些

最后

对于准备不是很充分的来说,总体上还是有点难度,大多数问题都是偏向基础部分,需要从知识点所涉及的原理上来想问题的解决方案


添加新评论

  1. 阅(●ˇ∀ˇ●)

    Reply