面试某厂的APT样本分析师职位,做一次总结
因过去了有段时间外加当时临时发挥不是很好,只记下一些记忆比较深刻的问题
一面
一面技术面,主要是了解基本情况、问些简历上的东西
怎么在内核层遍历进程信息?
EPROCESS
结构中有ActiveProcessLinks
字段,该字段为双向链表的结点,通过此双向列表节点可以遍历系统进程列表的EPROCESS
结构。在EPROCESS
结构中有PEB
结构,PEB
结构中存着进程的相关信息
写的远控程序是怎么实现控制对方鼠标和键盘?
使用keybd_event
和mouse_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_ptr
4个字节,两个int
成员8个字节,一共12个字节),类B对象的大小也为12字节
一个类对象会不会有两张虚表?
可能会有,若是多继承(菱形继承)或虚继承就可能存在多张虚表
在PE中怎么从FOV转RVA?
设FOV为x
- 判断x是否位于整个PE头部部分,若是则直接为RVA
- 遍历节表,得到每个节表的
PointerToRawData
与SizeofRawData
,判断x是否在此范围内,以此定位x位于哪个节上 - 定位到目标节区之后,利用
x - PointerToRawData
算出x所在节的节内偏移offset
- 利用节内偏移与目标节的虚拟地址
offset + VirtuallAddress
算出RVA
__stdcall
的printf
怎么实现?如果__stdcall
的printf
的格式化字符串为"%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:
...
远程线程注入怎么实现?
- 获取
LoadLibrary
的地址 - 在目标进程空间中申请内存
VirtualAllocEx
并写入DLL路径WriteProcessMemory
- 创建远程线程执行相关操作
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来了解些基本情况,压力较前两面轻松一些
最后
对于准备不是很充分的来说,总体上还是有点难度,大多数问题都是偏向基础部分,需要从知识点所涉及的原理上来想问题的解决方案
阅(●ˇ∀ˇ●)