Hexo

  • Home

  • About

  • Tags

  • Categories

  • Archives

  • Schedule

pwnable-kr4-flag

Posted on 2019-04-12 | Edited on 2019-08-16 | In pwn

题目要求是二进制逆向

先用64位IDA打开,观察

再根据题目中packed关键词推断存在加壳,再根据binary提示,使用winhex打开文件(也可查看IDA中Hex窗口)
可见存在UPX加壳,对于脱壳,Windows可以使用UPX Tool工具,Lunix可以使用命令upx -d 文件名

发现upx压缩了,linux下使用upx -d

解压缩之后在ida载入,发现关键代码如下:

找到后,输入flag

发现不对
所以是显示不完全
双击 进一步查看

或者将脱壳后的文件再用WinHex打开,搜索文本UPX得到flag

flag = UPX…? sounds like a delivery service :)

gdb

Posted on 2019-04-07 | Edited on 2019-05-13 | In pwn

gdb

用root权限的Terminal(或一般权限的Terminal)的vi编辑器编写一个C程序a.c:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main()
{
  int a = 1;
  int b = a;

  printf("a = %d, b =%d\n", a, b);

  return 0;
}

保存文件要按esc,这样就会退回vi的命令模式。
按完esc后输入冒号(英文的),然后就转换到了末行模式了,末行模式决定是否保存文件。
末行模式下可以按x来保存,x命令可以保存编辑好的文件。
也可以用到wq来保存,如果是q!则不保存哦,这点要自己谨记在心,这里我就保存一下vi编写的文件
来换切换vi窗口和命令行和终端命令行窗口的快捷键:
Ctl+Z (返回终端)
$fg (进入vi)

在可执行文件中加入源码信息

这个过程通过gcc来完成:

gcc –o a a.c -g

-o选项的作用是:对命令输出结果进行导入操作,这里是把gcc –o a a.c -g的操作结果输出到文件a(文件名可以自定义)中进行保存。

-g选项的作用是:在可执行文件中加入源码信息,比如:可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件都嵌入到可执行文件中,而是在调试时必须保证gdb能找到源文件。

进入gdb

gdb调试常用命令

[1] start

  用start命令开始执行程序:

1
2
3
4
5
6
7
(gdb) start
Temporary breakpoint 1 at 0x40052e: file a.c, line 5.
Starting program: /root/2/02/a

Temporary breakpoint 1, main () at a.c:5
5 int a = 1;
(gdb)

  gdb提示准备执行a.c程序的第六行代码。然后继续用(gdb)提示需要输入的命令。

[2] 单步执行(n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(gdb) start
Temporary breakpoint 1 at 0x652: file a.c, line 5.
Starting program: /home/linhua/a

Temporary breakpoint 1, main () at a.c:5
5 int a=1;
(gdb) n
6 int b=a;
(gdb) n
8 printf("a=%d,b=%d\n",a,b);
(gdb) n
a=1,b=1
9 return 0;
(gdb) quit
A debugging session is active.

Inferior 1 [process 11415] will be killed.

Quit anyway? (y or n) y

在start命令后,每输入一个n就能够单步执行一条语句(输入一个命令后,直接回车表示最近输入命令的含义)。当程序执行完时,可以输入quit命令来退出gdb模式。

[3] gdb断点调试


gdb a会进入a可执行程序的gdb模式,start命令就使程序准备运行程序中的第一条语句。b 8是breakpoint 8的简写(breakpoint的参数也可以以是某个函数名,表示在此函数处设置一个断点),表示在程序第八行设置一个断点。c是continue的缩写,表示继续运行程序,程序会在设置断点处停下来。displayb表示将b的值显示出来(undisplay取消对变量的跟踪),然后再输入单步调试命令n(next)就可以使程序继续运行。

  可见断点有助于快速跳过没有问题的代码,然后在有问题的代码上慢慢走慢慢分析,“断点加单步”是使用调试器的基本方法。至于应该在哪里设置断点,怎么知道哪些代码可以跳过,而哪些代码要慢慢走,也要通过对错误现象的分析和假设来确定,以前我们用printf打印中间结果时,也要分析应该在哪里插入printf,打印哪些中间结果,调试的基本思路是一样的。

[4]info

  一次调试可以设置多个断点,用info命令可以查看已经设置的断点:

[5]delete

  每个断点都有一个编号(有的断点行数不一样,但地址却一样,有的地方不能够设置断点或者说与上一个设置的断点等效),可以用编号指定删除某个断点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
......
(gdb) b 7
Breakpoint 2 at 0x40053b: file a.c, line 7.
(gdb) b 8
Note: breakpoint 2 also set at pc 0x40053b.
Breakpoint 3 at 0x40053b: file a.c, line 8.
(gdb) i breakpoints
Num Type Disp Enb Address What
breakpoint keep y 0x000000000040053b in main at a.c:7
breakpoint keep y 0x000000000040053b in main at a.c:8
(gdb) delete 3
(gdb) i breakpoints
Num Type Disp Enb Address What
breakpoint keep y 0x000000000040053b in main at a.c:7
(gdb)

有时候一个断点暂时不用可以禁用掉而不必删除,这样以后想用的时候可以直接启用,而不必重新从代码里找应该在哪一行设断点,这个过程用 disable 和 enable 来完成。

[6]条件断点 (break 和run)

  gdb的断点功能非常灵活,还可以设置断点在满足某个条件时才激活,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
......
//先把其余的断点删掉。
(gdb) b 9 if a == 2
Breakpoint 5 at 0x400552: file a.c, line 9.
(gdb) i breakpoints
Num Type Disp Enb Address What
breakpoint keep y 0x0000000000400552 in main at a.c:9
stop only if a == 2
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/2/02/a
a = 1, b = 1
[Inferior 1 (process 22968) exited normally]
(gdb)

 r表示从头开始运行程序,在a==2的条件下中断才有效。a不等于2,所以中断无效。 

[7] gdb的观察点(watch 和c)

  断点是当程序执行到某一代码行时中断,而观察点是当程序访问某个存储单元时中断,如果我们不知道某个存储单元是在哪里被改动的,这时候观察点尤其有用。

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
30
31
32
33
34
root@iZ2zeeailqvwws5dcuivdbZ:~/2/02# gdb a
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a...done.
(gdb) start
Temporary breakpoint 1 at 0x40052e: file a.c, line 5.
Starting program: /root/2/02/a

Temporary breakpoint 1, main () at a.c:5
5 int a = 1;
(gdb) watch b
Hardware watchpoint 2: b
(gdb) c
Continuing.

Hardware watchpoint 2: b

Old value = 0
New value = 1
main () at a.c:8
8 printf("a = %d, b = %d\n", a, b);
(gdb)

  程序执行到b存储单元,将此执行单元执行前后的值都显示出来。

[8] 段错误

  如果程序运行时出现段错误,用gdb可以很容易定位到究竟是哪一行引发的段错误。在gdb中运行,遇到段错误会自动停下来,这时可以用命令查看当前执行到哪一行代码了。

  gdb显示段错误出现在 _IO_vfscanf 函数中,用bt命令可以看到是哪一个函数调用了它。

gdb基本命令

gdb有许多有用的命令如list(显示源代码),这样就可以结合源码与调试信息更好的进行调试。将gdb常用命令摘抄如下表:
l <n> 输出第n行到n+9行的源代码

break <n> 在第n行设置断点

info break 查看断点信息

r 运行

n 单步执行

c 继续执行

p varName 输出变量值

q 退出

命令 描述
backtrace(bt) 查看各级函数调用及参数
finish 连续运行到当前函数返回为止,然后停下来等待命令
frame(f) 帧编号 选择栈帧
info(i) locals 查看当前栈帧局部变量的值
list(l) 列出源代码,接着上次的位置往下列,每次列十行
list 行号 列出第几行开始的源代码list
函数名 列出某个函数的源代码
next(n) 执行下一行语句
print(p) 打印表达式的值,通过表达式的值可以修改变量的值或者调用函数
quit(q) 退出gdb调试环境
set var 修改变量的值
start 开始执行程序,停在main函数第一行语句前面等待命令
step(s) 执行下一行语句,如果有函数则进入到函数中
break(b) 行号 在某一行设置断点
break 函数名 在某个函数开头设置断点
break(b)… if… 设置条件断点
continue(c) 从当前位置开始连续运行程序
delete breakpoints 断点号 删掉此号的断点
display 变量名 跟踪查看某个变量,每次停下来都显示它的值
disable breakpoints 断点号 禁用此断点
enable 断点号 启用此断点
info(i) breakpoints 查看当前设置了哪些断点
run(r) 从头开始连续运行程序
undisplay 跟踪显示行号 取消跟踪显示
watch 设置观察点
info(i) watchpoints 查看当前设置了哪些观察点
x 从某个位置开始打印存储单元的内容,全部当成字节来看,而不区分哪个字节属于哪个变量
disassemble 反汇编当前函数或者指定的函数,单独用disassemble命令是反汇编当前函数,如果disassemble命令后面跟函数名或地址则反汇编指定的函数。
si 可以一条指令一条指令地单步调试。
info registers 可以显示所有寄存器的当前值。在gdb中表示寄存器名时前面要加个$,例如p $esp可以打印esp寄存器的值。
set follow-fork-mode child/parent 设置gdb在fork之后跟踪子进程/父进程
set args ‘command-line’ 给执行的程序传命令行参数
s(stepin) 进入子函数

checksec(此处使用gdbdb里peda插件里自带的)

一开始不行,运行gdb发现

发现应该是peda没弄好,重新搞一下
然后再次输入 gdb

可以看到成功下面红色那一行,gdb-peda
然后就可以用peda的checksec功能了

WINDOWS和LINUX下的内存保护机制

一、LINUX下的内存防护机制

0x00、checksec

(就是上面的工具了)
checksec 用来检查可执行文件属性,例如PIE, RELRO, PaX, Canaries, ASLR, Fortify Source等等属性。
一般来说,如果是学习二进制漏洞利用的朋友,建议大家使用gdb里peda插件里自带的checksec功能,如下:

0x01、CANNARY

CANNARY(栈溢出保护)是一种缓冲区溢出攻击缓解手段。
启用栈保护后,函数开始执行的时候会先往栈里插入cookie信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary。
gcc在4.2版本中添加了-fstack-protector和-fstack-protector-all编译参数以支持栈保护功能,4.9新增了-fstack-protector-strong编译参数让保护的范围更广。
这种方法很像windows下的启用GS选项

0x02、FORTIFY

这个网上的信息很少因此举个例子来描述:
void fun(char s) {
char buf[0x100];
strcpy(buf, s);
/
Don’t allow gcc to optimise away the buf */
asm volatile(“” :: “m” (buf));
}

用包含参数-U_FORTIFY_SOURCE编译
08048450 :
push %ebp ;
mov %esp,%ebp

sub $0x118,%esp ; 将0x118存储到栈上
mov 0x8(%ebp),%eax ; 将目标参数载入eax
mov %eax,0x4(%esp) ; 保存目标参数
lea -0x108(%ebp),%eax ; 数组buf
mov %eax,(%esp) ; 保存
call 8048320 <strcpy@plt>

leave ;
ret
用包含参数-D_FORTIFY_SOURCE=2编译
08048470 :
push %ebp ;
mov %esp,%ebp

sub $0x118,%esp ;
movl $0x100,0x8(%esp) ; 把0x100当作目标参数保存
mov 0x8(%ebp),%eax ;
mov %eax,0x4(%esp) ;
lea -0x108(%ebp),%eax ;
mov %eax,(%esp) ;
call 8048370 <__strcpy_chk@plt>

leave ;
ret
我们可以看到gcc生成了一些附加代码,通过对数组大小的判断替换strcpy, memcpy, memset等函数名,达到防止缓冲区溢出的作用。

0x03、NX

NX即No-eXecute(不可执行)的意思,NX的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。和windows下的DEP原理相同。
关于NX的绕过方式在linux叫做Ret2libc,与WINDOWS下的ROP大相径庭。
既然注入Shellcode无法执行,进程和动态库的代码段怎么也要执行吧,具有可执行属性,那攻击者能否利用进程空间现有的代码段进行攻击,答案是肯定的。
linux下shellcode的功能是通过execute执行/bin/sh,那么系统函数库(Linux称为glibc)有个system函数,它就是通过/bin/sh命令去执行一个用户执行命令或者脚本,我们完全可以利用system来实现Shellcode的功能。EIP一旦改写成system函数地址后,那执行system函数时,它需要获取参数。而根据Linux X86 32位函数调用约定,参数是压到栈上的。噢,栈空间完全由我们控制了,所以控制system的函数不是一件难事情。
这种攻击方法称之为ret2libc,即return-to-libc,返回到系统库函数执行 的攻击方法。
有关ret2libc的攻击方式可以参考http://blog.csdn.net/linyt/article/details/43643499。

0x04、PIE

一般情况下NX和地址空间分布随机化会同时工作。在linux下内存空间随机化被称作PIE。
内存地址随机化机制,有以下三种情况0 - 表示关闭进程地址空间随机化。1 - 表示将mmap的基址,stack和vdso页面随机化。2 - 表示在1的基础上增加栈(heap)的随机化。

可以防范基于Ret2libc方式的针对DEP的攻击。ASLR和DEP配合使用,能有效阻止攻击者在堆栈上运行恶意代码。
Built as PIE:位置独立的可执行区域(position-independent executables)。这样使得在利用缓冲溢出和移动操作系统中存在的其他内存崩溃缺陷时采用面向返回的编程(return-oriented programming)方法变得难得多。

0x05、RELRO

在Linux系统安全领域数据可以写的存储区就会是攻击的目标,尤其是存储函数指针的区域. 所以在安全防护的角度来说尽量减少可写的存储区域对安全会有极大的好处.
GCC, GNU linker以及Glibc-dynamic linker一起配合实现了一种叫做relro的技术: read only relocation.大概实现就是由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读.
RELRO设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。
有关RELRO的技术细节 https://hardenedlinux.github.io/2016/11/25/RelRO.html。
有关GOT攻击的技术原理参考 http://blog.csdn.net/smalosnail/article/details/53247502。

windows下的

二、windows下的内存保护机制

0x00、二进制漏洞

二进制漏洞是可执行文件(PE、ELF文件等)因编码时考虑不周,造成的软件执行了非预期的功能。二进制漏洞早期主要以栈溢出为主。
我们都知道在C语言中调用一个函数,在编译后执行的是CALL指令,CALL指令会执行两个操作:
(1)、将CALL指令之后下一条指令入栈。
(2)、跳转到函数地址。
函数在开始执行时主要工作为保存该函数会修改的寄存器的值和申请局部变量
空间,而在函数执行结束时主要的工作为:
(1)、将函数返回值入eax
(2)、恢复本函数调用前的寄存器值
(3)、释放局部变量空间
(4)、调用ret指令跳转到函数调用结束后的下一条指令(返回地址)
栈溢出指的是局部变量在使用过程中,由于代码编写考虑不当,造成了其大小超出了其本身的空间,覆盖掉了前栈帧EBP和返回地址等。由于返回地址不对,函数调用结束后跳转到了不可预期的地址,造成了程序崩溃。
早期的栈溢出漏洞利用就是将函数的返回地址覆盖成一个可预期的地址,从而控制程序执行流程触发shellcode。漏洞发生时,能控制的数据(包含shellcode)在局部变量中,局部变量又存在于栈上面,因此要想执行shellcode必须将程序执行流程跳转到栈上。
shellcode存好了,返回地址也可控,如果将返回地址改写为shellcode地址就OK了,可偏偏栈的地址在不同环境中是不固定的。
这时候有聪明的程序员发现了一条妙计,栈地址不固定,但是程序地址是固定的。通过在程序代码中搜索jmp esp指令地址,将返回地址改成jmp esp的地址,就可以实现控制程序执行流程跳转到栈上执行shellcode。

0x01、启用GS选项

启用GS选项是在编辑器中可以启用的一项选择。
启用GS选项之后,会在函数执行一开始先往栈上保存一个数据,等函数返回时候检查这个数据,若不一致则为被覆盖,这样就跳转进入相应的处理过程,不再返回,因此shellcode也就无法被执行,这个值被称为“Security cookie”。
感兴趣的同学可以编写一个DEMO启用GS后编译,使用IDA便可以看软件到调用Check_Security_Cookie()检查栈是否被覆盖。
可是他们忽略了异常处理SEH链也在栈上因此可以覆盖SEH链为jmp esp的地址,之后触发异常跳转到esp执行shellcode。
有关SEH链的的技术可以参考http://blog.csdn.net/hustd10/article/details/51167971。

0x02、SafeSEH

SafeSEH是在程序编译的时候,就将所有的异常处理函数进行注册。凡是执行过程中触发异常后,都要经过一个检验函数,检查SEH链指向的地址是否在注册的列表中。
可是再检验函数的逻辑中阻止执行的情况只有在SEH链指向模块(exe、dll)地址的情况下,如果SEH链指向的地址不在这些模块中,那就可以执行了。因此在程序中非模块的数据空间找到jmp esp,比方说nls后缀的资源文件等。或者是在支持JS脚本的软件中(浏览器等),通过脚本申请堆空间写入shellcode。

0x03、DEP

数据执行保护(DEP)指的是堆和栈只有读写权限没有执行权限。
对抗DEP的方式是将shellcode写入堆栈中,从程序自身的代码去凑到执行VirtualProtect()将shellcode所在内存属性添加上可执行权限,将函数返回值或者SEH链覆盖成代码片段的起始地址。这种利用程序自身碎片绕过DEP的方式被称作ROP,关于ROP的技术细节可以参考http://rickgray.me/2014/08/26/bypass-dep-with-rop-study.html。
ROP技术是通过拼凑代码碎片执行API,在最开始没有相应辅助工具的时候,构建ROP链是耗时耗力的。随着研究人员的增多,相应的辅助工具也被开发出来,ROP链的构建已经相对容易了。

0x04、ASLR

ROP技术的前提是代码片段的地址固定,这样才能知道往函数返回值或者SEH链中填写哪个地址。因此地址空间布局随机化(ASLR)应运而上,ALSR即是让exe、dll的地址全都随机。
对抗ASLR的方式是暴力把程序空间占满,全铺上shellcode,只要跳转地址没落在已有模块中,落在我们的空间中即可以执行了shellcode,但是这样做无法绕过DEP,这种将程序空间全部占满铺上shellcode的技术被称为堆喷射技术,堆喷射技术只能对抗ASLR,缺无法对抗ASLR+DEP的双重防护。
ASLR+DEP的双重防护使得大多数软件的漏洞只能造成崩溃,无法稳定利用。将程序空间占满的技术,称之为堆喷射(Heap Spraying),这种技术只能应用在可以执行JS等脚本的软件上,如浏览器等。
堆喷射通过大面积的申请内存空间并构造适当的数据,一旦EIP指向这片空间,就可以执行shellcode。堆喷射已经是不得已而为之,有时候会造成系统卡一段时间,容易被发现;另一点,如果EIP恰好指向shellcode中间部分就会造成漏洞利用失败,因此不能保证100%成功。
关于堆喷射技术可以参考http://blog.chinaunix.net/uid-24917554-id-3492618.html。
因为ASLR+DEP的双重防护使得PC上的软件若存在漏洞也无法稳定利用,因为safeSEH技术的存在大多数的软件安全研究者都转向了浏览器+JS,可是因为JS的效率问题,JS也渐渐的使用变少,此时FLASH的AS脚本渐渐进入了人们的视野,而且FLASH不仅仅在windows上,包括Linux和Andriod均可以使用。因此很多的软件安全研究人员转向了浏览器+AS。
由于二进制漏洞破坏了程序执行流程,因此如果执行了shellcode之后不做其他处理,软件会崩溃掉。通常的做法是shellcode调用ExitProcess退出进程,这样会造成软件打开了闪退掉。而Flash作为浏览器插件的存在,居然发展出很多不卡不闪不挂的漏洞,Flash漏洞利用研究如星火燎原般炽热。因此大多数的浏览器渐渐的不再支持FLASH插件,HTML5动画技术也渐渐的发展起来。

0x05、CFG

虽然FlASH的漏洞使得其他厂商有点无奈,但是微软并没有停止防守的脚步。微软在Win 8.1 Update 3以及Win 10中启用了一种抵御内存泄露攻击的新机制,即Control Flow Guard(CFG)——控制流防护。这项技术是为了弥补此前不完美的保护机制,例如地址空间布局随机化(ASLR)导致了堆喷射的发展,而数据执行保护(DEP)造成了漏洞利用代码中返回导向编程(ROP)技术的发展。
有关CFG控制流保护的分析可以参考http://www.freebuf.com/articles/security-management/58373.html。

绕过CFG的研究也在不断的进行比如http://blog.nsfocus.net/win10-cfg-bypass/ 以及XCon2016的议题《JIT喷射技术不死—利用WARP Shader JIT喷射绕过控制流保护(CFG)》。

摘自https://www.52pojie.cn/forum.php?mod=viewthread&tid=588708

参考:https://www.cnblogs.com/chenmingjun/p/8280889.html

python语法

Posted on 2019-04-07 | In python

python的基本语法和脚本格式

2019中国杭州网络安全技能大赛-pwn

Posted on 2019-04-07 | In pwn

主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
FILE *v3; // rdi
void *ptr; // ST18_8
void *v5; // ST20_8

setbuf(stdin, 0LL);
v3 = stdout;
setbuf(stdout, 0LL);
ptr = (void *)sub_400915(v3, 0LL);
v5 = (void *)sub_4009A0(v3);
puts("Thank you for you share!!");
free(ptr);
free(v5);
return 0LL;
}

setbuf()函数

C标准库函数,主要用于打开和关闭缓冲机制。
函数名: setbuf
功 能: 把缓冲区与流相联
用 法: void setbuf(FILE steam, char buf);
参数
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流。
buffer – 这是分配给用户的缓冲,它的长度至少为 BUFSIZ 字节,BUFSIZ 是一个宏常量,表示数组的长度。
返回值
该函数不返回任何值。
说明:setbuf函数具有打开和关闭缓冲机制。为了带缓冲进行I/O,参数buf必须指向一个长度为BUFSIZ(定义在stdio.h头文件中)的缓冲区。通常在此之后该流就是全缓冲的,但是如果该流与一个终端设备相关,那么某些系统也可以将其设置为行缓冲。为了关闭缓冲,可以将buf参数设置为NULL。

free

函数释放指针ptr指向的空间,以供以后使用。指针ptr 必须由先前对malloc(), calloc(), realloc()的调用返回

pwnable.kr2-col

Posted on 2019-04-05 | Edited on 2019-04-06 | In pwn

代码

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
30
31
32
33
34
35
36
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC; //之后要想办法让函数返回的值等于这个
unsigned long check_password(const char* p){
int* ip = (int*)p;
/*强制类型转换,char转为int。
注意:char为1字节,int为4字节因此输入5个int累加(即输入的是20字节),
可以用十六进制表示数,如/x01就是一个字节,
我们可以让一个int值为/x01/x01/x01/x01 它的值为16843009(10)这个,总之就是凑5个int相加为指定的值
*/
int i;
int res=0;
for(i=0; i<5; i++){ //这里循环五次
res += ip[i]; //将五个数累加
}
return res; //返回值
}

int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){ //告诉我们输入要为20个字节
printf("passcode length should be 20 bytes\n");
return 0;
}

if(hashcode == check_password( argv[1] )){ //这行给出flag,即想办法让这两个相等
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}

解题思路

首先定位到

1
2
3
4
if(hashcode == check_password( argv[1] )){  //这行给出flag,即想办法让这两个相等
system("/bin/cat flag");
return 0;
}

说明要想得到flag就要让 你的输入 传入函数后得到一个与hashcode一样的值
if(strlen(argv[1]) != 20){ 这个if语句也告诉我们输入要为20个字节

int* ip = (int*)p;
这里对p进行了一次强制转化,从原本读出来的char转化为了int
char占一个字节,8个比特。int占4个字节,32个比特。 (十六进制0x01就是一个字节了,那么十六进制下01010101就可以表示一个int数了)
hashcode == check_password( argv[1] )实现了一个累加
这里需要构造5个int类型的数(总共是20字节)相加为0x21DD09EC
实际上就是把5*4=20个字符,每4个分成一组累加。
普遍的一个想法: 0x1DD905E8+0x01010101*4=0x21DD09EC 这里用01010101来构造
那么开始操作,实践

探究数据在内存中的存储方式

1
2
3
4
5
6
7
8
9
10
#include<bits/stdc++.h>
using namespace std;
int main()
{
char str[20]="ABCDEFGHIJKLMNOPQRS";
int *p=(int *)str;
for(int i=0; i<5; i++)
cout<<p[i]<<endl;
return 0;
}
1
2
3
4
5
1145258561
1212630597
1280002633
1347374669
5460561

转为16进制:

1
2
3
4
5
0x44434241
0x48474645
0x4C4B4A49
0x504F4E4D
0x535251

如果我们输入A-T这20个字符,那么会报出一下的错误:
[Error] initializer-string for array of chars is too long [-fpermissive]
说明数组的限制了,那么推理出应是结束符需要占一个的位子
接着测试

发现输出了一个空行,那么应该是结束符了

接着进行测试

1
2
3
4
5
6
7
8
9
10
11
12
#include<bits/stdc++.h>
using namespace std;
int main()
{
char str[20]={'\x01','\x02','\x03','\x04','\x05','\x06','\x07','\x08','\x09','\x10','\x11','\x12','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01'};
int *p=(int *)str;
for(int i=0; i<5; i++){
cout<<dec<<p[i]<<endl;
cout<<hex<<p[i]<<endl;
}
return 0;
}

得到

1
2
3
4
5
6
7
8
9
10
67305985
4030201
134678021
8070605
303108105
12111009
16843009
1010101
16843009
1010101

尝试构造payload

0x1DD905E8+0x010101014=0x21DD09EC
我们通过之前的观察发现转换为int时,四个字符是倒过来存储合并的, 0x1DD905E8我们要写成’\xE8’,’\x05’,’\xD9’,’\x1D’才行
那么尝试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<bits/stdc++.h>
using namespace std;
int main()
{

char str[20]={'\xE8','\x05','\xD9','\x1D','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01','\x01'};

int *p=(int *)str;
for(int i=0; i<5; i++){
cout<<dec<<p[i]<<endl;
cout<<hex<<p[i]<<endl;
}

int tmp=0;
for(int i=0; i<5; i++)
tmp+=p[i];
cout<<dec<<tmp;
printf("\n");
cout<<hex<<tmp;
return 0;
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
500762088
1dd905e8
16843009
1010101
16843009
1010101
16843009
1010101
16843009
1010101
568134124
21dd09ec

发现构造成功,得到我们想要的0x21DD09EC

终端输入

大佬的爬虫脚本

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python
import subprocess
from pwn import *

hashcode = 0x21DD09EC

a = 0x01020304
b = hashcode - 4 * a

payload = p32(a) * 4 + p32(b)

target = subprocess.Popen(args=['/home/col/col',payload])

脚本来源网址及脚本分析

另解
python -c “print ‘\xe8\x05\xd9\x1d’+16*’\x01’”|xargs ./col因为是小端机器需要反序,xargs可以把数据当作命令行参数传给制定的程序。

来自https://blog.csdn.net/u010334666/article/details/81193660的python代码

1
2
3
4
5
6
7
8
9
10
 collision.py+                                                      buffers 
1 #!/usr/bin/env python
2 # coding=utf-8
3 from pwn import *
4 pwn_ssh=ssh(host='pwnable.kr',user='col',password='guest',port=2222)
5 print(pwn_ssh.connected())
6 sh=pwn_ssh.process(argv=['collision','\xc9\xce\xc5\x06' * 4 + '\xcc\xce\xc5\x06'],executable='./col')#那些数字是0x21DD09EC/5计算得到,然后用0x21DD09EC-得到的结果*4
7 print(sh.recvall())
~
~


补充

‘\x’

\xhh 表示1到2位十六进制所代表的任意字符
字符型常量所表示的值是字符型变量所能包含的值。我们可以用ASCII表达式来表示一个字符型常量,或者用单引号内加反斜杠表示转义字符。
\x表示后面的字符是十六进制数,\o表示后面的字符是八进制数。例如十进制的17用十六进制表示就是
‘\x11’,用八进制表示就是‘\021’;

所有的ASCII码都可以用“\”加数字(一般是8进制数字)来表示。而C中定义了一些字母前加”\”来表示常见的那些不能显示的ASCII字符,如\0,\t,\n等,就称为转义字符,因为后面的字符,都不是它本来的ASCII字符意思了。

char *p

char *p

char* p和char *p
eg:
char *p=”abc123ABC”;//char p[]=”abc123ABC”
char* p是一个指针,根本没分配内存,他指向的”abc123ABC” 是只读的,不能改变,你在下面给他赋值肯定是错的
而char p[]是一个数组,已经分配内存,是将”abc123ABC” 复制到该内存里面,这个内存是可读写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const char *p; //*p是const,p可变:const 后面紧跟的是char,所以*p是一个char字符,不可变

const (char *) p;//p是const,*p可变:const 后面紧跟的是(char *)这个整体,所以p是char*类型,不可变。

char* const p; //p是const,*p可变:const 后面紧跟的是p,所以p不可变

const char* const p; //p和*p都是const:第一个const后面紧跟的是char,所以char类型的字符*p不可变;第二个const后面紧跟的是p,所以p不可变。

char const * p;// *p是const,p可变:const后面紧跟的是*,但是单独的*不能表明修饰的内容,所以将*p看成一个整体,所以const修饰的是*p,*p不可变。

(char*) const p;//p是const,*p可变:const紧跟的是p,所以p不可变。

char* const p;// p是const,*p可变:const紧跟的是p,所以p不可变。

char const* const p;// p和*p都是const:第一个const紧跟的是*,不能表明修饰的内容,将后面整体的(* const p)看成一个整体,那就说明*p不可变,第二个const后面紧跟的是p,所以p不可变。


原文:https://blog.csdn.net/qq_38204481/article/details/79711702
原文:https://blog.csdn.net/jack0201/article/details/75071980
原文:https://blog.csdn.net/u010334666/article/details/81193660

pwnable.kr1-fd

Posted on 2019-04-04 | Edited on 2019-04-16 | In pwn


题目最下方有个ssh命令,这是linux的远程连接命令,(host=pwnable.kr , user =fd password=2222 password=guest)
可以打开linux终端,直接输入那行命令进行连接
ssh fd@pwnable.kr -p2222
输入那行命令后输入密码,有时候会提示安全性问题,选择Y(yes),输入密码,输入密码的时候是不会回显的(显示在屏幕上)

密码为guest,然后ls(ls命令学习) 查看有什么文件,然后输入cat fd.c 查看fd.c的文件内容,之后发现有三个文件分别为fd、fd.c 、flag。
fd@ubuntu:~$ ls
fd fd.c flag

显然不能直接查看flag
fd@ubuntu:~$ cat flag
cat: flag: Permission denied
我们抓取源码

fd@ubuntu:~$ cat fd.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
if(argc<2){
printf("pass argv[1] a number\n");
return 0;
}
int fd = atoi( argv[1] ) - 0x1234; //此处设置fd的值,解题时应想办法为0
int len = 0;
len = read(fd, buf, 32); //让fd=0,我们就可以用标准输入(键盘)控制buf(缓存)的值了,
if(!strcmp("LETMEWIN\n", buf)){ //此处告诉我们的目标是让它等于"LETMEWIN\n"
printf("good job :)\n");
system("/bin/cat flag");
exit(0);
}
printf("learn about Linux file IO\n");
return 0;

}

随便输入,会出现啥?

1
2
3
4
5
6
7
8
fd@ubuntu:~$ ./fd
pass argv[1] a number
fd@ubuntu:~$ ./fd 3
learn about Linux file IO
fd@ubuntu:~$ ./fd 2
learn about Linux file IO
fd@ubuntu:~$ ./fd 1
learn about Linux file IO

那么开始分析

所需知识:

文件描述符

  对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读或写一个文件时,使用open或create返回的文件描述符表示该文件,将其作为参数传给read或write函数。

read 函数

read函数———详解

ssize_t read(int filedes, void *buf, size_t nbytes);
// 返回:若成功则返回读到的字节数,若已到文件末尾则返回0,若出错则返回-1
// filedes:文件描述符[0-标准输入(stdin),1-标准输出(stdout),2-标准错误输出(stderr)]
// buf:读取数据缓存区
// nbytes:要读取的字节数
有几种情况可使实际读到的字节数少于要求读的字节数:
1)读普通文件时,在读到要求字节数之前就已经达到了文件末端。例如,若在到达文件末端之前还有30个字节,而要求读100个字节,则read返回30,下一次再调用read时,它将返回0(文件末端)。
2)当从终端设备读时,通常一次最多读一行。
3)当从网络读时,网络中的缓存机构可能造成返回值小于所要求读的字结束。
4)当从管道或FIFO读时,如若管道包含的字节少于所需的数量,那么read将只返回实际可用的字节数。
5)当从某些面向记录的设备(例如磁带)读时,一次最多返回一个记录。
6)当某一个信号造成中断,而已经读取了部分数据。
main函数中:

argc

argc是命令行参数个数,char argv[]是指所有命令行参数,
对于C语言int main(int argc char
argv[])来说,argc保存的是命令行总的参数个数(包括程序名),argv这是传入参数的数组.
举个例子,当你执行: ./test 1 2 3 时,argc = 4 而 argv[0] = “test”,argv[1] = 1,argv[2] = 2,argv[3] = 3

char *envp[]

*envp[]是环境变量(argv[1]位置处存放的是命令行输入的第一个参数)

atoi

atoi函数的作用是把字符转化为int型数据。 命令行读入的参数默认是字符型(如:atio(10)=‘10’)

ssize_t read(int fd,void * buf,size_t count);

函数说明
read()会参数fd所有的文件传送count个字节到buf指针所指的内存中。若参数count为0,则read )不会有作用并返回0返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。

整数值 名称 < unistd.h > 符号常量[1] < stdio.h > 文件流[2]

1
2
3
0   标准输入    STDIN_FILENO    标准输入
1 标准输出 STDOUT_FILENO 标准输出
2 标准错误 STDERR_FILENO 标准错误

推理:read(0,buf,32)表示从键盘读入至多32个字节到buf中

那么我们只要控制了FD的值为标准输入,那么buf中的值就可以用我们的键盘输入了,
目标是使FD为0,那么我们传进去的第一个参数就是为0x1234,即十进制的4660
这里要填十进制因为天0x1234中的0x它会读成字符的,然后把后面的字符变成10进制

解题

0为标准输入,1为标准输出,2为标准错误,而我们看到read(fd, buf, 32);//关键点,这里fd便是关键,若为0,我们自己输入后,变成功得到flag,所以,我们就要推回去,怎么让fd为0,也就是atoiargv[1]大小为0x1234(atoi是c语言的将字符串转化为数字,假设字符串为str =1234,他会转化为数字int=1234),而0x1234是16进制的,用计算器转换下为4660,也就是说我们要使argv[1]为4660,好,整个思路理清了,接下来便是怎么利用了

//这好像是脚本?蛮放着。。。

1
2
3
4
5
6
from pwn import *
4 pwn_ssh=ssh(host='pwnable.kr',user='fd',password='guest',port=2222)#远程连接
5 print(pwn_ssh.connected())#连接成功判断
6 sh=pwn_ssh.process(argv=['fd','4660'],executable='./fd')#加载进程
7 sh.sendline('LETMEWIN')#发送一行数据,在末尾加上\n
8 print(sh.recvall())#接受到EOF


原文:https://blog.csdn.net/u010334666/article/details/81192987
原文:https://blog.csdn.net/qq_38204481/article/details/79711702

汇编-实验:寻址方式在结构化数据访问中的应用

Posted on 2019-03-23 | In 汇编

大方向

用bx,si指向data

用bp,di指向table

想过一次循环(cx=21)完成 1年的所有数据,发现有难度,data段的数据要不断跳到不同的一堆数据中去。之后改成一次循环(cx=21)只完成一类数据。

如此就需要4段 代码完成(在一个代码段中)

每一小段通过21次循环,完成一个21年的一类数据(eg:完成21个年份;完成21年各个年份的收入…)

~~~asm

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
assume cs:codesg,ds:datasg
datasg segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995' ;表示21年的21个字符串

dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
;总收入的21个数据

dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
datasg ends
tablesg segment
db 21 dup('year summ ne ?? ')
tablesg ends
;使用bp时,默认段地址是ss,所以ss:bp+di指向table,ds:bx+si指向数据
codesg segment
start:
mov ax,datasg
mov ds,ax ;data段的段地址
mov ax,tablesg
mov ss,ax ;table段的段地址

mov bp,0 ;bp,di表示table的地址
mov bx,0 ;bx,si表示data放的地址
mov si,0
mov di,0
mov cx,21
year: mov ax,[bx+si]
mov [bp+di],ax
add si,2
add di,2
mov ax,[bx+si]
mov [bp+di],ax
add si,2 ;第一年结束
add bp,16
mov di,0
loop year ;至此年份结束

mov cx,21
add bx,84
mov si,0
mov bp,0
summ: mov di,5
mov ax,[bx+si]
mov [bp+di],ax
add si,2
add di,2
mov ax,[bx+si]
mov [bp+di],ax
add si,2 ;第一年结束
add bp,16

loop summ ;至此收入结束

mov cx,21
add bx,84
mov si,0
mov bp,0
mov di,10
people: mov ax,[bx+si]
mov [bp+di],ax
add si,2 ;第一年结束
add bp,16
loop people

mov cx,21
mov bp,0
mov di,0

income:
mov ax,[bp+di+5] ;被除数的低八位
add di,2
mov dx,[bp+di+5] ;被除数的高八位
add di,3
div word ptr [bp+di+5] ;除数
add di,3
mov [bp+di+5],ax ;ax保存的是商,将商输入到table中
;第一年结束
add bp,16
mov di,0
loop income

mov ax,4c00H
int 21H
codesg ends

end start

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85


assume cs:code,ds:data,ss:stack

data segment

db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
;以上是表示21年的21个字符串 year


dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
;以上是表示21年公司总收入的21个dword数据 sum

dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800

data ends

table segment
;0123456789ABCDEF
db 21 dup ('year summ ne ?? ')
table ends

stack segment stack
db 128 dup (0)
stack ends



code segment

start: mov ax,stack
mov ss,ax
mov sp,128 ;栈

mov ax,data
mov ds,ax ;data段的数据 ds

mov ax,table
mov es,ax ;tble段的数据 es

mov si,0 ;
;mov di,21*4 ;总收入 此处用si+21*4表示了(因为di表示的总收入也是4字节的所以可以用si代替)
mov bx,21*4 + 21*4 ;员工数
mov bp,0 ;tble段的偏移地址bp

mov cx,21
inputtable:
push ds:[si+0]
pop es:[bp+0]
push ds:[si+2]
pop es:[bp+2]
;年份的输入

mov ax,ds:[si+21*4+0] ;收入的输入
mov dx,ds:[si+21*4+2]
mov es:[bp+5],ax
mov es:[bp+7],dx

push ds:[bx] ;人员数的输入
pop es:[bp+0AH]

div word ptr ds:[bx] ;除法
mov es:[bp+0Dh],ax ;人均输入

add si,4
add bx,2
add bp,2
loop inputtable



mov ax,4C00H
int 21H



code ends



end start

汇编-实验:将每个的单词的前4个字母改为大写字母(【bx+si+3】)

Posted on 2019-03-20 | In 汇编

题目:

将每个的单词的前4个字母改为大写字母(

key

  • 两层的循环,用栈来暂存循环次数cx
  • 利用灵活的定位内存方法: 【bx+si+常数】
    bx表示行数,由于每个单词占用16个字节,故每次加16;si表示单词中的第几个字母,inc si;常数用来指示每个单词修改的起始地址

code 如下:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
assume cs:codesg,ds:datasg,ss:stacksg
datasg segment
db '1. display '
db '2. brows '
db '3. replace '
db '4. modify '
datasg ends

stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends

codesg segment
start:
mov ax,stacksg
mov ss,ax
mov sp,16

mov ax,datasg
mov ds,ax

mov cx,4
mov bx,0
s:
mov si,0
push cx
mov cx,3
s0:
mov al,[bx+si+3]
and al,11011111b
mov [bx+si+3],al
inc si
loop s0

add bx,16
pop cx
loop s


mov ax,4c00H
int 21H
codesg ends

end start

在设置好ds 之后

p指令

我们发现p指令在没遇到loop循环指令时,其实是和t指令没什么不一样的,都是执行一步

但是遇到loop时,p指令就会执行完此处循环

此时执行完了一次内层循环,可以看到第一个单词的字母都已经修改完毕
这里我们注意下p指令执行的语句loop 001A


NEXT
我们接着运行,此时会遇到一个loop指令(外层的),p指令运行后

我们可以看到这次p指令执行后,所有单词都已经修改完成,可见此次p指令完成的是外层循环
我们对比此次p指令执行的语句loop 0013 和之前的可以发现,这次的p指令执行的循环跳转是0013比之前的001A小,说明是比上次跳转到的地方更之前的语句,也证实此次p指令一次执行完了外层的循环,而上一次的p指令一次性执行的是内层

总之,两个p指令,一个一次性执行完了内层,一个一次性执行了外层(当前肯定是要先执行完内循环)
注意点:看p 指令执行的是哪个loop执行 loop的地址是关键

按g一次性执行完程序后

Sublime Text安装Package Control

Posted on 2019-03-18

参考
https://www.cnblogs.com/notemore/p/10354918.html 这篇文章
具体参考上面的文章,以及下载上面的资源
以下简单说明情况:
由于自动安装失败(主要是https://packagecontrol.io 这个被墙了 下载不下来导致的错误)手动安装不了package control

1.我们需要自己去网络上找现成的文件(package control.sublime-package 文件)
2.我们下载好文件放到目录后又会出现这个问题

因此又需要下载一个channel_v3.json 这个东西
弄完之后终于可以使用安装插件的功能了

汇编-实验:向内存依次传送数据(【bx】和loop)

Posted on 2019-03-18 | In 汇编

书上的实验4
题目:向内存 0:200-0:23F依次传送数据0-63(3FH),程序中只能使用9条指令,9条指令包括“mov ax,4c00h”和“int 21h”
第一代(严格上是有错误的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
assume cs:codesg
codesg segment
mov cx,0040h
mov ax,0020h
mov ds,ax
mov bx,0
s: mov [bx],bx
inc bx
loop s
mov ax,4c00H
int 21H
codesg ends

end

正确的应该是把
mov [bx],bx 改成
mov [bx],bl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
assume cs:codesg
codesg segment
mov cx,0040h
mov ax,0020h
mov ds,ax
mov bx,0
s: mov [bx],bl
inc bx
loop s
mov ax,4c00H
int 21H
codesg ends

end

当然不是做题目,没必要这样省代码,直接用两个寄存器分别表示自增的数字和自增的内存单元地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:codesg
codesg segment
mov cx,0040h
mov ax,0020h
mov ds,ax
mov bx,0
mov ax,0
s: mov [bx],ax
inc bx
inc ax
loop s
mov ax,4c00H
int 21H
codesg ends

end

1…345…9

lh

89 posts
12 categories
© 2020 lh
Powered by Hexo v3.8.0
|
Theme – NexT.Pisces v6.7.0