================
Intel 80386 arch
================
Registers :
(1) data
(a) eax : accumulator
(b) ebx : base (can hold addy of procedure or variable)
(c) ecx : counter
(d) edx : data
(2) segment : provides base locations for program instructions, data, and
the stack
(a) cs : code segment
(b) ds : data segment
(c) ss : stack segment
(d) es : extra segment
(e) fs and gs
(3) index : contain offsets (from base locations as above, segment registers)
of data and instructions
(a) ebp : base pointer
(b) esp : stack pointer (contains offset to top of stack from SS)
-- holds address of last data element to be pushed onto the stack
(c) esi : soure index
(d) edi : destination index
(4) control
(a) eip : instruction pointer (contains address of next instruction
to be executed)
(b) eflags
Adding to the stack : done via PUSH, when a new value is pushed, the ESP
is decremented (to point to lower mem addys, since the stack grows down)
E.g. ESP points to mem loc 0x9ffffc04
Then want to push value 0x00a5
So, move that value (0x00a5) into eax
Decremetn ESP by 4, so ESP now points to mem loc 0x9ffffc00
Write the value of eax at mem loc eax
The pop operation removes a value from the stack, then increments the ESP
E.g. esp points to 0x9ffffe00
So we move the value at ESP (0x9ffffe00) into eax
Then we increment ESP by four to get 0x9ffffe04
The stack frame pointer (aka ebp) always points to a fixed location within
the stack frame. Prior to function invocation, args to that function are
pushed onto the stack, then the return address (this will often be a value
in the code segment of a program, it is the address of the next
instruction to execute after this function returns), then the EBP (SFP). Then
when the function is actually called, any local variables of that function
will be pushed onto the stack. Ergo, function parameters have positive
offsets from the SFP (EBP), whereas local vars have negative offsets from
the SFP (EBP).
void bar( int barvar ) {
int bar1 = barvar % 3;
char buf[ 64 ];
memset( buf, '/0', 64 );
strncpy( buf, itoa( bar1 ), 63 );
}
void foo( int blah ) {
int foo1 = 3;
bar( blah + foo1 );
}
int main( int argc, char **argv ) {
int arg = atoi( argv[1] );
foo( arg );
}
[Don't ask whether this code is useful (or, much less, makes sense);
it doesn't].
The value stored at the SFP is the address of the calling function's
ESP. For example if main() calls foo() and foo() calls bar(), then the
stack might look something like this:
|-------------------------------------------------
| argv[1]
|-------------------------------------------------
| argv[0]
|-------------------------------------------------
| argc
|-------------------------------------------------
| 0x80c1248 ( <start> + 24 )
|-------------------------------------------------
0x9ffffd18 | 0x9ffffe18
|-------------------------------------------------
0x9ffffd14 | int arg
|-------------------------------------------------
0x9ffffd10 | arg
|-------------------------------------------------
0x9ffffc0c | 0x80c256 ( <main> + 30 )
|-------------------------------------------------
0x9ffffc08 | 0x9ffffd18
|-------------------------------------------------
0x9ffffc04 | into foo1 = 3
|-------------------------------------------------
0x9ffffc00 | blah + foo1
|-------------------------------------------------
0x9ffffbfc | 0x80c588 ( <foo> + 12 )
|-------------------------------------------------
0x9ffffbf8 | 0x9ffffc08
|-------------------------------------------------
0x9ffffbf4 | int barvar
|-------------------------------------------------
and so on...
When bar() is done executing, bar will :
- EIP = mem[ ESP + 4 ]
--> so in this case, ESP = 0x9ffffbf8
--> so then the EIP is stored at ESP + 4 == 0x9ffffbfc
--> then the value of the EIP is 0x80c588
- ESP = mem[ ESP ]
--> the new ESP is the value stored at the memory address
of the old ESP
--> so in this case, the old ESP = 0x9ffffbf8
--> and the value stored at that address is 0x9ffffc08
--> so the new ESP will be 0x9ffffc08
Note that when main() called foo(), main() pushed the argument to foo (int
blah) onto the stack, then called foo(). The CALL instruction pushes the
EIP onto the stack: in this case, that value was stored at the current
ESP, which is 0x9ffffc0c, and that value was 0x80c256 -- which is where in
main(), control should return when foo() completes execution.
Then foo() for its part, (1) pushes the the EBP (which is 0x9ffffd18) onto
the stack -- now the ESP points to 0x9ffffc08 (cuz remember that the ESP
is decremented whenever we push onto the stack), then (2) copies the ESP into
the EBP, so the new EBP = 0x9ffffc08, (3) then subtracts from ESP some # of
bytes which represents how much local var space is required by foo(). In this
case it's just 4 bytes, to store variable "int foo1." Then foo() does its thing...
++++++++++++++++++++++
On function invocation
++++++++++++++++++++++
--------------------------
So the calling procedure :
--------------------------
- pushes args to the called procedure
- calls the called procedure [and, in so doing, pushes the return address --
the location in the calling procedure where control should return)
---------------------------
Then the called procedure :
---------------------------
- pushes the current EBP
- sets the new EBP to be the value of the current ESP
- decrements the ESP by some number of bytes to account for how much
local storage is needed (so that, the ESP will again point to the
"top" of the stack)
+=+=+=+=+=+=+=+=+=+=+=+
Now for linux syscalls
+=+=+=+=+=+=+=+=+=+=+=+
- on syscall entry, eax contains the syscall #
- ebx, ecx, edx, esi, edi, ebp contain the params to that syscall
- so if there's just one argument, routine sys1 is used; sys1 does :
pop that arg is popped off the stack into ebx,
then direct execution to sys0 (common syscall handler)
- if there are two args, routine sys2 is used; sys2 does :
pop the first param off the stack into ebx,
pop the second off the stack into ecx,
then direct execution to sys0
- ... and so on for syscalls which require 3, 4, 5, 6 args
+=+=+=+=+=+=+=+=+=+=+=+=+
Now for Windows API calls
+=+=+=+=+=+=+=+=+=+=+=+=+
相关推荐
32位栈溢出实战:CTFshow pwn052参数传递与后门函数调用分析
栈溢出是二进制安全领域的经典漏洞类型,其原理是程序向栈上的固定长度缓冲区写入超出其容量的数据,导致相邻的关键数据(如函数返回地址、栈帧指针)被覆盖。在32位x86架构下,函数调用遵循cdecl等约定,参数通过栈进行传递,这为漏洞利用提供了清晰的布局。理解栈帧结构、参数压栈顺序以及返回地址的精确覆盖,是构造有效攻击载荷(Payload)的核心技术价值。在CTF竞赛与安全研究中,此类漏洞常出现在未启用栈保护(Canary)的32位程序中,攻击者通过精心构造的输入数据,可以劫持程序控制流,执行任意代码。本文以一道
C语言解析FLM(ELF)格式文件
运行效果 flmparse.c /* * flmparse.c * * Created on: 2021年4月10日 * Author: hello */ #include <stdio.h> #include <stdint.h> #include <fcntl.h> #include <unistd.h> #include <ctype.h> #include "elf.h" #define FILENAME "..
基于集成运放心电放大器设计(原创)
原创作者:einyboy or alert 最近在看美剧《危机边缘》关于边缘科学的案件,科技控可以看一下,但建议不在吃东西的时候看。想像力是创造的思想源头,动手能力则是创造的车床。 自认为是没有想像力的那类,只能发挥动手能力了。 心电测量简介 图1 心电极接线图 如图1心电测试一般使用3个心电极进行测量,各个电极的说明如下: RA: Right Arm缩写,右手电极 LA: L...
linux 查看系统 32位 or 64位
linux 查看系统 32位 or 64位
右腿驱动电路设计:从共模干扰原理到生物电采集实战
在生物电信号采集领域,共模干扰是影响测量精度的核心挑战之一,其本质源于环境电磁场通过容性耦合在人体表面产生的感应电流。根据欧姆定律,由于人体组织阻抗的不均衡性,共模电流会在不同测量点产生差异化的压降,从而转化为难以滤除的差分噪声。为解决这一问题,右腿驱动技术应运而生,它通过负反馈原理主动抵消干扰,显著提升系统的共模抑制比。该技术广泛应用于心电图、脑电图等医疗电子设备中,是确保微弱生理信号质量的关键环节。本文结合工程实践,深入剖析了右腿驱动电路中的仪表放大器选型、反馈环路稳定性设计以及PCB布局布线要点,为高
永不消逝的电波 -GSM空口信号的接收与解码
一开始接触软件无线电应该是从很早之前对RFID洗衣卡的解密开始的,后来接触人员定位,慢慢的打开了这样一扇大门。。。。 翻到之前做的GSM信号的接收解码,今天就对其过程做一个记录,也不是什么新颖的内容,权当是记录和回忆了。 对于GSM网络的相关内容,不是非常的熟悉,如有不正确的,欢迎批评指正。
Linux 小知识翻译 - 「架构 续」(arch)
上次,从「计算机的内部构造」的角度解释了架构这个术语。这次,介绍下架构中经常提到的「i386架构」及之后的「i486」,「i586」。 安装Linux的时候,很多人即使不了解但也会经常听到i386架构这个词。因此,会误认为「自己的PC的架构是i386架构的」。现在用i386的人是非常少的。 i386的 i 就是「Intel」的首字母。i386是1985年发布的架构,那么如今还在用i386...
【100%通过率】2023华为od机试C卷【电脑病毒感染】Java&Go 实现
一个局域网内有很多台电脑,分别标注为 0 ~ N-1 的数字。相连接的电脑距离不一样,所以感染时间不一样,感染时间用t 表示。其中网络内一台电脑被病毒感染,求其感染网络内所有的电脑最少需要多长时间。如果最后有电脑不会感染,则返回-1.
x86 分段机制
【代码】x86 分段机制。
全网唯一OpenCyphal/UAVCAN教程(8)libcanard介绍
当Cyphal里,这个结构体代表一条完整的Cyphal消息,之后我们会根据metadata里提供的元信息,其实就是消息ID,调用对应的反序列化函数,将payload还原成具体的Cyphal消息。用于构造一个新的发送队列,原则上一个can物理接口需要分配一个发送队列,如果它们的MTU大小一致,是可以共用一条的。这个库不包含IO层,是平台无关的,意义是没有实现具体的数据收发,仅提供将发送出去的Cyphal消息转成can帧,以及将收到的can帧转成Cyphal消息。返回值是这条消息拆解成的can帧的数目。....
四足狗机器人硬件开发技术栈详解
本文系统介绍了四足狗机器人的硬件开发技术,重点分析了五大核心模块:机械结构(骨架设计)、动力系统(关节驱动)、感知系统(环境监测)、控制系统(决策中枢)和电源系统(能源管理)。文章详细阐述了各模块的选型标准、技术要点和协同设计原则,提供了从需求定位到组装调试的完整开发流程,并针对常见问题给出解决方案。随着材料轻量化、模块标准化和AI集成化的发展趋势,四足机器人硬件将向更灵活、更智能的方向演进,为工业巡检、应急救援等场景提供更强大的移动平台支持。
i386和i686的区别
i386和i686 现在所有的intel 32位体系(包括AMD等兼容CPU)都叫i386体系,包括P4。、i686仍然属于i386体系,不过对CPU(相对于386)的特性作了指令优化。GNU/Linux分为alpha、PowerPC、Sun等各个不同版本,所有从Intel386-P4都用i386版本,但i386版本中有几个内核(i486,i486,i586,i686),安装时安装程序检测到
Claude Code 学习笔记之一:理解 Agentic Loop 与内置工具
本文介绍了Claude Code这一终端智能助手的工作原理与使用方式。作者从ABAP开发经验出发,指出学习Claude Code的高级用法具有长期价值。文章详细解析了Claude Code的Agentic Loop机制:通过大语言模型进行推理,借助各类工具执行任务,并循环收集上下文、执行操作、验证结果三个步骤。系统阐述了Claude Code支持的五大类工具及其扩展方式,说明了其访问本地资源的范围和自治处理能力,同时介绍了权限模式等安全机制。最后提供了从小任务开始、充分探索、及时引导、使用CLAUDE.md
64位系统32位系统的表示方法
64位系统32位系统的表示方法
毕设-MobileNetV4:给YOLOv8装上轻量化“引擎”——通用瓶颈与C2f融合的实战教程
本文介绍了一种将MobileNetV4与YOLOv8结合的目标检测模型轻量化方案。MobileNetV4通过通用瓶颈(UB)结构实现多任务适配,其核心改进包括动态卷积核、注意力机制和多路径特征聚合,显著提升特征提取能力。作者详细阐述了如何在YOLOv8框架中实现MobileNetV4的通用瓶颈模块,包括"膨胀-卷积-注意力-压缩"的流程设计,并构建完整的backbone网络。该方案通过融合MobileNetV4的轻量化优势与YOLOv8的高效检测架构,在移动设备上实现了精度与速度的良好平
温度PID控制PLC程序
温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值
CTF PWN入门实战:栈溢出原理与ret2text利用详解
栈溢出是计算机安全领域中最经典的漏洞类型之一,其原理源于程序在栈内存中分配固定大小的缓冲区,当输入数据超出预留空间时,多余数据会覆盖相邻内存区域,包括关键的函数返回地址。这种内存破坏漏洞的技术价值在于攻击者可通过精心构造的输入数据,劫持程序执行流程,实现任意代码执行。在软件安全测试和CTF竞赛中,栈溢出常作为入门考点,其中ret2text技术通过覆盖返回地址,使程序跳转到自身已有的代码片段(如system("/bin/sh"))来获得控制权。本文以CTFHub经典题目为例,结合pwntools工具链,详细演
怀旧版电脑GBC模拟器
再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。
[CTF Writeup] Pwnable.tw - start 题目解析与漏洞利用
中序遍历(Inorder Traversal)遵循"左子树-根节点-右子树"的顺序。
Camera2Demo
在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作
TMS_WebGMaps_1.9.8.0_Full_Source
TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder
CTF Writeup:Pwnable.tw - start 题目解析
背包问题(Knapsack Problem)是经典的动态规划问题,分为0-1背包和完全背包两类。核心目标是在容量限制下选择物品,使得总价值最大。0-1背包:物品不可重复,逆序遍历容量。完全背包:物品可重复,正序遍历容量。初始化时 $dp[0] = 0$,其余可设为负无穷(若要求恰好装满)。
x86、i386、i486、i586、i686
一、x86与i386、i486、i586、i686等 x86或80x86是英特尔Intel首先开发制造的一种微处理器体系结构的泛称。 该系列较早期的处理器名称是以数字来表示,并以“86”作为结尾,包括Intel 8086、80186、80286、80386以及80486,因此其架构被称为“x86”。由于数字并不能作为注册商标,因此Intel及其竞争者均在 新一代处理器使用可注册的名称,如Pentium。现时Intel把x86-32称为IA-32,全名为Intel Archite
x86架构演进:从CISC兼容性到现代微架构设计
处理器指令集架构(ISA)是计算机系统的核心规范,定义了硬件与软件之间的交互接口。x86作为典型的复杂指令集计算机(CISC)架构,其设计哲学强调通过单条指令完成复杂操作,以提升代码密度。这种架构的技术价值在于其无与伦比的向后兼容性,使得数十年前的软件仍能在现代硬件上运行,形成了庞大的软件生态。在工程实践中,x86处理器通过内部微架构RISC化,将复杂指令解码为微操作(μops),在保持外部兼容性的同时实现高性能执行。应用场景涵盖桌面计算、服务器数据中心及新兴系统移植,如Android TV x86和Ope
x86/x64/x86_64/i386/ia32/ia64/amd/amd64 辨析
x64 = x86_64 = amd64 64位指令集,是对IA-32的扩展,由AMD提出,implemented by AMD,Intel。可兼容32位指令集(IA-32) 目前大部分64位计算机均使用这套指令集。 为什么叫x86-64,因为它是X86的一部分,兼容X86的其他指令集(32-bit,16-bit)。 详见wiki x86-64(also known asx...
从8086到i7-8086K:x86架构的演进、兼容性与实战调优
x86架构作为现代个人计算机的基石,其核心在于指令集与微架构设计。从CISC复杂指令集出发,通过内部微操作解码与RISC化执行引擎,实现了高性能与向后兼容的平衡。这一技术价值在于构建了庞大的软件生态,使得从操作系统到应用软件都能在跨越数十年的硬件平台上运行。其应用场景覆盖了桌面计算、服务器、虚拟化及嵌入式系统。本文以英特尔纪念版处理器i7-8086K为切入点,探讨了x86架构在虚拟化、驱动兼容性及系统优化中遇到的具体问题,例如处理虚拟机迁移时的“处理器功能不支持”错误,以及解决“nacos cannot d
ROP技术实战:从原理到CTF题目解析
ROP(Return-Oriented Programming)是一种高级的漏洞利用技术,通过复用程序中已有的代码片段(gadgets)来构建攻击链。其核心原理是利用内存中现有的指令序列,通过精心组合这些片段实现任意代码执行。在安全防护日益增强的今天,ROP技术成为绕过NX(No-eXecute)等内存保护机制的主要手段,广泛应用于CTF竞赛和实际漏洞利用中。本文以2018年CTF赛题为案例,详细解析了ROP技术在32位ELF程序中的实战应用,包括gadget收集、ROP链构造、动态链接器利用等关键步骤,并
交叉编译(gcc 2.95.3)
<br />1、创建目录<br />[fedora@localhost lqm]$ pwd<br />/home/fedora/lqm<br />[fedora@localhost lqm]$ mkdir -p ./embedded-system/{setup-dir,src-dir,kernel,build-dir,tool-chain,program,doc<br />}<br />[fedora@localhost lqm]$<br />2、下载相关数据<br />[fedora@localhost
508



被折叠的 条评论
为什么被折叠?



