Bomb_Lab

bomb lab 的实验挺好玩的,实质上就是逆向分析,然后找到正确的输入,错误的输入会导致跳转”炸弹函数”,就会导致程序结束;

规则:

根据提示,我们不能调试,但可以使用逆向工程解决炸弹;

输入正确的字符串以拆除炸弹;

源码中没有什么有用信息,不过有提示,从代码中可看出有6个需要解决的炸弹;

main

进入IDA分析后,可以发现函数栏有main以及 phase_1 ~ phase_6;

在puts函数之前的判断函数与破解炸弹没什么大关系,所以直接跳过;

main

直接分析phase;(终于找到图床了)

Phase_1

首先进入phase_1,只有6条汇编码;

phase_1汇编

首先进栈,然后设置对比字符串esi,呼叫函数比较两个值,将返回值传给eax,然后不是0的时候就爆炸;

所以只需要输入“加拿大的边境问题”就可以解除炸弹1;

“Border relations with Canada have never been better.”

Phase_2

phase_2

其实phase2的汇编也比较简单,但这里用伪代码能截图直接截满,可以更好地说明;

在read six number 里会有一个判断爆炸,之后&v3的判断和&v4的判断,只要满足这三个条件,就行了,那就来看看它们的条件是什么;

readsix

可以发现三个判断和&v4,&v3有关系,&v4和&v3是存在于栈中,在汇编码中,把rsp指向的地址传送给了rsi(输入数据),意思就是使输入的数据入栈了;

汇编

那很明显是让输入数据的某个部分成为了&v4和&v3,然后去判断;

①read six number 里让输入6个数字都正确就继续,否则炸;

②v3(rsp)指向的值需要是1,否则爆炸,而rsp指向的是rsi,第一个数据,所以第一个数必须是1;

③v4(rsp+4)的上一个数据乘上2需要和v4相同,不然爆炸,所以1的后面是2,2的后面是4,等等;

所以只需要输入 1 2 4 8 16 32;6个数字就可以解除炸弹2;

Phase_3

phase3

开始也是读入2个数据判断(read two number),正确就通过,否则就炸;

输入进去的两个数字会成为v8(rsp+8h),v9(rsp+Ch)的存在,而往后是一个switch选择,根据v8选择;

switch

之后从switch中的数据里与v9比较,如果不同,炸弹爆炸;

cmp

所以我直接写一个最简单的就是 0 207 就解决炸弹3;

simple

Phase_4

phase4

一开始也同phase2一样,判断了输入的数据个数,需要是2个,不然就会跳转爆炸;紧接着比较v8(rsp+8h)和E(14),如果大于了14,也会导致跳转爆炸;

在phase4的后面一部分,会用func4函数运算result,之后result与v9(rsp+Ch)进行逻辑或运算要使得结果为0,不然爆炸;

而func4函数如图所示:

func4

phase4中运用func函数的最开始的参数是 v8 ,0,14;可以看出这是个递归函数,而且越递越复杂;

v3第一轮就应该是7;

它比较的就是7和v8了,但它漏了v8=7的时候,所以当v8=7的时候,就不会调用递归了,直接让result=0;

所以我们让输入成为 7 0 就可以解除第四炸弹;

Phase_5

phase5

这道属于baby级别的逆向CTF了;

输入的字符串需要为6的长度,之后用box里的字典来运算出v3的值,之后比较v3和”flyers”字符串,如果不满足以上情况,炸弹爆炸;

box

那思路就很简单了,就是解方程,比如第一个会是f,那么让f = box[input[1] & 0xF],f在box里的下标是9,所以 9 = x & 0xF,我们求6次这样的x就行了;

最后得出输入数据为:IONEFG,解决第五炸弹;

Phase_6

while1

这是进入phase6之后的代码;也是需要读入6个正确数据,然后进行第一个循环,这个循环要求每个输入的数字小于等于6,且每个数字都不相等;

while2

v16是rsp+18h,而我们输入了24个字节的数据;所以v16代表我们输入的字符,这个循环是让输入的数据成为每个对应位置的自己求负然后加上7,ascii码对应时退出,看起来像是在把数据颠倒,因为数只能是 0 1 2 3 4 5 6;

while3

第三个循环判断每个数据,之后做出不同的操作;如果大于1,就会让node1里的第v8个下标的值成为v6,不然v6=node1[0];

之后让v17[2*i] = v6,v17是rsp+20h;v17就对应了node1~node6;

while4

v18是rsp+28h;也就是v17的第二个数据;v19是rsp+50h;

第一个循环是挪位,然后当v10与v19相等时退出;

第二个循环是v9的后一个数必须小于前一个数,不然爆炸;v9就为node系列;

然后就IDA突发恶疾把数据定义成命令了,然后就看不懂了;

之后解除定义后,让node按序排列就成了;

顺序是node3,node4,node5,node6,node1,node2;

输入便是4 3 2 1 6 5;

deal

总结

相较于data lab,这个实验更简单一点,可能是因为本身就是逆向学的原因,做了这个东西过后对汇编理解更深刻,以及栈的处理,而且觉得逆向并不困难,困难的是不认识的加密形式和一开始看不懂的垃圾代码,如果能够多看多做,学会更多的加密方式和认识更多的垃圾代码,根据数据和逻辑便能够猜出一个大概,然后就是不断地去调试了;

继续加油⑧~

genshin

阅读全文
逆向工程核心原理复现01

第一章的内容其实很基础简单,通过第一章学到的东西就是栈和栈帧,以及根据寄存器不同的功能,如何查找数据;

介绍了一系列的逆向手法,以及使用例子讲解如何去查看,了解了什么是API;

这里来复现一下:

逆向手法

① 最常见的就是字符串搜索定位,和数据搜索定位;

② 紧接着就是使用断点不断地去调试,缩小范围,查找功能代码;

③ 修改可疑的语段,当然这些语段一般都是跳转语段,然后去查看现有的功能,如果功能异常,就说明找到了入口;

④ 最需要注意的便是记录地址,以及寄存器的指向,通过栈回溯的功能也可以发掘可疑函数;

⑤ 构建思维,推测编码者的函数运用思路,有一个好的和广的思路才会方便逆向推导(不然就只能硬调);

⑥ API 中文意思就是入口,所谓的API断点,其实就是跳转的地点设置断点去测试,修改;

寄存器

以32位为例子:

① 通用寄存器有8个:

EAX:一般处理操作数的结果数据;

EBX:一般存放基址 (Data段中的数据指针);

ECX:字符串和循环的累计计数;

EDX:I / O 指针;

ESI与EDI:处理操作数据和字符串;

ESP:栈顶指针,始终指向栈顶;

EBP:指向栈帧的返回处;

② 段寄存器有6个:

CS - Code 代码段

DS - Data 数据段

SS - Stack 栈段

ES,FS,GS - Extra 格外数据段

③ 状态控制寄存器1个:

里面一个字节就是一个控制符,cmp,test指令会改变的就是其中的一些字节;

④指针寄存器1个:

EIP:存放指针;

有了寄存器基础,就知道每步执行之后,可以查看ESP来观察栈的情况,查看EBP观察返回处和栈顶的距离,而EAX一般存放的是返回的值;

栈和栈帧

① 栈是一种数据结构,栈即为客栈,只有一个大门,所以先进的人被后进的人堵住,呈现出一种先进后出的情况;

② 在一个程序中,栈的体现方式是一个段,也是一个字节数组,也是内存的一部分;

一般运用栈便是查看栈中的内存,可以往栈中放各种各样的东西,但因为它先进后出的特性,有还原性,在内存中,栈顶往地址小的方向扩展;

就比如,调用一个函数,先入栈的是返回地址,然后存放不下的变量再入栈;当函数结束后,清空变量,并把返回地址送给PC寄存器,栈恢复,继续执行调用完函数的代码;

③ 栈帧存在于栈中,栈类似于视频,它也会有 “帧率” ;一个栈就是一个视频时长条,不同的时段有不同的剪辑和内容,同样,栈中的不同段有着不同的功能,比如有存储主函数的变量区域,和调用的函数区域,一般来讲,一个函数就是栈中的一帧,调用函数区域可以叫栈帧,主函数区域也是另一个栈帧;

④ 添加一个小内容,PC寻址规则;jmp 后面的编码加上下一个地址即为跳转地址;

总结

① 也许是这周看了很多的内容,但是做不起带嘲讽名词的 normal 题,我会觉得学习方法有问题;但其实还好,因为学的是基础知识,应该用Bomb Lab来测验,而normal题里的Va_List我也是第一次接触,所以算不上什么很大的问题,慢慢去做去看WP,就能学好了;

② Pwn方向也多少会一些基础操作了,根据危险函数的特性来栈溢出填充返回地址,来进入shell,下个星期可以来试着解决ret2xxx;

③ 在第一章最有意思也最麻烦的地方就是跟踪数据和跳转去寻找答案,这一点很类似于破解游戏,dll注入过后也是这样去查找,用架空法,最终找到实际执行的那句代码,以及冷却条件;最近也打算用游戏来练练手;

阅读全文
Pwnの前瞻

1.简单の概述Pwn是什么

Pwn是一个动作,攻击服务器或进程的一个动作,针对的目标也就是服务器和运行中的软件;

这些服务器和软件中必然需要存在着二进制漏洞等待挖掘;

一但找到漏洞并写出恶意代码,使目标运行,对方就可出现一些特殊的情况,从而拿到控制权限以及夺旗战中的旗帜;

要分析并挖掘二进制漏洞,必然少不了逆向工程和二进制知识的加持;

2.Pwn的前置知识:文件构架&程序执行

需要从一个服务器或进程中找到漏洞就需要了解软件的框架,软件的结构层次分为不同的功能;

两个名词:节,会在磁盘中的软件里出现,它的作用则是分配不同种类和作用的代码块和文本;

段,可以理解为节的集合,不同的节对应不同的段,段存在于进程中,作用则是分配不同权限的代码块和文本(权限相同的节在一个段中);

对应的,磁盘中的软件和内存中的进程之间因为段和节的关系有着类似的结构,不过也有些许不同:进程中还包含了除原软件外的系统共享进程,比如内核;

①可执行文件

Windows中的PE(portable executable)

.exe 常见执行文件; .dll 动态链接库; .lib 静态链接库;

Linux中的ELF(executable and linkable format)

.out 执行文件; .so 动态链接库; .a 静态链接库;

一般人为的函数和功能都放在动态链接库中;

②抽象的虚拟内存

虚拟内存中装载的也就是一个可执行程序运行起来后的进程,相当于把CPU和主存之间的关系抽象为了一个”磁盘“装载运行中的文件;其次,装载内存中的东西又过于晦涩,不方便人观察,而虚拟内存的概念就可以很好的等效成进程的人为观察视角,所以会有这样一个概念,手机里称为运存;

每个进程的最小单位是字节,每个字节则是进程的一个地址,所以又把进程称之为一个庞大的字节数组;

③C代码在进程中的划分

未初始化全局变量会被划分到一个叫做Bss的区域中;

只读数据(如字符串,声明函数)被划分到Text段区域中;

可改(写权限)全局变量被划分到Data段区域中;

局部变量被划分到栈区域中;malloc申请空间是来自于堆;

阅读全文
Data_Lab

每道题都有约束的规则,具体内容在代码块里;

1.bitXor

运算两数异或,不使用 ^;

代码:

1
2
3
4
5
6
7
8
9
10
11
/* 
* bitXor - x^y using only ~ and &
* Example: bitXor(4, 5) = 1
* Legal ops: ~ &
* Max ops: 14
* Rating: 1
*/
int bitXor(int x, int y) {
int result = ~(~x&~y)&~(x&y)
return result;
}

二进制异或是相同0或相同1就变0,而不同为0或1就变1;

让原来不同的地方变为相同,同的地方变为不同,再用 & 运算就可得到该值;

2.tmin

返回最小的二进制补码整数;

代码:

1
2
3
4
5
6
7
8
9
10
/* 
* tmin - return minimum two's complement integer
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 4
* Rating: 1
*/
int tmin(void) {
int result = 0x80<<24;
return result;
}

最小二进制补码整数是一个负数,补码规则最高位为1表示负数,则最小的负数是10000000 00000000 00000000 0000000;也即是 0x80 00 00 00;

3.isTmax

返回1,如果是二进制补码最大值,不然返回0;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
* isTmax - returns 1 if x is the maximum, two's complement number,
* and 0 otherwise
* Legal ops: ! ~ & ^ | +
* Max ops: 10
* Rating: 1
*/
int isTmax(int x) {
int result = x + x + 1;
result = ~result;
result = result + !(x + 1) //排除-1特殊
return !result;
}

最大值是开头为0,后面全为1;

返回的需要是0,1,那应该想到用逻辑非 !,当为最大时是0,其他情况当然就为非0,倒置一下就变为输出1时最大,其他情况为0;

各个非0数 !都为0,所以需要位 ~ ,所以要使得0111…. 变为全0;

又因为溢出的效应,-1也有加1再加自己为全1的特点,所以来排除这种情况;

4.allOddBits

返回1,当所有奇数位为1时,否则返回0;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 
* allOddBits - return 1 if all odd-numbered bits in word set to 1
* where bits are numbered from 0 (least significant) to 31 (most significant)
* Examples allOddBits(0xFFFFFFFD) = 0, allOddBits(0xAAAAAAAA) = 1
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 12
* Rating: 2
*/
int allOddBits(int x) {
int mask = 0xAA + (0xAA<<8);
mask = mask + (mask<<16); //32位10101010....
result = (x&mask)^mask;
return !result;
}

根据例子可以知道,位数从0开始计算,而不是1;

要使其奇数位全为1时(偶数位无关,则运算时把偶数全看为0),结果生成1,不难想到掩码;

最后用异或转换;

5.negate

返回x的负值;

代码:

1
2
3
4
5
6
7
8
9
10
11
/* 
* negate - return -x
* Example: negate(1) = -1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 5
* Rating: 2
*/
int negate(int x) {
result = ~x + 1;
return result;
}

取反加1求补码的补码;

6.isAsciiDigit

返回1,如果输入的时ascii中 ‘0’ ~ ‘9’ 的字符,否则返回0;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 
* isAsciiDigit - return 1 if 0x30 <= x <= 0x39 (ASCII codes for characters '0' to '9')
* Example: isAsciiDigit(0x35) = 1.
* isAsciiDigit(0x3a) = 0.
* isAsciiDigit(0x05) = 0.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 15
* Rating: 3
*/
int isAsciiDigit(int x) {
int mini = 0x80<<24;
int min = (~0x2F + x) & mini; //正确时,min是0000...
int max = (~0x39 + x) & mini; //正确时,max是1000...
int result = (min^max)>>31;
result = ~result + 1;
return result;
}

用位级运算判断x是否在这个范围内;

已知~x + y(y小于等于x),该值为负数,而y大于x时,这个值是非负数;

因此约定两个范围数:0x2F,0x39;

结果如果在区间内,则两个算出的值必为 正数 与 负数,则返回值用符号位来计算,需要用到最小补码来&运算得到符号位;

注意移位是符号移位,最后得变一下;

7.conditional

实现三目运算 x ? y : z

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 
* conditional - same as x ? y : z
* Example: conditional(2,4,5) = 4
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 16
* Rating: 3
*/
int conditional(int x, int y, int z) {
x = !x;
x = ~x + 1;
int result = (~x&y)|(x&z)
return result;
}

最后的结果是y,z中的值,根据前面题里的规律,可以想象让其中一个与全0&,另一个与全1&,再或,可以得到其中之一的结果,这个全0与全1就由x实现;

8.isLessOrEqual

如果x <= y 则返回1,否则返回0;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 
* isLessOrEqual - if x <= y then return 1, else return 0
* Example: isLessOrEqual(4,5) = 1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 24
* Rating: 3
*/
int isLessOrEqual(int x, int y) {
int r1 = (x>>31) & ~(y>>31); //是否x是负数,y是正数
r1 = !!r1;
int r2 = !((x>>31)^(y>>31)); //是否x,y同号
int r3 = (!(y + ~x + 1))>>31; //y-x的符号
r3 = !!r3;
int result = r1|(r2&r3);
return result;
}

比大小,先看符号,符号相同比差与0的大小;

9.logicalNeg

实现逻辑非且不使用 ! ;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 
* logicalNeg - implement the ! operator, using all of
* the legal operators except !
* Examples: logicalNeg(3) = 0, logicalNeg(0) = 1
* Legal ops: ~ & ^ | + << >>
* Max ops: 12
* Rating: 4
*/
int logicalNeg(int x) {
int result = ((~x+1)>>31)^(x>>31);
result = ~result + 1;
return result;
}

0的补码是自己,正数的补码是负数,负数的补码是正数;

根据符号的不同,用异或来判断是否不同,同为0(此时全为0),不同为非0(此时全为1);

再求补码一次让符号填充从0变0或者从-1变1;

10.howManyBits

求一个数用补码表示最少需要多少位;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* howManyBits - return the minimum number of bits required to represent x in
* two's complement
* Examples: howManyBits(12) = 5
* howManyBits(298) = 10
* howManyBits(-5) = 4
* howManyBits(0) = 1
* howManyBits(-1) = 1
* howManyBits(0x80000000) = 32
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 90
* Rating: 4
*/
int howManyBits(int x) {
x = x^(x>>31); //得到负数补码,正数不变
int A,B,C,D,E,F;
A = !!(x>>16)<<4; //看最高16位有无1,如果有,A为16;
x = x>>A; //如果上面成立,则消掉后面16位(后面的数字不讨论,折半讨论有1的区域)
B = !!(x>>8)<<3; //看原数据最高8位有无1,有则B为8;
x = x>>B; //成立则消掉后面8位(相当于总地消掉原数据后面24位)
C = !!(x>>4)<<2; //同理,直到遍历到1的准确位置,之后把记的数字加起来,就是需要表示的最小数量
x = x>>C;
D = !!(x>>2)<<1;
x = x>>D;
E = !!(x>>1);
x = x>>E;
F = x;
int result = A + B + C + D + E + F + 1; //+1表示符号位
return result;
}

这道题就是需要找这个数字二进制补码最高位为1的位数;

所以我们可以用位运算来模拟遍历,找最高位的1,可以一半一半地来找;

这里要思考正负数的问题:正就是最高位为1,但负数时最高位为0,统一一下,就设置个判断正负,让其全为找1;

11.floatScale2

求2乘一个浮点数;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 
* floatScale2 - Return bit-level equivalent of expression 2*f for
* floating point argument f.
* Both the argument and result are passed as unsigned int's, but
* they are to be interpreted as the bit-level representation of
* single-precision floating point values.
* When argument is NaN, return argument
* Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
* Max ops: 30
* Rating: 4
*/
unsigned floatScale2(unsigned uf) {
int exp = (uf&0x7F800000)>>23; //求指数
if(exp==0) //无穷小和0的情况,返回乘2的值
return uf<<1|(!!(uf>>31));
if(exp==255) //无穷大和NaN的情况,返回原值(它们乘2等于本身)
return uf;
exp = exp + 1;
if(exp==255) //剩下的情况则是如果指数+1为255,那就返回原符号无穷大,否则返回指数加1后的原符号数
return 0x7F800000|(!!(uf>>31));
else
result = (exp<<23)|(uf&0x807FFFFF);
return result;
}

浮点数这块可以用if while这种判断了,那可以就可以更直接地区分无穷,NaN,0的情况;

无穷大和NaN的指数都是255,而无穷小和0的指数都是0;

12.floatFloat2Int(f)

将浮点数转换为整数;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 
* floatFloat2Int - Return bit-level equivalent of expression (int) f
* for floating point argument f.
* Argument is passed as unsigned int, but
* it is to be interpreted as the bit-level representation of a
* single-precision floating point value.
* Anything out of range (including NaN and infinity) should return
* 0x80000000u.
* Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
* Max ops: 30
* Rating: 4
*/
int floatFloat2Int(unsigned uf) {
int exp = (uf&0x7F800000)>>23;
int frac = (uf&0x007FFFFF)|0x00800000; //或上0x00800000是为了加上省掉的1
if((exp<127)||(!uf)) return 0; //特殊情况
if(exp>158) return 0x80000000u; //指数大于31+127
if(exp>150) frac <<= (exp-150); //把小数转换成整数
else frac >>= (150-exp);
if(!((frac>>31)^(uf>>31))) return frac; //符号相同
else if(frac>>31) return 0x80000000u; //原符号为正溢出
else return ~frac+1; //原负数情况
}

先想想溢出的情况:指数大于了31+127,或者原来的符号是正,变换后变成了负;

另一种特殊情况是:原来的数字是小数(也就是指数小于0+127)或者0,则直接返回0;

原来的符号是负,变换后为正,就求一次补码;

其他的就是正常返回变换过后的值就行了;

13.floatPower2(x)

求浮点2的x次幂;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 
* floatPower2 - Return bit-level equivalent of the expression 2.0^x
* (2.0 raised to the power x) for any 32-bit integer x.
*
* The unsigned value that is returned should have the identical bit
* representation as the single-precision floating-point number 2.0^x.
* If the result is too small to be represented as a denorm, return
* 0. If too large, return +INF.
*
* Legal ops: Any integer/unsigned operations incl. ||, &&. Also if, while
* Max ops: 30
* Rating: 4
*/
unsigned floatPower2(int x) {
int exp = x + 127;
if(exp<=0) return 0;
if(exp>=255) return 0xFF<<23;
return exp<<23;
}

2的几次幂只需要在指数上加几次就行了,且题目上说太小就返回0,太大就返回INF;

那么意思就为无穷大和NaN返回INF,无穷小返回0;

总结

实验题是学习内容的一个运用总和,所以必须要自己动手做,做不来的时候就回头去翻书,看看哪里没有理解到位;

这也反着衬托出学习的效率,所以应该思考如何去提高看书吸收的内容?是否可以看慢一点,学地更好一点呢?

需要归纳这些解题的思路,当以后遇到这类数字问题的时候,可以很快地反应过来进行解决;

阅读全文